version 1.1
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
import javax.annotation.PreDestroy;
|
||||
import org.apache.sis.util.NullArgumentException;
|
||||
import org.geotoolkit.nio.IOUtilities;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.DigestUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class DocManager {
|
||||
|
||||
protected final Path tmpDir;
|
||||
|
||||
protected DocManager() {
|
||||
Path tmp = null;
|
||||
try {
|
||||
// Init folder to put docs into.
|
||||
tmp = Files.createTempDirectory("rhomeo_docs");
|
||||
} catch (IOException ex) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "Impossible to prepare documentation management.", ex);
|
||||
}
|
||||
|
||||
tmpDir = tmp;
|
||||
}
|
||||
|
||||
public void openDocument(final URL resource) {
|
||||
try {
|
||||
if (isHref(resource)) {
|
||||
Rhomeo.browse(resource);
|
||||
}
|
||||
final Path p = getPath(resource);
|
||||
if (p != null)
|
||||
Rhomeo.openFile(p.toFile());
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "Cannot open given resource : ".concat(String.valueOf(resource)), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getPath(URL resource) throws URISyntaxException, IOException {
|
||||
if (resource == null)
|
||||
throw new NullArgumentException("Given resource is null !");
|
||||
if ("file".equals(resource.getProtocol()))
|
||||
return Paths.get(resource.toURI());
|
||||
String md5 = DigestUtils.md5DigestAsHex(resource.toExternalForm().getBytes());
|
||||
|
||||
// HACK for win systems which are too dumb to open files without extension
|
||||
final String extension = IOUtilities.extension(resource);
|
||||
final Path target;
|
||||
if (extension != null && !extension.isEmpty())
|
||||
target = tmpDir.resolve(md5 + "." + extension);
|
||||
else
|
||||
target = tmpDir.resolve(md5);
|
||||
|
||||
if (!Files.exists(target)) {
|
||||
try (final InputStream source = resource.openStream()) {
|
||||
Files.copy(source, target);
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void clear() throws IOException {
|
||||
if (tmpDir != null)
|
||||
IOUtilities.deleteRecursively(tmpDir);
|
||||
}
|
||||
|
||||
private boolean isHref(URL resource) {
|
||||
return resource != null && resource.getProtocol().startsWith("http");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo;
|
||||
|
||||
import com.sun.javafx.PlatformUtil;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.data.UpdateInfo;
|
||||
import fr.cenra.rhomeo.core.preferences.net.NetPreferences;
|
||||
import fr.cenra.rhomeo.core.state.StateManager;
|
||||
import fr.cenra.rhomeo.fx.FXInitialDialogPane;
|
||||
import fr.cenra.rhomeo.fx.FXMainPane;
|
||||
import fr.cenra.rhomeo.fx.FXSplashScreen;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.regex.Matcher;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.DialogEvent;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.controlsfx.dialog.ExceptionDialog;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.geotoolkit.internal.GeotkFX;
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* Application launcher.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
public class Launcher extends Application {
|
||||
/**
|
||||
* Launches the application with the given arguments.
|
||||
*
|
||||
* @see #launch(String...)
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5)
|
||||
|
||||
// add SLF4JBridgeHandler to j.u.l's root logger, should be done once during
|
||||
// the initialization phase of your application
|
||||
SLF4JBridgeHandler.install();
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
|
||||
|
||||
// if(e instanceof Exception){
|
||||
// TaskManager.INSTANCE.submit(() -> {throw (Exception) e;});
|
||||
// }
|
||||
// else{
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "A throwable has not been caught.", e);
|
||||
// }
|
||||
});
|
||||
|
||||
// perform initialization and plugin loading tasks
|
||||
final InitialLoadingTask initTask = new InitialLoadingTask();
|
||||
showLoadingStage(initTask);
|
||||
TaskManager.INSTANCE.submit(initTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
try {
|
||||
Session.getInstance().close();
|
||||
} catch (IllegalStateException ex) {
|
||||
// The context was not already initialized, no need to close it.
|
||||
// Not a real exception since nothing to do
|
||||
}
|
||||
|
||||
TaskManager.INSTANCE.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display splash screen.
|
||||
*
|
||||
* @param task the related task to show progress
|
||||
* @throws IOException
|
||||
*/
|
||||
private void showLoadingStage(final InitialLoadingTask task) throws IOException {
|
||||
// Initialize splash screen
|
||||
final Stage splashStage = Rhomeo.newStage();
|
||||
splashStage.initStyle(StageStyle.TRANSPARENT);
|
||||
|
||||
final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fr/cenra/rhomeo/fx/FXSplashScreen.fxml"));
|
||||
final GridPane root = loader.load();
|
||||
final FXSplashScreen splashScreen = loader.getController();
|
||||
splashScreen.uiCancel.setVisible(false);
|
||||
splashScreen.uiProgressLabel.textProperty().bind(task.messageProperty());
|
||||
splashScreen.uiProgressBar.progressProperty().bind(task.progressProperty());
|
||||
|
||||
final Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add("/fr/cenra/rhomeo/splashscreen.css");
|
||||
scene.setFill(new Color(0, 0, 0, 0));
|
||||
|
||||
splashStage.setScene(scene);
|
||||
splashStage.show();
|
||||
|
||||
// If the task succeeded, meaning the initialisation is finished or an update has been found
|
||||
// and user wants to download it.
|
||||
task.setOnSucceeded(evt -> Platform.runLater(() -> {
|
||||
final Object value = task.getValue();
|
||||
// If value is an URL, then it is an update to download.
|
||||
// Otherwise, nothing to download keep going with the application start.
|
||||
if (value instanceof URLAndMD5) {
|
||||
final DownloadUpdateTask downloadTask = new DownloadUpdateTask((URLAndMD5) value);
|
||||
splashScreen.uiProgressLabel.textProperty().unbind();
|
||||
splashScreen.uiProgressLabel.textProperty().bind(downloadTask.messageProperty());
|
||||
splashScreen.uiProgressBar.progressProperty().unbind();
|
||||
splashScreen.uiProgressBar.progressProperty().bind(downloadTask.progressProperty());
|
||||
|
||||
TaskManager.INSTANCE.submit(downloadTask);
|
||||
|
||||
downloadTask.setOnCancelled(evt2 -> System.exit(0));
|
||||
|
||||
downloadTask.setOnFailed(evt2 -> Platform.runLater(() -> {
|
||||
GeotkFX.newExceptionDialog("Le téléchargement de la mise à jour a échoué !", downloadTask.getException()).showAndWait();
|
||||
System.exit(1);
|
||||
}));
|
||||
|
||||
downloadTask.setOnSucceeded((evt2) -> {
|
||||
Path newValue = downloadTask.getValue();
|
||||
try {
|
||||
Rhomeo.openFile(newValue);
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot proceed to update !", e);
|
||||
} finally {
|
||||
Platform.runLater(() -> splashStage.close());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Launches the main window here
|
||||
splashStage.toFront();
|
||||
splashScreen.uiLoadingPane.setVisible(false);
|
||||
|
||||
final FadeTransition fadeSplash = new FadeTransition(Duration.seconds(1.2), root);
|
||||
fadeSplash.setFromValue(1.0);
|
||||
fadeSplash.setToValue(0.0);
|
||||
fadeSplash.setOnFinished(actionEvent -> {
|
||||
splashStage.hide();
|
||||
root.setOpacity(1.0);
|
||||
try {
|
||||
final Stage mainStage = createDashboardStage();
|
||||
final Preferences welcomePrefs = Preferences.userNodeForPackage(FXInitialDialogPane.class);
|
||||
|
||||
final Stage popup;
|
||||
if (welcomePrefs.getBoolean(FXInitialDialogPane.DISPLAY_RULE_KEY, true)) {
|
||||
popup = Rhomeo.createWelcomePopup();
|
||||
popup.initOwner(mainStage);
|
||||
popup.show();
|
||||
popup.requestFocus();
|
||||
} else {
|
||||
popup = null;
|
||||
}
|
||||
|
||||
// HACK : display of both stages is done now, because if
|
||||
// main stage is displayed before we've ended configuring
|
||||
// popup, some properties (like resizing capabilities)
|
||||
// looks shared.
|
||||
mainStage.show();
|
||||
if (popup != null) {
|
||||
popup.show();
|
||||
popup.requestFocus();
|
||||
}
|
||||
|
||||
} catch (Throwable ex) {
|
||||
try {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Erreur inattendue lors de l'initialisation du panneau principal.", ex);
|
||||
final ExceptionDialog exDialog = GeotkFX.newExceptionDialog("L'application a rencontré une erreur inattendue et doit fermer.", ex);
|
||||
exDialog.setOnHidden((DialogEvent de) -> System.exit(1));
|
||||
exDialog.show();
|
||||
} catch (Throwable e) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot show error dialog to user", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
fadeSplash.play();
|
||||
}
|
||||
}));
|
||||
|
||||
task.setOnCancelled(evt -> {
|
||||
System.exit(0);
|
||||
});
|
||||
|
||||
// Task cancelled, because of an error or a user cancellation action.
|
||||
task.setOnFailed(evt -> {
|
||||
splashScreen.uiProgressLabel.getStyleClass().remove("label");
|
||||
splashScreen.uiProgressLabel.getStyleClass().add("label-error");
|
||||
splashScreen.uiCancel.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the updates confirmation window. User is asked if the update should be downloaded.
|
||||
*
|
||||
* @param newVersion The update version.
|
||||
* @return {@code true} if the user wants to apply the update, {@code false} otherwise.
|
||||
* @throws ExecutionException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private boolean showUpdatesStage(final UpdateInfo newVersion) throws ExecutionException, InterruptedException {
|
||||
// Now that we found that an update is available, we can ask the user if the update should be downloaded.
|
||||
final Task<Boolean> askForUpdate = new Task<Boolean>() {
|
||||
|
||||
@Override
|
||||
protected Boolean call() throws Exception {
|
||||
final StringBuilder builder = new StringBuilder()
|
||||
.append("Version installée : ").append(Rhomeo.getVersion())
|
||||
.append(System.lineSeparator())
|
||||
.append("Version de la mise à jour : ").append(newVersion.getVersion())
|
||||
.append(System.lineSeparator())
|
||||
.append("Date de publication : ").append(newVersion.getDate() == null ? "Inconnue" : newVersion.getDate())
|
||||
.append(System.lineSeparator());
|
||||
|
||||
final String[] releaseNote = newVersion.getReleaseNote();
|
||||
if (releaseNote != null && releaseNote.length > 0) {
|
||||
builder.append("Release-note : ");
|
||||
for (final String str : releaseNote) {
|
||||
builder.append(System.lineSeparator()).append(str);
|
||||
}
|
||||
}
|
||||
|
||||
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
|
||||
builder.toString(), ButtonType.NO, ButtonType.YES);
|
||||
alert.setHeaderText("Une mise à jour est disponible. Télécharger la mise à jour ?");
|
||||
final TextArea infoArea = new TextArea(builder.toString());
|
||||
infoArea.setEditable(false);
|
||||
infoArea.setPrefSize(666, 444);
|
||||
alert.setResizable(true);
|
||||
alert.getDialogPane().setContent(infoArea);
|
||||
final Optional<ButtonType> choice = alert.showAndWait();
|
||||
|
||||
return ButtonType.YES.equals(choice.orElse(ButtonType.NO));
|
||||
}
|
||||
};
|
||||
Platform.runLater(() -> askForUpdate.run());
|
||||
return Boolean.TRUE.equals(askForUpdate.get());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main dashboard stage. Will be opened when the application will be launched.
|
||||
*
|
||||
* @return The main stage, never {@code null}
|
||||
*/
|
||||
private Stage createDashboardStage() {
|
||||
final Stage mainStage = Rhomeo.newStage();
|
||||
mainStage.setOnCloseRequest(event -> System.exit(0));
|
||||
final FXMainPane mainPane = Session.getInstance().getBean(FXMainPane.class);
|
||||
final Scene scene = new Scene(mainPane);
|
||||
scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH);
|
||||
mainStage.setScene(scene);
|
||||
mainStage.initModality(Modality.NONE);
|
||||
mainStage.setMinWidth(800);
|
||||
mainStage.setMinHeight(600);
|
||||
mainStage.setResizable(true);
|
||||
mainStage.setMaximized(true);
|
||||
return mainStage;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private URLAndMD5 checkUpdate() throws Exception {
|
||||
final UpdateInfo newVersion = RhomeoCore.existingUpdate();
|
||||
if (newVersion != null) {
|
||||
final URL updateURL;
|
||||
final String md5;
|
||||
if (PlatformUtil.isWindows()) {
|
||||
updateURL = newVersion.getWin32();
|
||||
md5 = newVersion.getWin32MD5();
|
||||
} else if (PlatformUtil.isMac()) {
|
||||
updateURL = newVersion.getMacOS64();
|
||||
md5 = newVersion.getMacOS64MD5();
|
||||
} else if (PlatformUtil.isLinux()) {
|
||||
if (newVersion.getDeb64() != null && isDeb()) {
|
||||
updateURL = newVersion.getDeb64();
|
||||
md5 = newVersion.getDeb64MD5();
|
||||
} else {
|
||||
updateURL = newVersion.getRpm64();
|
||||
md5 = newVersion.getRpm64MD5();
|
||||
}
|
||||
} else {
|
||||
updateURL = null;
|
||||
md5 = null;
|
||||
}
|
||||
|
||||
if (updateURL != null && showUpdatesStage(newVersion)) {
|
||||
return new URLAndMD5(updateURL, md5);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isDeb() {
|
||||
try {
|
||||
return Runtime.getRuntime().exec("dpkg --version").waitFor() == 0;
|
||||
} catch (InterruptedException|IOException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot detect if the system is a dpkg or RPM based OS.", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial loading task, to follow with a progress bar.
|
||||
* If an update has been detected, returns the download url.
|
||||
*/
|
||||
private final class InitialLoadingTask extends Task {
|
||||
/**
|
||||
* Launched when this task will be called.
|
||||
*
|
||||
* @return Always return {@code null}.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected Object call() throws Exception {
|
||||
final int total = 4;
|
||||
int inc = 0;
|
||||
|
||||
try {
|
||||
// CHECK UPDATES ///////////////////////////////////////////////
|
||||
updateProgress(inc++, total);
|
||||
updateMessage("Vérification des mises à jour...");
|
||||
final URLAndMD5 upURL = checkUpdate();
|
||||
if (upURL != null) {
|
||||
return upURL;
|
||||
}
|
||||
|
||||
// EPSG DATABASE ///////////////////////////////////////////////
|
||||
updateProgress(inc++, total);
|
||||
updateMessage("Création de la base de données de projections cartographiques (EPSG) ...");
|
||||
// try to create it, won't do anything if already exist
|
||||
RhomeoCore.initEpsgDB();
|
||||
|
||||
// SPRING CONTEXT //////////////////////////////////////////////
|
||||
updateProgress(inc++, total);
|
||||
updateMessage("Chargement du contexte de l'application ...");
|
||||
new ClassPathXmlApplicationContext(RhomeoCore.SPRING_CONTEXT_XML);
|
||||
|
||||
// RESTORE APPLICATION STATE ///////////////////////////////////
|
||||
updateProgress(inc++, total);
|
||||
updateMessage("Récupération des données...");
|
||||
Session.getInstance().getBean(StateManager.class).restoreState();
|
||||
|
||||
// END
|
||||
updateProgress(total, total);
|
||||
updateMessage("Chargement terminé");
|
||||
Thread.sleep(400);
|
||||
} catch (Throwable ex) {
|
||||
updateProgress(-1, -1);
|
||||
updateMessage("Erreur inattendue : ".concat(ex.getLocalizedMessage()));
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, ex.getMessage(), ex);
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Application update downloader.
|
||||
* When completed, returns the {@linkplain Path downloaded file}.
|
||||
*/
|
||||
private final class DownloadUpdateTask extends Task<Path> {
|
||||
private final URLAndMD5 url;
|
||||
|
||||
public DownloadUpdateTask(final URLAndMD5 url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the new update.
|
||||
*
|
||||
* @return The {@linkplain Path downloaded update}
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
protected Path call() throws Exception {
|
||||
updateMessage("Téléchargement de la mise à jour en cours ...");
|
||||
|
||||
String completeURL = url.url.toExternalForm();
|
||||
final Matcher matcher = RhomeoCore.EXT_PATTERN.matcher(completeURL);
|
||||
|
||||
final Path tmpFile = Files.createTempFile("tmpInstall", matcher.find()? matcher.group() : ".tmp");
|
||||
|
||||
// Now download the update in the appropriate file
|
||||
final URLConnection bundleConnec = new NetPreferences().openConnection(url.url);
|
||||
bundleConnec.setConnectTimeout(RhomeoCore.CONNECTION_TIMEOUT);
|
||||
bundleConnec.setReadTimeout(RhomeoCore.READ_TIMEOUT);
|
||||
final long totalLength = bundleConnec.getContentLengthLong();
|
||||
long downloaded = 0;
|
||||
final String totalReadableSize = RhomeoCore.toReadableSize(totalLength);
|
||||
final MessageDigest digest = MessageDigest.getInstance("md5");
|
||||
try (final InputStream input = bundleConnec.getInputStream();
|
||||
final OutputStream output = Files.newOutputStream(tmpFile)) {
|
||||
int readBytes;
|
||||
final byte[] buffer = new byte[4096];
|
||||
while ((readBytes = input.read(buffer)) >= 0) {
|
||||
output.write(buffer, 0, readBytes);
|
||||
digest.update(buffer, 0, readBytes);
|
||||
downloaded += readBytes;
|
||||
updateProgress(downloaded, totalLength);
|
||||
updateMessage("Téléchargement "+ RhomeoCore.toReadableSize(downloaded) + " / " + totalReadableSize);
|
||||
}
|
||||
output.flush();
|
||||
}
|
||||
|
||||
// Verify that the content was fully downloaded
|
||||
if (downloaded != totalLength) {
|
||||
throw new IOException("La mise à jour n'a pas été entièrement téléchargée");
|
||||
}
|
||||
|
||||
// MD5 checksum
|
||||
if (url.md5Base64 != null && !url.md5Base64.isEmpty()) {
|
||||
byte[] computed = digest.digest();
|
||||
if (!url.md5Base64.equals(new BigInteger(1, computed).toString(16))) {
|
||||
throw new IOException("Downloaded file checksum is invalid !");
|
||||
}
|
||||
}
|
||||
|
||||
updateMessage("Mise à jour téléchargée. Veuillez cliquer sur le bouton et l'installer.");
|
||||
return tmpFile;
|
||||
}
|
||||
}
|
||||
|
||||
private static class URLAndMD5 {
|
||||
final URL url;
|
||||
final String md5Base64;
|
||||
|
||||
public URLAndMD5(final URL packageURL, final String base64MD5) {
|
||||
this.url = packageURL;
|
||||
this.md5Base64 = base64MD5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,758 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo;
|
||||
|
||||
import com.sun.javafx.PlatformUtil;
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.core.CSVEncoder;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.RhomeoRuntimeException;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.util.ExportUtils;
|
||||
import fr.cenra.rhomeo.fx.FXInitialDialogPane;
|
||||
import java.awt.Color;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Font;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.Preferences;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TablePosition;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.StringConverter;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.font.FontAwesomeIcons;
|
||||
import org.geotoolkit.font.IconBuilder;
|
||||
import org.geotoolkit.gui.javafx.util.ComboBoxCompletion;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.geotoolkit.nio.ZipUtilities;
|
||||
|
||||
/**
|
||||
* Regroups methods used by the application for handling JavaFX features.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
* @see RhomeoCore
|
||||
*/
|
||||
public final class Rhomeo extends RhomeoCore {
|
||||
/**
|
||||
* Application icon.
|
||||
*/
|
||||
public static final Image ICON = new Image(Rhomeo.class.getResource("/fr/cenra/rhomeo/images/logo.png").toString(), 16, 16, true, false);
|
||||
|
||||
public static final Image ICON_NOT_PUBLISHED = new Image(Rhomeo.class.getResource("/fr/cenra/rhomeo/images/not_published.png").toString(),
|
||||
16, 16, true, true);
|
||||
|
||||
public static final Color GRAY_SITE = Color.decode("#898989");
|
||||
public static final Color GREEN_SITE = Color.decode("#9CB35B");
|
||||
public static final Color BLUE_SITE = Color.decode("#5B8AA4");
|
||||
public static final Color RED_SITE = Color.decode("#D85F70");
|
||||
public static final Color ORANGE_SITE = Color.decode("#FFA500");
|
||||
|
||||
public static final Font PROGRESS_MONITOR_ICON_FONT = IconBuilder.FONT.deriveFont(16f);
|
||||
|
||||
public static final Image ICON_BARS_WHITE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_BARS,24, Color.WHITE),null);
|
||||
public static final Image ICON_CARET_UP = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CARET_UP,16, GRAY_SITE),null);
|
||||
public static final Image ICON_CARET_DOWN = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CARET_DOWN,16, GRAY_SITE),null);
|
||||
public static final Image ICON_CHECK_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CHECK,16, GRAY_SITE),null);
|
||||
public static final Image ICON_CHECK_GREEN = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CHECK,16, GREEN_SITE),null);
|
||||
public static final Image ICON_CIRCLE_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CIRCLE,8, GRAY_SITE),null);
|
||||
public static final Image ICON_COG = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_COG,24, GRAY_SITE),null);
|
||||
public static final Image ICON_DOWNLOAD_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_DOWNLOAD,24, BLUE_SITE),null);
|
||||
public static final Image ICON_EXCHANGE_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_EXCHANGE,16, GRAY_SITE),null);
|
||||
public static final Image ICON_EYE_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_EYE,16, GRAY_SITE),null);
|
||||
public static final Image ICON_FILE_PDF = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_FILE_PDF_O,24, RED_SITE),null);
|
||||
public static final Image ICON_FILE_TEXT = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_FILE_TEXT_O,24, BLUE_SITE),null);
|
||||
public static final Image ICON_INFO_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_INFO_CIRCLE,24, BLUE_SITE),null);
|
||||
public static final Image ICON_INFO_GREY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_INFO_CIRCLE,24, GRAY_SITE),null);
|
||||
public static final Image ICON_LONG_ARROW_RIGHT = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_LONG_ARROW_RIGHT,48, GRAY_SITE),null);
|
||||
public static final Image ICON_PENCIL_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_PENCIL,24, BLUE_SITE),null);
|
||||
public static final Image ICON_PENCIL_WHITE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_PENCIL,24, Color.WHITE),null);
|
||||
public static final Image ICON_STOP_RED = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_STOP,16, RED_SITE),null);
|
||||
public static final Image ICON_TIMES_RED = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_TIMES,16, RED_SITE),null);
|
||||
public static final Image ICON_TRASH_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_TRASH_O,24, BLUE_SITE),null);
|
||||
public static final Image ICON_WARNING_ORANGE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_WARNING_ALIAS,24, ORANGE_SITE),null);
|
||||
|
||||
public static final String CSS_FONT_AWESOME = "rhomeo-font-awesome";
|
||||
public static final String CSS_ERROR_FIELD = "error-field";
|
||||
public static final String CSS_WARNING_FIELD = "warning-field";
|
||||
public static final String CSS_INDICATOR_LABEL = "indicator-label";
|
||||
public static final String CSS_CURRENT_STEP = "current-step";
|
||||
public static final String CSS_PROTOCOL_BUTTON = "protocol-button";
|
||||
public static final String CSS_PROTOCOL_POPUP_BODY = "protocol-popup-body";
|
||||
|
||||
/**
|
||||
* Main theme path
|
||||
*/
|
||||
public static final String CSS_THEME_PATH = "/fr/cenra/rhomeo/theme.css";
|
||||
|
||||
private static final ChangeListener<String> SPINNER_LISTENER = Rhomeo::spinnerTextChanged;
|
||||
/**
|
||||
* Generates a stage with {@linkplain RhomeoCore#APPLICATION_TITLE application title} and
|
||||
* {@linkplain #ICON application icon}.
|
||||
*
|
||||
* @return a new Rhomeo stage, never {@code null}
|
||||
*/
|
||||
public static Stage newStage() {
|
||||
final Stage rhomeoStage = new Stage();
|
||||
rhomeoStage.getIcons().add(ICON);
|
||||
final String version = RhomeoCore.getVersion();
|
||||
final String title = (version == null || version.isEmpty()) ? APPLICATION_TITLE : APPLICATION_TITLE +" ("+ version +")";
|
||||
rhomeoStage.setTitle(title);
|
||||
return rhomeoStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the initial dialog. Will be shown when launching the application.
|
||||
*
|
||||
* @return The dialog stage, never {@code null}
|
||||
*/
|
||||
public static Stage createWelcomePopup() {
|
||||
final Stage stage = Rhomeo.newStage();
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.setResizable(true);
|
||||
stage.setTitle("Accueil");
|
||||
final FXInitialDialogPane dialog = new FXInitialDialogPane();
|
||||
final Scene scene = new Scene(dialog);
|
||||
scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH);
|
||||
stage.setScene(scene);
|
||||
stage.sizeToScene();
|
||||
return stage;
|
||||
}
|
||||
|
||||
public static void loadFXML(Parent candidate) {
|
||||
final Class modelClass = null;
|
||||
loadFXML(candidate, modelClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load FXML document matching input controller. If a model class is given,
|
||||
* we'll try to load a bundle for text internationalization.
|
||||
* @param candidate The controller object to get FXMl for.
|
||||
* @param bundleClass A class which will be used for bundle loading.
|
||||
*/
|
||||
public static void loadFXML(final Parent candidate, final Class bundleClass) {
|
||||
ResourceBundle bundle = null;
|
||||
if (bundleClass != null) {
|
||||
try{
|
||||
bundle = ResourceBundle.getBundle(bundleClass.getName(), Locale.FRENCH,
|
||||
Thread.currentThread().getContextClassLoader());
|
||||
}catch(MissingResourceException ex){
|
||||
LOGGER.log(Level.INFO, "Missing bundle for : {0}", bundleClass.getName());
|
||||
}
|
||||
}
|
||||
loadFXML(candidate, bundle);
|
||||
}
|
||||
|
||||
public static void loadFXML(final Parent candidate, final ResourceBundle bundle) {
|
||||
loadFXML(candidate, candidate.getClass(), bundle);
|
||||
}
|
||||
|
||||
public static void loadFXML(final Parent candidate, final Class fxmlClass, final ResourceBundle bundle) {
|
||||
ArgumentChecks.ensureNonNull("JavaFX controller object", candidate);
|
||||
final String fxmlpath = "/"+fxmlClass.getName().replace('.', '/')+".fxml";
|
||||
final URL resource = fxmlClass.getResource(fxmlpath);
|
||||
if (resource == null) {
|
||||
throw new RuntimeException("No FXML document can be found for path : "+fxmlpath);
|
||||
}
|
||||
final FXMLLoader loader = new FXMLLoader(resource);
|
||||
loader.setController(candidate);
|
||||
loader.setRoot(candidate);
|
||||
//in special environement like osgi or other, we must use the proper class loaders
|
||||
//not necessarly the one which loaded the FXMLLoader class
|
||||
loader.setClassLoader(fxmlClass.getClassLoader());
|
||||
|
||||
if(bundle!=null) loader.setResources(bundle);
|
||||
|
||||
fxRunAndWait(() -> {
|
||||
try {
|
||||
loader.load();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException(ex.getMessage(), ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run given task in FX application thread (immediately if we're already in it),
|
||||
* and wait for its result before returning.
|
||||
*
|
||||
* @param <T> Return TYPE of the input task.
|
||||
* @param toRun Task to run in JavaFX thread.
|
||||
* @return Result of the input task.
|
||||
*/
|
||||
public static <T> T fxRunAndWait(final Callable<T> toRun) {
|
||||
return fxRunAndWait(new TaskManager.MockTask<>(toRun));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param toRun The task to run.
|
||||
*/
|
||||
public static void fxRunAndWait(final Runnable toRun) {
|
||||
fxRunAndWait(new TaskManager.MockTask(toRun));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run given task in FX application thread (immediately if we're already in it),
|
||||
* and wait for its result before returning.
|
||||
*
|
||||
* @param <T> Return TYPE of the input task.
|
||||
* @param toRun Task to run in JavaFX thread.
|
||||
* @return Result of the input task.
|
||||
*/
|
||||
public static <T> T fxRunAndWait(final Task<T> toRun) {
|
||||
return fxRun(true, toRun);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run given task in FX application thread (immediately if we're already in it).
|
||||
* According to input boolean, we will return immediately or wait for the task to
|
||||
* be over.
|
||||
* @param wait True if we must wait for the task to end before returning, false
|
||||
* to return immediately after submission.
|
||||
* @param toRun The task to run into JavaFX application thread.
|
||||
*/
|
||||
public static void fxRun(final boolean wait, final Runnable toRun) {
|
||||
fxRun(wait, new TaskManager.MockTask(toRun));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run given task in FX application thread (immediately if we're already in it).
|
||||
* According to input boolean, we will return immediately or wait for the task to
|
||||
* be over.
|
||||
* @param <T> Return TYPE of input task.
|
||||
* @param wait True if we must wait for the task to end before returning, false
|
||||
* to return immediately after submission.
|
||||
* @return The task return value if we must wait, or we're in platform thread. Otherwise null.
|
||||
* @param toRun The task to run into JavaFX application thread.
|
||||
*/
|
||||
public static <T> T fxRun(final boolean wait, final Callable<T> toRun) {
|
||||
return fxRun(wait, new TaskManager.MockTask<>(toRun));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run given task in FX application thread (immediately if we're already in it).
|
||||
* According to input boolean, we will return immediately or wait for the task to
|
||||
* be over.
|
||||
* @param <T> Return TYPE of input task.
|
||||
* @param wait True if we must wait for the task to end before returning, false
|
||||
* to return immediately after submission.
|
||||
* @return The task return value if we must wait, or we're in platform thread. Otherwise null.
|
||||
* @param toRun The task to run into JavaFX application thread.
|
||||
*/
|
||||
public static <T> T fxRun(final boolean wait, final Task<T> toRun) {
|
||||
if (Platform.isFxApplicationThread())
|
||||
toRun.run();
|
||||
else
|
||||
Platform.runLater(toRun);
|
||||
|
||||
if (wait || Platform.isFxApplicationThread()) {
|
||||
try {
|
||||
return toRun.get();
|
||||
} catch (RuntimeException ex) {
|
||||
throw ex;
|
||||
} catch (ExecutionException ex) {
|
||||
if (ex.getCause() instanceof RuntimeException) {
|
||||
throw (RuntimeException) ex.getCause();
|
||||
} else {
|
||||
throw new RhomeoRuntimeException(ex.getCause());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RhomeoRuntimeException(e);
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open given file on system.
|
||||
* @param toOpen The file open on underlying system.
|
||||
* @return True if we succeeded opening file on system, false otherwise.
|
||||
*/
|
||||
public static Task<Boolean> openFile(final Path toOpen) {
|
||||
return openFile(toOpen.toAbsolutePath().toFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open given file on system.
|
||||
* @param toOpen The file open on underlying system.
|
||||
* @return True if we succeeded opening file on system, false otherwise.
|
||||
*/
|
||||
public static Task<Boolean> openFile(final File toOpen) {
|
||||
return TaskManager.INSTANCE.submit("Ouverture d'un fichier", () -> {
|
||||
try {
|
||||
final Desktop desktop = Desktop.getDesktop();
|
||||
if (desktop.isSupported(Desktop.Action.OPEN)) {
|
||||
desktop.open(toOpen);
|
||||
} else if (desktop.isSupported(Desktop.Action.EDIT)) {
|
||||
desktop.edit(toOpen);
|
||||
} else {
|
||||
throw new IOException("Impossible de communiquer avec l'OS.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
/*
|
||||
* HACK : on Windows, it seems that some file associations cannot be found
|
||||
* using above method (Ex : .odt), so we try running following command as
|
||||
* a fallback.
|
||||
*/
|
||||
if (PlatformUtil.isWindows()) {
|
||||
Runtime.getRuntime().exec(new String[]{"cmd /c start", toOpen.toURI().toString()});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<Boolean> browse(final URL href) {
|
||||
return TaskManager.INSTANCE.submit("Ouverture d'un lien", () -> {
|
||||
final Desktop desktop = Desktop.getDesktop();
|
||||
if (desktop.isSupported(Desktop.Action.BROWSE))
|
||||
desktop.browse(href.toURI());
|
||||
else
|
||||
throw new IOException("Impossible de communiquer avec l'OS.");
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<Boolean> mail(final String mailAdress) {
|
||||
return TaskManager.INSTANCE.submit("Ouverture d'un lien e-mail", () -> {
|
||||
final Desktop desktop = Desktop.getDesktop();
|
||||
if (desktop.isSupported(Desktop.Action.MAIL))
|
||||
desktop.mail(new URI(mailAdress.startsWith("mailto")? mailAdress : "mailto:".concat(mailAdress)));
|
||||
else
|
||||
throw new IOException("Impossible de communiquer avec l'OS.");
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an auto completable combo box, without selecting one.
|
||||
*
|
||||
* @param <I>
|
||||
* @param comboBox
|
||||
* @param items
|
||||
* @see #initComboBox(ComboBox, ObservableList, Object)
|
||||
*/
|
||||
public static <I> void initComboBox(final ComboBox<I> comboBox, final ObservableList<I> items) {
|
||||
initComboBox(comboBox, items, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add items in the combo box and make it auto completable.
|
||||
*
|
||||
* @param comboBox A new combo box, not {@code null}.
|
||||
* @param items List of items to add.
|
||||
* @param itemSelected If not null, select this item in the list.
|
||||
* @param <I> item class
|
||||
*/
|
||||
public static <I> void initComboBox(final ComboBox<I> comboBox, final ObservableList<I> items, final I itemSelected) {
|
||||
comboBox.setItems(items);
|
||||
try {
|
||||
comboBox.setEditable(true);
|
||||
} catch (RuntimeException re) {
|
||||
// A runtime exception is launched if the editable property is already bound
|
||||
// Just log and let the property bound and handled by something else.
|
||||
Rhomeo.LOGGER.log(Level.FINE, re.getLocalizedMessage(), re);
|
||||
}
|
||||
if (itemSelected != null) {
|
||||
comboBox.getSelectionModel().select(itemSelected);
|
||||
}
|
||||
ComboBoxCompletion.autocomplete(comboBox);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates an alert of the given type with the given text and just an OK button.
|
||||
*
|
||||
* @param type {@linkplain Alert.AlertType alert type}.
|
||||
* @param text Text to display.
|
||||
*/
|
||||
public static void showAlert(final Alert.AlertType type, final String text) {
|
||||
final Alert alert = new Alert(type, text, ButtonType.OK);
|
||||
alert.setResizable(true);
|
||||
alert.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous path stored for reopening a file chooser in it.
|
||||
*
|
||||
* @param packageClass Class in a specific java package on which we want to attach
|
||||
* the path preference
|
||||
* @return the previous path stored with the last opening of the file chooser.
|
||||
*/
|
||||
public static File getPreviousPath(final Class packageClass) {
|
||||
final Preferences prefs = Preferences.userNodeForPackage(packageClass);
|
||||
final String str = prefs.get("path", null);
|
||||
if (str != null) {
|
||||
final File file = new File(str);
|
||||
if (file.isDirectory()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path of the previously selected file into the file chooser.
|
||||
*
|
||||
* @param packageClass Class in a specific java package on which we want to attach
|
||||
* the path preference
|
||||
* @param path the path to store. Should point on a folder.
|
||||
*/
|
||||
public static void setPreviousPath(final Class packageClass, final File path) {
|
||||
final Preferences prefs = Preferences.userNodeForPackage(packageClass);
|
||||
prefs.put("path", path.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Open modal popup asking for a file on the local path.
|
||||
*
|
||||
* @param initialFileName Initial file name to use.
|
||||
* @param owner The owner of this popup.
|
||||
* @param filters Filters to use. Possibly empty or null. The first item will be used as the default one.
|
||||
* @return The file wished, or {@code null} if the user cancelled the popup.
|
||||
*/
|
||||
public static File askOutputFilePopup(final String initialFileName, final Node owner, final FileChooser.ExtensionFilter... filters) {
|
||||
final FileChooser fileChooser = new FileChooser();
|
||||
|
||||
final File previousPath = getPreviousPath(ExportUtils.class);
|
||||
if(previousPath!=null && previousPath.exists()){
|
||||
fileChooser.setInitialDirectory(previousPath);
|
||||
}
|
||||
|
||||
// Set some usefull filters
|
||||
if (filters != null && filters.length > 0) {
|
||||
fileChooser.getExtensionFilters().addAll(filters);
|
||||
fileChooser.setSelectedExtensionFilter(filters[0]);
|
||||
}
|
||||
|
||||
if (initialFileName != null && !initialFileName.isEmpty()) {
|
||||
fileChooser.setInitialFileName(initialFileName);
|
||||
}
|
||||
|
||||
final File outputFile = fileChooser.showSaveDialog(owner.getScene().getWindow());
|
||||
|
||||
if(outputFile==null) return null;
|
||||
setPreviousPath(ExportUtils.class, outputFile.getParentFile());
|
||||
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find a combobox under given node scene tree.
|
||||
* @param source The node to search into.
|
||||
* @return The first found combobox, or nothing.
|
||||
*/
|
||||
public static Optional<ComboBox> findComboBox(final Node source) {
|
||||
if (source instanceof ComboBox) {
|
||||
return Optional.of((ComboBox)source);
|
||||
} else if (source instanceof Parent) {
|
||||
final ObservableList<Node> nodes = ((Parent)source).getChildrenUnmodifiable();
|
||||
Optional<ComboBox> opt;
|
||||
for (final Node n : nodes) {
|
||||
opt = findComboBox(n);
|
||||
if (opt.isPresent())
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static void prepareSpinners(final Node source) {
|
||||
if (source instanceof Spinner) {
|
||||
final Spinner spinner = (Spinner) source;
|
||||
spinner.getEditor().textProperty().removeListener(SPINNER_LISTENER);
|
||||
spinner.getEditor().textProperty().addListener(SPINNER_LISTENER);
|
||||
final SpinnerValueFactory valueFactory = spinner.getValueFactory();
|
||||
if (valueFactory instanceof SpinnerValueFactory.DoubleSpinnerValueFactory) {
|
||||
final SpinnerValueFactory.DoubleSpinnerValueFactory dvf = (SpinnerValueFactory.DoubleSpinnerValueFactory) valueFactory;
|
||||
dvf.setAmountToStepBy(0.01);
|
||||
dvf.setConverter(new SpinnerConverter(spinner));
|
||||
// HACK : To force display refresh immediately, we introduce temporary value change.
|
||||
final Double oldValue = dvf.getValue();
|
||||
if (oldValue == null || oldValue.isNaN() || oldValue.isInfinite())
|
||||
dvf.setValue(0.0);
|
||||
else dvf.setValue(oldValue + 1);
|
||||
dvf.setValue(oldValue);
|
||||
}
|
||||
|
||||
} else if (source instanceof Parent) {
|
||||
final ObservableList<Node> nodes = ((Parent)source).getChildrenUnmodifiable();
|
||||
for (final Node n : nodes) {
|
||||
prepareSpinners(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void spinnerTextChanged(final ObservableValue<? extends String> obs, final String oldValue, final String newValue) {
|
||||
if (obs instanceof ReadOnlyProperty) {
|
||||
final Object bean = ((ReadOnlyProperty)obs).getBean();
|
||||
if (bean instanceof Node) {
|
||||
Parent parent = ((Node) bean).getParent();
|
||||
if (parent instanceof Spinner) {
|
||||
final Spinner spinner = (Spinner) parent;
|
||||
try {
|
||||
spinner.getValueFactory().setValue(spinner.getValueFactory().getConverter().fromString(newValue));
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.FINE, "Cannot convert seized text to numeric value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check column and row indices to determine if we must edit next column or
|
||||
* next row. If next cell is not editable, we test the next one, etc.
|
||||
*
|
||||
* @param editingCell Cell currently edited. If null, this method does nothing.
|
||||
*/
|
||||
public static void editNextCell(final TablePosition editingCell) {
|
||||
if (editingCell == null)
|
||||
return;
|
||||
final TableView<Object> tableView = editingCell.getTableView();
|
||||
TableColumn targetCol;
|
||||
int rowIndex = editingCell.getRow();
|
||||
int colIndex = editingCell.getColumn();
|
||||
final int maxRow = tableView.getItems().size() - 1;
|
||||
final int maxCol = tableView.getColumns().size() - 1;
|
||||
do {
|
||||
if (colIndex >= maxCol) {
|
||||
rowIndex++;
|
||||
colIndex = 0;
|
||||
} else {
|
||||
colIndex++;
|
||||
}
|
||||
|
||||
if (rowIndex < 0 || rowIndex > maxRow) {
|
||||
targetCol = null;
|
||||
} else {
|
||||
targetCol = tableView.getColumns().get(colIndex);
|
||||
}
|
||||
|
||||
} while (targetCol != null && !targetCol.isEditable());
|
||||
|
||||
tableView.edit(rowIndex, targetCol);
|
||||
}
|
||||
|
||||
public static Button createProtocolDocumentButton(final Protocol protocol) {
|
||||
final Button button = new Button("Fiche protocole "+ protocol.getName());
|
||||
button.setGraphic(new ImageView(ICON_FILE_PDF));
|
||||
button.getStyleClass().add("transparent");
|
||||
button.setOnAction(event -> {
|
||||
final String fileName = protocol.getName() + ".pdf";
|
||||
Session.getInstance().getBean(DocManager.class).openDocument(protocol.getClass().getResource(fileName));
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
public static Button createModelButton(final Protocol protocol) {
|
||||
final Button button = new Button("Format protocole "+ protocol.getRemarks() +" ("+ protocol.getName() +")");
|
||||
button.setGraphic(new ImageView(ICON_FILE_TEXT));
|
||||
button.getStyleClass().add("transparent");
|
||||
button.setOnAction(event -> generateModelZip(protocol, button));
|
||||
return button;
|
||||
}
|
||||
|
||||
public static void generateModelZip(final Protocol protocol, final Node owner) {
|
||||
final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
|
||||
final File outputFile = Rhomeo.askOutputFilePopup(protocol.getName() +".zip", owner, zipFilter);
|
||||
if (outputFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Path parentPath = Paths.get(outputFile.getParentFile().getAbsolutePath());
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final Path folderPath = parentPath.resolve(uuid.toString());
|
||||
|
||||
final Class<? extends Statement> statementClass = protocol.getDataType();
|
||||
try {
|
||||
Files.createDirectory(folderPath);
|
||||
|
||||
final Path formatPath = folderPath.resolve("format.csv");
|
||||
final CSVEncoder<? extends Statement> encoder = new CSVEncoder<>(formatPath, statementClass);
|
||||
encoder.encode(new ArrayList<>(), true);
|
||||
|
||||
final Path readmePath = createReadMeFile(statementClass, folderPath);
|
||||
ZipUtilities.zip(outputFile.toPath(), ZipOutputStream.DEFLATED, 9, null, formatPath, readmePath);
|
||||
|
||||
Files.deleteIfExists(formatPath);
|
||||
Files.deleteIfExists(readmePath);
|
||||
Files.deleteIfExists(folderPath);
|
||||
} catch (IOException | IntrospectionException ex) {
|
||||
Rhomeo.LOGGER.log(Level.INFO, "Cannot generate format file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Path createReadMeFile(final Class<? extends Statement> statementClass, final Path outputFolderPath) throws IOException, IntrospectionException {
|
||||
final boolean bundleAvailable = InternationalResource.class.isAssignableFrom(statementClass);
|
||||
|
||||
final Path formatPath = outputFolderPath.resolve("readme.txt");
|
||||
Files.createFile(formatPath);
|
||||
String description = null;
|
||||
try (final BufferedWriter bw = Files.newBufferedWriter(formatPath, StandardOpenOption.APPEND)) {
|
||||
final BeanInfo beanInfo = Introspector.getBeanInfo(statementClass, Object.class);
|
||||
for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) {
|
||||
if (bundleAvailable) {
|
||||
try {
|
||||
description = InternationalResource.getResourceString(statementClass, desc.getName(), "tooltip");
|
||||
} catch (MissingResourceException ex) {
|
||||
description = null;
|
||||
}
|
||||
if (description == null || description.isEmpty()) {
|
||||
try {
|
||||
description = InternationalResource.getResourceString(statementClass, desc.getName(), "label");
|
||||
} catch (MissingResourceException ex) {
|
||||
description = null;
|
||||
}
|
||||
}
|
||||
bw.write(desc.getName());
|
||||
bw.write("=");
|
||||
if (description == null || description.isEmpty()) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "No resources for key " + desc.getName() + ".label or " + desc.getName() + ".tooltip in bundle for class "
|
||||
+ statementClass.getCanonicalName());
|
||||
} else {
|
||||
bw.write(description);
|
||||
}
|
||||
bw.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if additional content is available for the format readme.
|
||||
try (final InputStream in = statementClass.getResourceAsStream("readme.additional");
|
||||
final InputStreamReader tmpReader = new InputStreamReader(in, StandardCharsets.UTF_8);
|
||||
final BufferedReader reader = new BufferedReader(tmpReader)) {
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
bw.newLine();
|
||||
bw.write(line);
|
||||
}
|
||||
|
||||
} catch (NullPointerException e) {
|
||||
LOGGER.log(Level.FINE, "No additional text defined for data description.", e);
|
||||
}
|
||||
}
|
||||
return formatPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* A special string converter for Spinners. The aim is to avoid changes in
|
||||
* spinner's editor when value is updated, but matches editor text. Because
|
||||
* without any check, spinner will force editor text, which could bother
|
||||
* user seizing.
|
||||
*/
|
||||
private static class SpinnerConverter extends StringConverter<Double> {
|
||||
|
||||
private final Spinner target;
|
||||
|
||||
public SpinnerConverter(Spinner target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(Double object) {
|
||||
final String text = target.getEditor().getText();
|
||||
Object fromString = DOUBLE_CONVERTER.fromString(text);
|
||||
if ((fromString instanceof Number) && (target.getValue() instanceof Number) && ((Number)fromString).doubleValue() == ((Number)target.getValue()).doubleValue())
|
||||
return text;
|
||||
else return DOUBLE_CONVERTER.toString(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromString(String string) {
|
||||
return (Double) DOUBLE_CONVERTER.fromString(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.DashboardResultItem;
|
||||
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.data.DashboardResultsManager;
|
||||
import fr.cenra.rhomeo.core.result.ResultStorage;
|
||||
import fr.cenra.rhomeo.core.util.ExportUtils;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TablePosition;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
/**
|
||||
* Dashboard panel displayed initially when opening the application.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXDashboardPane extends BorderPane implements InternationalResource {
|
||||
/**
|
||||
* Body panel containing the data table and some action buttons.
|
||||
*/
|
||||
@FXML private BorderPane uiDashboardBodyPane;
|
||||
|
||||
@FXML
|
||||
private Button uiPublish;
|
||||
|
||||
@FXML
|
||||
private Button uiExport;
|
||||
|
||||
@Autowired
|
||||
private FXDashboardMenuPane menuPane;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private DashboardResultsManager resultsManager;
|
||||
|
||||
@Autowired
|
||||
private ResultStorage resultStorage;
|
||||
|
||||
FXDashboardResultsTable resultTable;
|
||||
|
||||
public FXDashboardPane() {
|
||||
Rhomeo.loadFXML(this, FXDashboardPane.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes JavaFX components after spring components are done loading.
|
||||
*/
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
setTop(menuPane);
|
||||
|
||||
resultTable = session.createPrototype(FXDashboardResultsTable.class, true);
|
||||
uiDashboardBodyPane.setCenter(resultTable);
|
||||
resultTable.siteProperty().bind(menuPane.siteProperty());
|
||||
|
||||
uiDashboardBodyPane.visibleProperty().bind(menuPane.siteProperty().isNotNull().and(menuPane.newSiteProperty().not()));
|
||||
uiDashboardBodyPane.managedProperty().bind(uiDashboardBodyPane.visibleProperty());
|
||||
|
||||
final ObservableList<TablePosition> selectedCells = resultTable.getSelectionModel().getSelectedCells();
|
||||
uiPublish.disableProperty().bind(Bindings.createBooleanBinding(() -> {
|
||||
if (selectedCells.size() != 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
TablePosition<IndicatorValuesByYearItem, ?> pos = selectedCells.get(0);
|
||||
if (pos.getTableColumn() instanceof IndicatorSiteColumn) {
|
||||
final String indicName = ((IndicatorSiteColumn)pos.getTableColumn()).getIndicator().getName();
|
||||
final Boolean published = pos.getTableView().getItems().get(pos.getRow()).isPublished(indicName);
|
||||
return published == null? true : published;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, selectedCells));
|
||||
|
||||
uiExport.disableProperty().bind(Bindings.createBooleanBinding(() -> {
|
||||
return (selectedCells.size() != 1 || !(selectedCells.get(0).getTableColumn() instanceof IndicatorSiteColumn));
|
||||
}, selectedCells));
|
||||
}
|
||||
|
||||
public ObjectProperty<Site> siteProperty() {
|
||||
return menuPane.siteProperty();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void publishData(final ActionEvent evt) {
|
||||
final ObservableList<TablePosition> selectedCells = resultTable.getSelectionModel().getSelectedCells();
|
||||
if (resultTable == null || selectedCells.size() != 1)
|
||||
return;
|
||||
|
||||
final DashboardResultItem item = getSelectedItem();
|
||||
final IndicatorValuesByYearItem rowItem = resultTable.getItems().get(selectedCells.get(0).getRow());
|
||||
if (item == null || item.isPublished())
|
||||
return;
|
||||
|
||||
TaskManager.INSTANCE.submit("Publication ".concat(item.getIndicator()), () -> {
|
||||
final FTPClient ftp = session.connectResultFTP();
|
||||
try {
|
||||
resultsManager.publishResults(menuPane.siteProperty().get(), Collections.singletonList(item), ftp);
|
||||
} finally {
|
||||
ftp.disconnect();
|
||||
}
|
||||
|
||||
return true;
|
||||
}).setOnSucceeded((evt2) -> Platform.runLater(() -> {
|
||||
// Update publication state.
|
||||
rowItem.getValueForIndicator(item.getIndicator()).setPublished(true);
|
||||
resultTable.getItems().set(resultTable.getItems().indexOf(rowItem), rowItem);
|
||||
}));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void exportData() {
|
||||
if (resultTable == null || resultTable.getSelectionModel().getSelectedCells().size() != 1)
|
||||
return;
|
||||
|
||||
final DashboardResultItem item = getSelectedItem();
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
final Indicator indic = ((IndicatorSiteColumn) resultTable.getSelectionModel().getSelectedCells().get(0).getTableColumn()).getIndicator();
|
||||
final String initialFileName = new StringBuilder(item.getSite()).append('_').append(item.getIndicator()).append('_').append(item.getYear()).append(".zip").toString();
|
||||
final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
|
||||
final File outputFile = Rhomeo.askOutputFilePopup(initialFileName, this, zipFilter);
|
||||
if (outputFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TaskManager.INSTANCE.submit("Export de résultats", () -> {
|
||||
try (final FileSystem fs = RhomeoCore.toZipFileSystem(outputFile.toPath())) {
|
||||
final Path root = fs.getRootDirectories().iterator().next();
|
||||
Optional<Path> opt = resultStorage.getMainResults(item);
|
||||
if (opt.isPresent())
|
||||
Files.copy(opt.get(), root.resolve(opt.get().getFileName().toString()));
|
||||
opt = resultStorage.getAdditionalResults(item);
|
||||
if (opt.isPresent())
|
||||
Files.copy(opt.get(), root.resolve(opt.get().getFileName().toString()));
|
||||
|
||||
// Read metadata from file, and recover memory objects needed for
|
||||
// re-writing as text.
|
||||
final Map[] readMds = resultStorage.readMetadata(item)
|
||||
.map(ctx -> ctx.toDataContext(session))
|
||||
.map(ctx -> new Map[]{session.transform(ctx.getReferences()), ctx.getUserData()})
|
||||
.orElse(new Map[]{Collections.EMPTY_MAP, Collections.EMPTY_MAP});
|
||||
|
||||
ExportUtils.writeMetadataText(
|
||||
root.resolve("metadata.txt"),
|
||||
menuPane.siteProperty().get(),
|
||||
indic.getProtocol(),
|
||||
readMds[0],
|
||||
Collections.singleton(indic),
|
||||
readMds[1],
|
||||
Collections.EMPTY_SET
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return A dashboard item reflecting data pointed by currently selected cell. Can be null.
|
||||
*/
|
||||
private DashboardResultItem getSelectedItem() {
|
||||
if (resultTable == null) {
|
||||
return null;
|
||||
}
|
||||
final ObservableList<TablePosition> selectedCells = resultTable.getSelectionModel().getSelectedCells();
|
||||
if (resultTable.getSelectionModel().getSelectedCells().size() != 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TablePosition pos = selectedCells.get(0);
|
||||
if (!(pos.getTableColumn() instanceof IndicatorSiteColumn)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Site site = menuPane.siteProperty().get();
|
||||
final String siteName = site.getName();
|
||||
final IndicatorSiteColumn col = (IndicatorSiteColumn) pos.getTableColumn();
|
||||
IndicatorValuesByYearItem tmpItem = resultTable.getItems().get(pos.getRow());
|
||||
final Indicator indic = col.getIndicator();
|
||||
final String indicName = indic.getName();
|
||||
return new DashboardResultItem(
|
||||
tmpItem.getYear(), siteName, indicName, col.getCellData(tmpItem), tmpItem.isPublished(indicName)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.data.DashboardResultsManager;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.image.ImageView;
|
||||
import org.geotoolkit.gui.javafx.util.FXTableCell;
|
||||
import org.geotoolkit.gui.javafx.util.FXTableView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.TableCell;
|
||||
import fr.cenra.rhomeo.api.ui.DashboardMenuItem;
|
||||
import fr.cenra.rhomeo.core.RhomeoRuntimeException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Optional;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* Dashboard table presenting results for the selected site.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXDashboardResultsTable")
|
||||
@Scope("prototype")
|
||||
public class FXDashboardResultsTable extends FXTableView<IndicatorValuesByYearItem> {
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private DashboardResultsManager resultsManager;
|
||||
|
||||
@Autowired
|
||||
private List<DashboardMenuItem> menuItems;
|
||||
|
||||
private final ObjectProperty<Site> siteProperty = new SimpleObjectProperty<>();
|
||||
|
||||
/**
|
||||
* Generate a table for the specified site.
|
||||
*
|
||||
* @param byCell true if the table has to use the selection by cell mode, false for generic by line selection.
|
||||
*/
|
||||
public FXDashboardResultsTable(final boolean byCell) {
|
||||
setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
if (byCell) {
|
||||
getSelectionModel().setCellSelectionEnabled(true);
|
||||
getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
}
|
||||
|
||||
siteProperty.addListener(this::siteChanged);
|
||||
}
|
||||
|
||||
|
||||
public ObjectProperty<Site> siteProperty() {
|
||||
return siteProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes JavaFX components after spring components are done loading.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
final ContextMenu menu = new ContextMenu();
|
||||
setContextMenu(menu);
|
||||
for (final DashboardMenuItem menuItem : menuItems) {
|
||||
final Optional<MenuItem> item = menuItem.createItem(this);
|
||||
if(item.isPresent()){
|
||||
menu.getItems().add(item.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void siteChanged(final ObservableValue<? extends Site> obs, final Site oldSite, final Site newSite) {
|
||||
getColumns().clear();
|
||||
if (newSite == null) {
|
||||
setItems(null);
|
||||
setPlaceholder(new Label("Aucun site séléctionné"));
|
||||
} else {
|
||||
try {
|
||||
setItems(resultsManager.getDashboardResultsByYear(newSite));
|
||||
} catch (Exception ex) {
|
||||
throw new RhomeoRuntimeException(ex);
|
||||
}
|
||||
|
||||
if (getItems().isEmpty()) {
|
||||
setPlaceholder(new Label("Aucun résultat attaché au site"));
|
||||
} else {
|
||||
final TableColumn<IndicatorValuesByYearItem, Integer> yearCol = new TableColumn<>("Année");
|
||||
yearCol.setCellValueFactory(value -> new SimpleIntegerProperty(value.getValue().getYear()).asObject());
|
||||
getColumns().add(yearCol);
|
||||
|
||||
final List<Indicator> indicators = session.getIndicators();
|
||||
final List<Protocol> protocols = session.getProtocols();
|
||||
|
||||
final List<IndicatorSiteColumn> columnsToAdd = new ArrayList<>();
|
||||
|
||||
protocols.stream()
|
||||
.filter(protocol -> protocol.isCompatible(newSite))
|
||||
.sorted()
|
||||
.forEach(protocol
|
||||
-> indicators.stream()
|
||||
.filter(indicator -> indicator.getProtocol() == protocol)
|
||||
.sorted()
|
||||
.forEach(indicator -> {
|
||||
final IndicatorSiteColumn siteColumn = new IndicatorSiteColumn(indicator);
|
||||
siteColumn.setCellValueFactory(this::getCellValueFactory);
|
||||
siteColumn.setCellFactory(this::getCellFactory);
|
||||
columnsToAdd.add(siteColumn);
|
||||
})
|
||||
);
|
||||
Collections.sort(columnsToAdd);
|
||||
getColumns().addAll(columnsToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TableCell<IndicatorValuesByYearItem, Double> getCellFactory(final TableColumn<IndicatorValuesByYearItem, Double> input) {
|
||||
// hack #61 display integers for I04/I07
|
||||
final DecimalFormat format;
|
||||
if (input instanceof IndicatorSiteColumn &&
|
||||
(((IndicatorSiteColumn)input).getIndicator().getName().matches("I0(4|7)"))) {
|
||||
format = Rhomeo.SIMPLE_DECIMAL_FORMAT;
|
||||
} else {
|
||||
format = Rhomeo.SECOND_DECIMAL_FORMAT;
|
||||
}
|
||||
final FXTableCell cell = new FXTableCell<IndicatorValuesByYearItem, Double>() {
|
||||
|
||||
@Override
|
||||
protected void updateItem(Double item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
setContentDisplay(ContentDisplay.RIGHT);
|
||||
if (empty || item == null || Double.isNaN(item)) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
final IndicatorValuesByYearItem itemLine = getItems().get(getIndex());
|
||||
if (itemLine == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(format.format(item));
|
||||
if (input instanceof IndicatorSiteColumn) {
|
||||
final Boolean isPublished = itemLine.isPublished(((IndicatorSiteColumn) input).getIndicator().getName());
|
||||
if (isPublished != null && !isPublished) {
|
||||
setGraphic(new ImageView(Rhomeo.ICON_NOT_PUBLISHED));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
cell.setAlignment(Pos.CENTER_RIGHT);
|
||||
return cell;
|
||||
}
|
||||
|
||||
private ObservableValue<Double> getCellValueFactory(final TableColumn.CellDataFeatures<IndicatorValuesByYearItem, Double> feature) {
|
||||
final TableColumn<IndicatorValuesByYearItem, Double> col = feature.getTableColumn();
|
||||
final String indicName;
|
||||
if (col instanceof IndicatorSiteColumn) {
|
||||
indicName = ((IndicatorSiteColumn) col).getIndicator().getName();
|
||||
} else {
|
||||
indicName = null;
|
||||
}
|
||||
|
||||
final IndicatorValuesByYearItem.PublishedValue valueForIndicator
|
||||
= indicName == null ? null : feature.getValue().getValueForIndicator(indicName);
|
||||
|
||||
if (valueForIndicator == null || valueForIndicator.getValue() == null) {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
return new SimpleObjectProperty<>(valueForIndicator.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.api.InternationalDescription;
|
||||
import fr.cenra.rhomeo.api.preferences.PasswordKey;
|
||||
import fr.cenra.rhomeo.api.preferences.PreferenceGroup;
|
||||
import fr.cenra.rhomeo.api.ui.PreferencePane;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
/**
|
||||
*
|
||||
* A default preference pane based on key/value structure.
|
||||
*
|
||||
* Each preference is displayed as a row of a {@link GridPane} in which the key
|
||||
* is displayed as a {@link Label} on the left side, and the value is displayed
|
||||
* in a {@link TextField} on the right side.
|
||||
*
|
||||
* Preferences are provided by a {@link PreferenceGroup} which must be set only
|
||||
* once.
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys)
|
||||
*
|
||||
* @param <T> The TYPE of {@link PreferenceGroup} keys.
|
||||
*/
|
||||
public class FXDefaultPreferenceGroupPane<T extends InternationalDescription> extends GridPane implements PreferencePane<T> {
|
||||
|
||||
private PreferenceGroup<T> preferenceGroup;
|
||||
private final Map<T, StringProperty> valueFields = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Builds a {@link FXDefaultPreferenceGroupPane} based on key/value structure of
|
||||
* a {@link PreferenceGroup}.
|
||||
*
|
||||
* @param preferenceGroup
|
||||
*/
|
||||
public FXDefaultPreferenceGroupPane(final PreferenceGroup<T> preferenceGroup){
|
||||
this();
|
||||
setPreferenceGroup(preferenceGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor used by Spring to produce new prototype.
|
||||
*
|
||||
* This constructor does not fill the pane for any {@link PreferenceGroup}.
|
||||
*
|
||||
* The method {@link #setPreferenceGroup(PreferenceGroup)} must be used to fill the pane.
|
||||
*/
|
||||
public FXDefaultPreferenceGroupPane(){
|
||||
final String css = FXDefaultPreferenceGroupPane.class.getResource(FXDefaultPreferenceGroupPane.class.getSimpleName()+".css").toExternalForm();
|
||||
getStylesheets().add(css);
|
||||
getStyleClass().add("pane");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceGroup<T> getPreferenceGroup() {
|
||||
return preferenceGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference group.
|
||||
*
|
||||
* Must be called only once.
|
||||
*
|
||||
* If the current object has been built with the default constructor (for
|
||||
* instance by Spring autowiring), it must be called explicitly. On the
|
||||
* contrary, this method is implicitly called by the constructor using a
|
||||
* {@link PreferenceGroup} parameter.
|
||||
*
|
||||
* For each key of the {@link PreferenceGroup}, this method adds a new row
|
||||
* to the pane, with two columns. The first one contains a label relative to
|
||||
* the preference key. The second one contains a text field to edit the
|
||||
* corresponding preference value.
|
||||
*
|
||||
* @param preferenceGroup
|
||||
*/
|
||||
public final void setPreferenceGroup(final PreferenceGroup<T> preferenceGroup){
|
||||
|
||||
if(this.preferenceGroup!=null) throw new IllegalStateException("The preference group has already been set.");
|
||||
|
||||
this.preferenceGroup = preferenceGroup;
|
||||
int row = 0;
|
||||
for(final T key : preferenceGroup.getKeys()){
|
||||
final Label label = new Label(key.getLabel());
|
||||
final String comment = key.getDescription();
|
||||
if(comment!=null && !comment.equals("")){
|
||||
label.setTooltip(new Tooltip(comment));
|
||||
}
|
||||
|
||||
final Node editor = createEditor(key);
|
||||
addRow(row++, label, editor);
|
||||
}
|
||||
}
|
||||
|
||||
private Node createEditor(final T key) {
|
||||
if (key instanceof PasswordKey) {
|
||||
final Button editBtn = new Button("Modifier");
|
||||
final HBox container = new HBox(5, editBtn);
|
||||
editBtn.setOnAction((evt) -> {
|
||||
final PasswordField field = new PasswordField();
|
||||
valueFields.put(key, field.textProperty());
|
||||
final Button cancelBtn = new Button("Annuler");
|
||||
cancelBtn.setCancelButton(true);
|
||||
cancelBtn.setOnAction(evt2 -> {
|
||||
valueFields.remove(key);
|
||||
container.getChildren().clear();
|
||||
container.getChildren().add(editBtn);
|
||||
});
|
||||
container.getChildren().clear();
|
||||
container.getChildren().addAll(field, cancelBtn);
|
||||
});
|
||||
return container;
|
||||
} else {
|
||||
final TextField textField = new TextField(preferenceGroup.getPreference(key));
|
||||
textField.setPrefWidth(300);
|
||||
valueFields.put(key, textField.textProperty());
|
||||
return textField;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the preferences.
|
||||
*/
|
||||
@Override
|
||||
public void save(){
|
||||
if(preferenceGroup!=null){
|
||||
for(final T key : preferenceGroup.getKeys()){
|
||||
final StringProperty value = valueFields.get(key);
|
||||
if(value!=null){
|
||||
preferenceGroup.setPreference(key, value.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getEditor() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.api.data.DataContext;
|
||||
import fr.cenra.rhomeo.api.data.Dataset;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.api.data.TrackingPoint;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.process.Process;
|
||||
import fr.cenra.rhomeo.api.process.ProcessContext;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.api.ui.FilterTable;
|
||||
import fr.cenra.rhomeo.api.ui.FilterTableSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.core.util.SimplePredicateChain;
|
||||
import fr.cenra.rhomeo.fx.edition.FXTrackingPointTable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.FlowPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Default JavaFX pane to set processing parameters.
|
||||
*
|
||||
* For a given {@link Dataset} and a given {@link Protocol}, this pane allows to
|
||||
* choose the {@link Indicator}(s) and the {@link TrackingPoint}(s) the processing
|
||||
* must be computed for.
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys)
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXDefaultProcessingPane")
|
||||
@Scope("prototype")
|
||||
public class FXDefaultProcessingPane<T extends Statement> extends VBox implements InternationalResource {
|
||||
|
||||
@FXML private GridPane uiIndicatorPane;
|
||||
private final Collection<IndicatorSelection> indicatorSelections = new ArrayList<>();
|
||||
|
||||
@FXML private FlowPane uiYearPane;
|
||||
private final Collection<YearSelection> yearSelections = new ArrayList<>();
|
||||
|
||||
@FXML private BorderPane uiTrackingPointPane;
|
||||
|
||||
private FilterTable<? extends Predicate> uiSelectionTable;
|
||||
private final TableColumn<Predicate, Boolean> uiSelectionColumn;
|
||||
|
||||
private final Map<Predicate, BooleanProperty> filterSelection = new HashMap<>();
|
||||
private final Function<Predicate, BooleanProperty> selectionComputer;
|
||||
|
||||
@FXML
|
||||
private VBox uiProgressBox;
|
||||
|
||||
@FXML
|
||||
private Label uiProgressTitle;
|
||||
|
||||
@FXML
|
||||
private ProgressBar uiProgress;
|
||||
|
||||
@FXML
|
||||
private Label uiProgressMessage;
|
||||
|
||||
@FXML
|
||||
private HBox uiBtnBox;
|
||||
|
||||
private Dataset<T> dataset;
|
||||
private Protocol protocol;
|
||||
private ProcessContext processContext;
|
||||
private Site site;
|
||||
|
||||
private final ChangeListener<Boolean> selectionListener = this::selectionChanged;
|
||||
private final SimpleObjectProperty<Task<Set<Index>>> globalTask = new SimpleObjectProperty<>();
|
||||
|
||||
private SimplePredicateChain processFilter;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private List<FilterTableSpi> pointTableSpis;
|
||||
|
||||
public FXDefaultProcessingPane() {
|
||||
Rhomeo.loadFXML(this, FXDefaultProcessingPane.class);
|
||||
|
||||
uiProgressBox.setVisible(false);
|
||||
globalTask.addListener((obs, oldTask, newTask) -> {
|
||||
if (oldTask != null) {
|
||||
uiProgressBox.visibleProperty().unbind();
|
||||
uiProgressBox.setVisible(false);
|
||||
uiProgressTitle.textProperty().unbind();
|
||||
uiProgressMessage.textProperty().unbind();
|
||||
uiProgress.progressProperty().unbind();
|
||||
uiIndicatorPane.disableProperty().unbind();
|
||||
uiTrackingPointPane.disableProperty().unbind();
|
||||
uiBtnBox.disableProperty().unbind();
|
||||
} else {
|
||||
uiProgressBox.visibleProperty().bind(newTask.runningProperty());
|
||||
uiProgressTitle.textProperty().bind(newTask.titleProperty());
|
||||
uiProgressMessage.textProperty().bind(newTask.messageProperty());
|
||||
uiProgress.progressProperty().bind(newTask.progressProperty());
|
||||
uiIndicatorPane.disableProperty().bind(newTask.runningProperty());
|
||||
uiTrackingPointPane.disableProperty().bind(newTask.runningProperty());
|
||||
uiBtnBox.disableProperty().bind(newTask.runningProperty());
|
||||
}
|
||||
});
|
||||
|
||||
uiSelectionColumn = new TableColumn(ResourceBundle.getBundle(FXDefaultProcessingPane.class.getCanonicalName()).getString("selectionColumn"));
|
||||
selectionComputer = input -> {
|
||||
final BooleanProperty bp = new SimpleBooleanProperty(input, "selection", false);
|
||||
bp.addListener(selectionListener);
|
||||
bp.set(true);
|
||||
|
||||
return bp;
|
||||
};
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
dataset = session.getDataset();
|
||||
ArgumentChecks.ensureNonNull("dataset", dataset);
|
||||
|
||||
final DataContext context = session.getDataContext();
|
||||
protocol = context.getProtocol();
|
||||
ArgumentChecks.ensureNonNull("protocol", protocol);
|
||||
|
||||
site = context.getSite();
|
||||
ArgumentChecks.ensureNonNull("site", site);
|
||||
|
||||
/*
|
||||
1- Indicator pane.
|
||||
================*/
|
||||
final List<Indicator> sortedIndicators = new ArrayList<>(session.getIndicators());
|
||||
sortedIndicators.removeIf(i -> !protocol.equals(i.getProtocol()));
|
||||
Collections.sort(sortedIndicators);
|
||||
initIndicatorPane(sortedIndicators);
|
||||
|
||||
/*
|
||||
2- Year pane.
|
||||
============*/
|
||||
final List<Integer> years = new ArrayList<>();
|
||||
for(final TrackingPoint trackingPoint : dataset.getTrackingPoints()){
|
||||
final int year = trackingPoint.getYear();
|
||||
|
||||
// Do not add the same year twice…
|
||||
if(!years.contains(year)){
|
||||
years.add(year);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort years…
|
||||
Collections.sort(years);
|
||||
initYearPane(years);
|
||||
|
||||
/*
|
||||
3- Tracking point pane.
|
||||
=====================*/
|
||||
initTrackingPointPane();
|
||||
|
||||
/*
|
||||
4- LISTENERS.
|
||||
=====================*/
|
||||
uiSelectionTable.getItems().addListener(this::tableContentChanged);
|
||||
for (final IndicatorSelection is : indicatorSelections) {
|
||||
is.selectedProperty.addListener(selectionListener);
|
||||
}
|
||||
|
||||
// FXTrackingPointPane must only display tracking point which year is selected into FXYearPane
|
||||
yearSelections.forEach(yearSelection ->
|
||||
yearSelection.selectedProperty().addListener(selectionListener)
|
||||
);
|
||||
|
||||
/*
|
||||
4- RESTORE PROCESS CONTEXT.
|
||||
=====================*/
|
||||
// Start by extracting previous indicators / years / filters
|
||||
processContext = session.getProcessContext();
|
||||
final Set<Indicator> previousIndics = new HashSet<>(processContext.getIndicators());
|
||||
final HashSet<Integer> previousYears = new HashSet<>();
|
||||
processContext.getTrackingPoints().forEach(tp -> previousYears.add(tp.getYear()));
|
||||
if (processContext.getFilter() instanceof SimplePredicateChain)
|
||||
processFilter = (SimplePredicateChain) processContext.getFilter();
|
||||
else {
|
||||
processFilter = new SimplePredicateChain();
|
||||
if (processContext.getFilter() != null)
|
||||
processFilter.predicates.add(processContext.getFilter());
|
||||
processContext.setFilter(processFilter);
|
||||
}
|
||||
final Set<Predicate> previousFilters = new HashSet(processFilter.predicates);
|
||||
|
||||
// Restore selected indicators.
|
||||
for (final IndicatorSelection is : indicatorSelections)
|
||||
is.selectedProperty.set(previousIndics.contains(is.indicator));
|
||||
|
||||
// Find activated years
|
||||
for (final YearSelection ys : yearSelections)
|
||||
ys.selectedProperty.set(previousYears.contains(ys.year));
|
||||
|
||||
// Set same filter selection
|
||||
if (!previousFilters.isEmpty())
|
||||
for (final Predicate p : uiSelectionTable.getItems())
|
||||
filterSelection.computeIfAbsent(p, selectionComputer).set(previousFilters.contains(p));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The selected {@link Indicator}s.
|
||||
*/
|
||||
public Collection<Indicator> getIndicators() {
|
||||
return indicatorSelections.stream()
|
||||
.filter(is -> is.selectedProperty().get())
|
||||
.map(IndicatorSelection::getIndicator)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// indicator pane // indicator pane // indicator pane // indicator pane
|
||||
|
||||
/**
|
||||
* Init sub-pane for displaying indicators.
|
||||
*/
|
||||
private void initIndicatorPane(final List<Indicator> indicators){
|
||||
|
||||
for(int i=0; i<indicators.size(); i++){
|
||||
final Indicator indicator = indicators.get(i);
|
||||
final IndicatorSelection indicatorSelection = new IndicatorSelection(indicator);
|
||||
indicatorSelections.add(indicatorSelection);
|
||||
final CheckBox checkBox = new CheckBox();
|
||||
checkBox.setAllowIndeterminate(false);
|
||||
final Label label = new Label();
|
||||
label.setText(indicator.getRemarks()+" - "+indicator.getProtocol().getRemarks());
|
||||
indicatorSelection.selectedProperty.bindBidirectional(checkBox.selectedProperty());
|
||||
final Label labelButton = new Label(indicator.getName());
|
||||
labelButton.getStyleClass().add(Rhomeo.CSS_INDICATOR_LABEL);
|
||||
|
||||
if(!indicator.getZoneTypeCodes().contains(site.getZoneType())){
|
||||
checkBox.setSelected(false);
|
||||
checkBox.setDisable(true);
|
||||
labelButton.setDisable(true);
|
||||
final Label alert = new Label(null, new ImageView(Rhomeo.ICON_WARNING_ORANGE));
|
||||
alert.setTooltip(new Tooltip("Cet indicateur n'est pas compatible avec le type de zone humide du site."));
|
||||
uiIndicatorPane.addRow(i, checkBox, labelButton, label, alert);
|
||||
}
|
||||
else{
|
||||
uiIndicatorPane.addRow(i, checkBox, labelButton, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class associating a selection boolean state to an {@link Indicator}.
|
||||
*/
|
||||
private static class IndicatorSelection extends Selection {
|
||||
|
||||
private final Indicator indicator;
|
||||
|
||||
public IndicatorSelection(final Indicator indicator){
|
||||
ArgumentChecks.ensureNonNull("indicator", indicator);
|
||||
this.indicator = indicator;
|
||||
}
|
||||
|
||||
public Indicator getIndicator(){return indicator;}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// year pane // year pane // year pane // year pane // year pane // year pane
|
||||
|
||||
/**
|
||||
* Init sub-pane for displaying years.
|
||||
*
|
||||
* This pane is an immutable FlowPane whose children represent the state
|
||||
* (selected or not) of years given to the constructor. The state of the
|
||||
* years is accessible with {@link YearSelection#selectedProperty()}.
|
||||
*/
|
||||
private void initYearPane (final List<Integer> years){
|
||||
for (final Integer year : years) {
|
||||
final YearSelection yearSelection = new YearSelection(year);
|
||||
yearSelections.add(yearSelection);
|
||||
final CheckBox checkBox = new CheckBox();
|
||||
checkBox.setAllowIndeterminate(false);
|
||||
final Label label = new Label();
|
||||
label.setText(year.toString());
|
||||
yearSelection.selectedProperty.bindBidirectional(checkBox.selectedProperty());
|
||||
uiYearPane.getChildren().add(new HBox(checkBox, label));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class associating a selection boolean state to a year.
|
||||
*/
|
||||
private static class YearSelection extends Selection {
|
||||
|
||||
private final int year;
|
||||
|
||||
public YearSelection(final int year){
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public int getYear(){return year;}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// trackingPoint pane // trackingPoint pane // trackingPoint pane
|
||||
|
||||
/**
|
||||
* Init sub-pane for displaying {@link TrackingPoint}s.
|
||||
*/
|
||||
private void initTrackingPointPane(){
|
||||
for (final FilterTableSpi spi : pointTableSpis) {
|
||||
if (spi.getProtocol() == session.getDataContext().getProtocol()) {
|
||||
uiSelectionTable = spi.createEmptyTable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uiSelectionTable == null) {
|
||||
uiSelectionTable = new FXTrackingPointTable(session);
|
||||
}
|
||||
|
||||
uiSelectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
uiSelectionTable.getColumns().add(0, (TableColumn) uiSelectionColumn);
|
||||
uiSelectionTable.setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE);
|
||||
|
||||
uiSelectionColumn.setCellValueFactory(param -> filterSelection.computeIfAbsent(param.getValue(), selectionComputer));
|
||||
uiSelectionColumn.setCellFactory(param -> {
|
||||
final TableCell<Predicate, Boolean> cell = new CheckBoxTableCell<>();
|
||||
cell.setEditable(true);
|
||||
return cell;
|
||||
});
|
||||
uiSelectionColumn.setEditable(true);
|
||||
uiSelectionTable.setEditable(true);
|
||||
uiSelectionTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
uiTrackingPointPane.setCenter(uiSelectionTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter dataset tracking points according to current year selection, to set
|
||||
* {@link #uiTrackingPointPane} content. If no year is selected, all points
|
||||
* are activated.
|
||||
*/
|
||||
private void resetSelection() {
|
||||
uiSelectionTable.getSelectionModel().clearSelection();
|
||||
final Set<Integer> selectedYears = yearSelections.stream()
|
||||
.filter(ys -> ys.selectedProperty.get())
|
||||
.map(ys -> ys.year)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (selectedYears.isEmpty()) {
|
||||
uiSelectionTable.setPreFilter((input) -> false);
|
||||
} else if (selectedYears.size() == yearSelections.size()) {
|
||||
uiSelectionTable.setPreFilter(null);
|
||||
} else {
|
||||
final Function<TrackingPoint, Boolean> yearChecker = tp -> selectedYears.contains(tp.getYear());
|
||||
final Predicate<Statement> preFilter = st -> {
|
||||
return dataset.findPoint(st)
|
||||
.map(yearChecker)
|
||||
.orElse(false);
|
||||
};
|
||||
uiSelectionTable.setPreFilter(preFilter);
|
||||
}
|
||||
}
|
||||
|
||||
private void tableContentChanged(final ListChangeListener.Change<? extends Predicate> c) {
|
||||
while (c.next()) {
|
||||
if (c.wasRemoved()) {
|
||||
for (final Predicate p : c.getRemoved()) {
|
||||
filterSelection.computeIfAbsent(p, selectionComputer).set(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (c.wasAdded()) {
|
||||
for (final Predicate p : c.getAddedSubList()) {
|
||||
filterSelection.computeIfAbsent(p, selectionComputer).set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Utilities // Utilities // Utilities // Utilities // Utilities //
|
||||
|
||||
/**
|
||||
* Utility abstract class providing a selection boolean state.
|
||||
*/
|
||||
private static abstract class Selection {
|
||||
protected final BooleanProperty selectedProperty = new SimpleBooleanProperty(this, "selection", false);
|
||||
|
||||
public ReadOnlyBooleanProperty selectedProperty(){
|
||||
return selectedProperty;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update UI/related components when either year, indicator or filter
|
||||
* selection change.
|
||||
*
|
||||
* @param obs Observable selection which change. For this method to actually
|
||||
* do something, the given observable must be a {@link ReadOnlyProperty}
|
||||
* with non-null {@link ReadOnlyProperty#getBean() } property.
|
||||
* @param oldValue
|
||||
* @param newValue
|
||||
*/
|
||||
private void selectionChanged(final ObservableValue<? extends Boolean> obs, Boolean oldValue, Boolean newValue) {
|
||||
final Object bean;
|
||||
if (obs instanceof ReadOnlyProperty) {
|
||||
bean = ((ReadOnlyProperty)obs).getBean();
|
||||
} else {
|
||||
bean = null;
|
||||
}
|
||||
|
||||
if (bean instanceof YearSelection) {
|
||||
// Synchronize tracking points state with corresponding year.
|
||||
resetSelection();
|
||||
|
||||
} else if (bean instanceof IndicatorSelection) {
|
||||
final Indicator i = ((IndicatorSelection)bean).getIndicator();
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
processContext.getIndicators().add(i);
|
||||
} else {
|
||||
processContext.getIndicators().remove(i);
|
||||
}
|
||||
} else if (bean instanceof Predicate) {
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
processFilter.predicates.add((Predicate) bean);
|
||||
} else {
|
||||
processFilter.predicates.remove((Predicate) bean);
|
||||
}
|
||||
processContext.setFilter(null);
|
||||
processContext.setFilter(processFilter);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Process management // Process management // Process management //
|
||||
|
||||
@FXML
|
||||
void nextStep(ActionEvent event) {
|
||||
final Computer computer = new Computer();
|
||||
globalTask.set(computer);
|
||||
|
||||
computer.stateProperty().addListener(obs -> {
|
||||
if (computer.isDone()) {
|
||||
switch (computer.getState()) {
|
||||
case FAILED:
|
||||
Rhomeo.showAlert(Alert.AlertType.ERROR, getFormatedResourceString("process-failed"));
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
session.setResults(computer.getValue());
|
||||
globalTask.set(null);
|
||||
session.requestWorkflowStep(WorkflowStep.RESULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TaskManager.INSTANCE.submit(computer);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void cancelProcess(ActionEvent event) {
|
||||
final Task<Set<Index>> task = globalTask.get();
|
||||
if (task != null)
|
||||
task.cancel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap processes generated by requested indicators, execute them and wait
|
||||
* for completion of the entire process set.
|
||||
*/
|
||||
private class Computer extends Task<Set<Index>> {
|
||||
|
||||
private final List<Process> processes = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected Set<Index> call() throws Exception {
|
||||
updateProgress(-1, -1);
|
||||
updateTitle(getResourceString("process.prepare"));
|
||||
for (final Indicator indicator : getIndicators()) {
|
||||
processes.addAll(indicator.createProcesses(processContext));
|
||||
}
|
||||
|
||||
session.getAdditionalValues().clear();
|
||||
if (processes.size() < 1) {
|
||||
throw new IllegalStateException("Nothing to process.");
|
||||
|
||||
} else if (processes.size() == 1) {
|
||||
final Process wrapped = processes.get(0);
|
||||
Platform.runLater(() -> {
|
||||
wrapped.titleProperty().addListener((obs) -> updateTitle(wrapped.getTitle()));
|
||||
wrapped.messageProperty().addListener((obs) -> updateTitle(wrapped.getMessage()));
|
||||
wrapped.workDoneProperty().addListener((obs) -> updateProgress(wrapped.getWorkDone(), wrapped.getTotalWork()));
|
||||
});
|
||||
|
||||
wrapped.run();
|
||||
return (Set) wrapped.get();
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Compute a maximal progress for this process aggregator.
|
||||
* If any process has no total work defined, the aggregator total is
|
||||
* the number of processes to execute. Otherwise, its the sum of all
|
||||
* process to execute.
|
||||
*
|
||||
* The same is done for the "work done" property. If the aggregated
|
||||
* total is undefined, the work done is the number of finished
|
||||
* processes. Otherwise, it's the sum of work done of each process.
|
||||
*/
|
||||
final Observable[] totals = new Observable[processes.size()];
|
||||
final Observable[] worksDone = new Observable[processes.size()];
|
||||
for (int i = 0; i < processes.size(); i++) {
|
||||
totals[i] = processes.get(i).totalWorkProperty();
|
||||
worksDone[i] = processes.get(i).workDoneProperty();
|
||||
}
|
||||
|
||||
final DoubleBinding totalBinding = Bindings.createDoubleBinding(() -> {
|
||||
double result = 0;
|
||||
double processTotal;
|
||||
for (final Process p : processes) {
|
||||
processTotal = p.getTotalWork();
|
||||
if (processTotal < 0) {
|
||||
return (double) totals.length;
|
||||
}
|
||||
result += processTotal;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, totals);
|
||||
|
||||
final DoubleBinding workDoneBinding = Bindings.createDoubleBinding(() -> {
|
||||
double result = 0;
|
||||
double processWork;
|
||||
final boolean doCount = totalBinding.get() < 0;
|
||||
for (final Process p : processes) {
|
||||
if (doCount) {
|
||||
if (p.isDone())
|
||||
result++;
|
||||
} else {
|
||||
processWork = p.getWorkDone();
|
||||
if (processWork >= 0) {
|
||||
result += processWork;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}, worksDone);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
workDoneBinding.addListener((obs) -> updateProgress(workDoneBinding.get(), totalBinding.get()));
|
||||
});
|
||||
|
||||
final Set<Index> indices = new HashSet<>();
|
||||
updateTitle(getResourceString("process.go"));
|
||||
|
||||
for (final Process p : processes) {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
TaskManager.INSTANCE.submit(p);
|
||||
}
|
||||
|
||||
for (final Process p : processes) {
|
||||
if (Thread.interrupted()) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
|
||||
indices.addAll(p.get());
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
for (final Process p : processes) {
|
||||
p.cancel(true);
|
||||
}
|
||||
return super.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.InternationalDescription;
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.api.data.Dataset;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.TrackingPoint;
|
||||
import fr.cenra.rhomeo.api.data.Warning;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.AbstractParametrableIndex;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.api.result.IndexMapper;
|
||||
import fr.cenra.rhomeo.api.result.IndexSpi;
|
||||
import fr.cenra.rhomeo.api.ui.AdditionalResultSpi;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.core.result.ResultWriter;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validator;
|
||||
|
||||
|
||||
/**
|
||||
* The result pane controller.
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys)
|
||||
*
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXDefaultResultPane")
|
||||
@Scope("prototype")
|
||||
public class FXDefaultResultPane extends VBox implements InternationalResource {
|
||||
|
||||
/**
|
||||
* Key (or key-part) that must be used to retrieve specific headers from
|
||||
* {@link IndexSpi} and or base classes implementing {@link InternationalResource},
|
||||
* to complementary table headers.
|
||||
*/
|
||||
public static final String COMPLEMENTARY_TABLE_HEADER_PROPERTY_KEY = "result-pane.complementary-table";
|
||||
|
||||
/**
|
||||
* Fields taken into account to build columns mapping base classes into
|
||||
* complementary tables.
|
||||
*/
|
||||
private static final List<String> BASE_FIELDS = Arrays.asList("name", "year");
|
||||
|
||||
@FXML private Label uiSiteLbl;
|
||||
@FXML private TableView<IndexMapper<Integer>> uiSitePane;
|
||||
@FXML private GridPane uiInvalidateGridPane;
|
||||
@FXML private TableColumn<IndexMapper<Integer>, Integer> uiSiteYearColumn;
|
||||
@FXML private ToggleButton uiDisplayValuesToggleBtn;
|
||||
@FXML private ScrollPane uiAdditionalValuesScrollPane;
|
||||
@FXML private VBox uiAdditionalValuesBox;
|
||||
@FXML private VBox uiTableBox;
|
||||
|
||||
@FXML private Label uiYearsLbl;
|
||||
@FXML private Label uiIndicatorLbl;
|
||||
@FXML private Label uiDataStatusLbl;
|
||||
@FXML private Label uiAdditionalLabel;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Autowired
|
||||
private ResultWriter resultWriter;
|
||||
|
||||
@Autowired
|
||||
private List<AdditionalResultSpi> additionalSpis;
|
||||
|
||||
private final SimpleBooleanProperty isInvalid = new SimpleBooleanProperty(false);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public FXDefaultResultPane() {
|
||||
Rhomeo.loadFXML(this, FXDefaultResultPane.class);
|
||||
uiSitePane.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
uiSitePane.setFixedCellSize(25);
|
||||
uiAdditionalLabel.visibleProperty().bind(Bindings.isNotEmpty(uiAdditionalValuesBox.getChildren()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes JavaFX components after spring components are done loading.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
final Set<Index> results = session.getResults();
|
||||
ArgumentChecks.ensureNonNull("results", results);
|
||||
|
||||
/*
|
||||
1- Schedule.
|
||||
==========*/
|
||||
|
||||
// Determine indicators and schedule results.
|
||||
|
||||
final Map<Integer, IndexMapper<Integer>> mainResults = new HashMap<>();
|
||||
final ArrayList<IndexSpi> mainSpis = new ArrayList<>();
|
||||
final List<Indicator> indicators = new ArrayList<>();
|
||||
final Map<Class, Map<Object, IndexMapper>> complementaryResults = new HashMap<>();
|
||||
final Map<Integer, IndexMapper<Integer>> byYearComplementary = new HashMap<>();
|
||||
final Map<Class, List<IndexSpi>> indexSpis = new HashMap<>();
|
||||
for(final Index index : results){
|
||||
|
||||
// Seach for index indicators.
|
||||
final Indicator indicator = index.getSpi().getIndicator();
|
||||
if(!indicators.contains(indicator)){
|
||||
indicators.add(indicator);
|
||||
}
|
||||
|
||||
// Detect main indices for each indicator.
|
||||
if (index.getSpi().isPrimary()) {
|
||||
final int year = index.getYear();
|
||||
final IndexMapper<Integer> mapper = mainResults.computeIfAbsent(year, (input) -> new IndexMapper<>(input));
|
||||
if(!mainSpis.contains(index.getSpi()))
|
||||
mainSpis.add(index.getSpi());
|
||||
mapper.setValue(index.getSpi().getName(), index.getValue());
|
||||
}
|
||||
// Schedule AbstractParametrableIndexes by base type.
|
||||
else if(index instanceof AbstractParametrableIndex) {
|
||||
final AbstractParametrableIndex parametrableIndex = (AbstractParametrableIndex) index;
|
||||
final Class baseClass = parametrableIndex.getBaseClass();
|
||||
final Object base = parametrableIndex.getBase();
|
||||
final IndexSpi indexSpi = parametrableIndex.getSpi();
|
||||
|
||||
if(complementaryResults.get(baseClass)==null){
|
||||
complementaryResults.put(baseClass, new HashMap<>());
|
||||
}
|
||||
if(complementaryResults.get(baseClass).get(base)==null){
|
||||
complementaryResults.get(baseClass).put(base, new IndexMapper<>(base));
|
||||
}
|
||||
final IndexMapper indexMapper = complementaryResults.get(baseClass).get(base);
|
||||
indexMapper.setValue(indexSpi.getName(), parametrableIndex.getValue());
|
||||
|
||||
//
|
||||
if(indexSpis.get(baseClass)==null) indexSpis.put(baseClass, new ArrayList<>());
|
||||
final List<IndexSpi> spis = indexSpis.get(baseClass);
|
||||
if(!spis.contains(indexSpi)) spis.add(indexSpi);
|
||||
|
||||
} else {
|
||||
final int year = index.getYear();
|
||||
final IndexMapper<Integer> mapper = byYearComplementary.computeIfAbsent(year, (input) -> new IndexMapper<>(input));
|
||||
final List<IndexSpi> spis = indexSpis.computeIfAbsent(Integer.class, i -> new ArrayList<>());
|
||||
if(!spis.contains(index.getSpi())) spis.add(index.getSpi());
|
||||
mapper.setValue(index.getSpi().getName(), index.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// Sort indicators.
|
||||
Collections.sort(indicators);
|
||||
|
||||
/*
|
||||
2- Main indicators.
|
||||
=================*/
|
||||
|
||||
// Build header message listing indicators
|
||||
final StringBuilder sbIndicator = new StringBuilder();
|
||||
sbIndicator.append("Indicateur(s) : ");
|
||||
|
||||
final Protocol currentProtocol = session.getDataContext().getProtocol();
|
||||
boolean firstIndic = true;
|
||||
for(final Indicator indicator : indicators){
|
||||
if (!firstIndic) {
|
||||
sbIndicator.append(", ");
|
||||
}
|
||||
|
||||
sbIndicator.append(indicator.getName()).append(" - ").append(indicator.getRemarks());
|
||||
firstIndic = false;
|
||||
}
|
||||
sbIndicator.append(" - ").append(currentProtocol.getRemarks());
|
||||
|
||||
// Create columns for main indicator pane.
|
||||
uiSiteYearColumn.setCellValueFactory(value -> value.getValue().keyProperty());
|
||||
mainSpis.stream().sorted().forEach(spi -> {
|
||||
// Sometimes the displayed column name seems to be from the indexSpi itself, sometimes from its indicator (i.e. a specific name)
|
||||
final TableColumn<IndexMapper<Integer>, Number> column = new TableColumn<>(getIndexSpiColumnName(spi));
|
||||
column.setCellFactory(Rhomeo.ROUNDING_CELL_FACTORY);
|
||||
column.setCellValueFactory(value -> value.getValue().valueProperty(spi.getName()));
|
||||
uiSitePane.getColumns().add(column);
|
||||
});
|
||||
uiSitePane.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
// Feed main indicator pane.
|
||||
final ObservableList<IndexMapper<Integer>> items = FXCollections.observableArrayList(mainResults.values());
|
||||
uiSitePane.setItems(items);
|
||||
uiSitePane.prefHeightProperty().bind(Bindings.size(items).multiply(uiSitePane.fixedCellSizeProperty()).add(40));
|
||||
|
||||
// Summary
|
||||
uiYearsLbl.setGraphic(new ImageView(Rhomeo.ICON_CIRCLE_GRAY));
|
||||
final StringBuilder sbYears = new StringBuilder();
|
||||
sbYears.append("Année(s) : ");
|
||||
boolean firstYear = true;
|
||||
final List<Integer> years = new ArrayList<>(mainResults.keySet());
|
||||
Collections.sort(years);
|
||||
for (final Integer year : years) {
|
||||
if (!firstYear) {
|
||||
sbYears.append(", ");
|
||||
}
|
||||
sbYears.append(year);
|
||||
firstYear = false;
|
||||
}
|
||||
uiYearsLbl.setText(sbYears.toString());
|
||||
|
||||
uiIndicatorLbl.setGraphic(new ImageView(Rhomeo.ICON_CIRCLE_GRAY));
|
||||
uiIndicatorLbl.setText(sbIndicator.toString());
|
||||
|
||||
isInvalid.addListener((obj) -> {
|
||||
if (!isInvalid.get()) {
|
||||
uiDataStatusLbl.setGraphic(new ImageView(Rhomeo.ICON_CHECK_GREEN));
|
||||
uiDataStatusLbl.setText("Les données sont conformes au protocole : "+ currentProtocol.getRemarks());
|
||||
} else {
|
||||
uiDataStatusLbl.setGraphic(new ImageView(Rhomeo.ICON_TIMES_RED));
|
||||
uiDataStatusLbl.setText("Les données ne sont pas conformes au protocole : "+ currentProtocol.getRemarks());
|
||||
}
|
||||
});
|
||||
isInvalid.set(true);
|
||||
|
||||
uiInvalidateGridPane.visibleProperty().bind(isInvalid);
|
||||
uiInvalidateGridPane.managedProperty().bind(uiInvalidateGridPane.visibleProperty());
|
||||
|
||||
uiTableBox.visibleProperty().bind(isInvalid.not().or(uiDisplayValuesToggleBtn.selectedProperty()));
|
||||
uiTableBox.managedProperty().bindBidirectional(uiSiteLbl.visibleProperty());
|
||||
|
||||
// Verify validation.
|
||||
validate();
|
||||
|
||||
/*
|
||||
3- Other indicators.
|
||||
==================*/
|
||||
|
||||
// A table for complementary results displayed by year.
|
||||
if (!byYearComplementary.isEmpty()) {
|
||||
final TableView<IndexMapper<Integer>> table = new TableView<>(FXCollections.observableArrayList(byYearComplementary.values()));
|
||||
final TableColumn<IndexMapper<Integer>, Integer> yearColumn = new TableColumn("Année");
|
||||
yearColumn.setCellValueFactory(input -> input.getValue() == null ? null : input.getValue().keyProperty());
|
||||
table.getColumns().add(yearColumn);
|
||||
// index value columns
|
||||
indexSpis.get(Integer.class).stream().sorted().forEach(spi -> {
|
||||
// Sometimes the displayed column name seems to be from the indexSpi itself, sometimes from its indicator (i.e. a specific name)
|
||||
final TableColumn<IndexMapper<Integer>, Number> column = new TableColumn<>(getIndexSpiColumnName(spi));
|
||||
column.setCellFactory(Rhomeo.ROUNDING_CELL_FACTORY);
|
||||
column.setCellValueFactory(value -> value.getValue().valueProperty(spi.getName()));
|
||||
table.getColumns().add(column);
|
||||
});
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
uiAdditionalValuesBox.getChildren().add(table);
|
||||
}
|
||||
|
||||
// Create one TableView by base class.
|
||||
for(final Class baseClass : complementaryResults.keySet()){
|
||||
try {
|
||||
final TableView<IndexMapper> tableView = buildIndexMapperTableView(baseClass, indexSpis.get(baseClass));
|
||||
final ObservableList<IndexMapper> additionalItems = FXCollections.observableArrayList(complementaryResults.get(baseClass).values());
|
||||
tableView.setItems(additionalItems);
|
||||
tableView.setFixedCellSize(25);
|
||||
tableView.setMinHeight(70);
|
||||
tableView.setMaxHeight(300);
|
||||
tableView.prefHeightProperty().bind(Bindings.size(additionalItems).multiply(tableView.fixedCellSizeProperty()).add(40));
|
||||
uiAdditionalValuesBox.getChildren().add(tableView);
|
||||
} catch (IntrospectionException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Could not create tableview mapping {0}.", baseClass);
|
||||
}
|
||||
}
|
||||
|
||||
final Button nextStepButton = new Button(getResourceString("proceed"));
|
||||
nextStepButton.getStyleClass().add("contrast");
|
||||
nextStepButton.setOnAction(value -> {
|
||||
session.requestWorkflowStep(WorkflowStep.FINALIZATION);
|
||||
});
|
||||
nextStepButton.visibleProperty().bind(isInvalid.not());
|
||||
nextStepButton.managedProperty().bind(nextStepButton.visibleProperty());
|
||||
final HBox bottomHBox = new HBox(nextStepButton);
|
||||
bottomHBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
bottomHBox.setFillHeight(true);
|
||||
bottomHBox.setPrefSize(USE_PREF_SIZE, USE_PREF_SIZE);
|
||||
getChildren().add(bottomHBox);
|
||||
|
||||
for (AdditionalResultSpi spi : additionalSpis) {
|
||||
spi.getResultNode(session.getAdditionalValues()).ifPresent(node -> uiAdditionalValuesBox.getChildren().add(node));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify dataset validation.
|
||||
*/
|
||||
private void validate() {
|
||||
final Runnable taskInit = () -> TaskManager.INSTANCE.submit("Validation du lot de données", () -> {
|
||||
final Set<ConstraintViolation<Dataset>> violations = validator.validate(session.getProcessContext().getData());
|
||||
final Iterator<ConstraintViolation<Dataset>> it = violations.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!it.next().getConstraintDescriptor().getPayload().contains(Warning.class)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}).setOnSucceeded(evt -> Platform.runLater(() -> isInvalid.set((boolean)evt.getSource().getValue())));
|
||||
if (Platform.isFxApplicationThread())
|
||||
taskInit.run();
|
||||
else Platform.runLater(taskInit);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void exportData() {
|
||||
final String initialFileName = session.getDataContext().getSite().getName().concat(".zip");
|
||||
final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
|
||||
final File outputFile = Rhomeo.askOutputFilePopup(initialFileName, this, zipFilter);
|
||||
if (outputFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TaskManager.INSTANCE.submit("Export de résultats", () -> {
|
||||
try (final FileSystem fs = RhomeoCore.toZipFileSystem(outputFile.toPath())) {
|
||||
resultWriter.prepareWriting(fs.getRootDirectories().iterator().next()).writeMetadataText().writeResults().writeAdditionalValues();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a complementary table for indexes associated to a given base class (for instance {@link TrackingPoint}).
|
||||
*
|
||||
* This method use introspection on the given class, but it only displays information from fields which name
|
||||
* are specified in {@link FXDefaultResultPane#BASE_FIELDS}.
|
||||
*
|
||||
* @param <T>
|
||||
* @param baseClass
|
||||
* @return
|
||||
*
|
||||
* @throws IntrospectionException
|
||||
*/
|
||||
private <T> TableView<IndexMapper<T>> buildIndexMapperTableView(final Class<T> baseClass, final List<IndexSpi> indexSpis) throws IntrospectionException {
|
||||
|
||||
final BeanInfo beanInfo = Introspector.getBeanInfo(baseClass);
|
||||
|
||||
final TableView<IndexMapper<T>> tableView = new TableView<>();
|
||||
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
// Index base columns
|
||||
for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) {
|
||||
|
||||
final String dName = desc.getName();
|
||||
if(BASE_FIELDS.contains(dName)){
|
||||
|
||||
final TableColumn<IndexMapper<T>, ?> col = new TableColumn<>(getBaseColumnName(baseClass, dName));
|
||||
col.setCellValueFactory(value -> {
|
||||
try {
|
||||
final Method reader = desc.getReadMethod();
|
||||
if(reader!=null && value!=null && value.getValue()!=null && value.getValue() instanceof IndexMapper){
|
||||
final T target = ((IndexMapper<T>) value.getValue()).getKey();
|
||||
if(target!=null){
|
||||
return new SimpleObjectProperty(reader.invoke(target));
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
else return null;
|
||||
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.INFO, "Unable to build CellValueFactory for property {0}.", dName);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
tableView.getColumns().add(col);
|
||||
}
|
||||
}
|
||||
|
||||
// index value columns
|
||||
indexSpis.stream().sorted().forEach(spi -> {
|
||||
// Sometimes the displayed column name seems to be from the indexSpi itself, sometimes from its indicator (i.e. a specific name)
|
||||
final TableColumn<IndexMapper<T>, Number> column = new TableColumn<>(getIndexSpiColumnName(spi));
|
||||
column.setCellValueFactory(value -> value.getValue().valueProperty(spi.getName()));
|
||||
column.setCellFactory(Rhomeo.ROUNDING_CELL_FACTORY);
|
||||
tableView.getColumns().add(column);
|
||||
});
|
||||
|
||||
return tableView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a name for the column mapping a given base class field in a complementary table.
|
||||
*
|
||||
* @param baseClass
|
||||
* @param propertyName
|
||||
* @return
|
||||
*/
|
||||
private static String getBaseColumnName(final Class baseClass, final String propertyName){
|
||||
|
||||
// If indexSpi implements InternationalResource, try to use specific property
|
||||
if(InternationalResource.class.isAssignableFrom(baseClass)){
|
||||
|
||||
// 1- Try to retrieve some resource specific to a complementary result table.
|
||||
try{
|
||||
return InternationalResource.getResourceString(baseClass, propertyName, COMPLEMENTARY_TABLE_HEADER_PROPERTY_KEY);
|
||||
}
|
||||
catch(MissingResourceException ex1){
|
||||
|
||||
// 2- Try to retrive resource mapping the property name.
|
||||
try{
|
||||
return InternationalResource.getResourceString(baseClass, propertyName);
|
||||
}
|
||||
catch(MissingResourceException ex2){
|
||||
RhomeoCore.LOGGER.log(Level.FINEST, "Missing ressource to display column name from {0}", InternationalResource.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no better can be done, return the property name.
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a name for the column mapping a given {@link IndexSpi} in a complementary table.
|
||||
*
|
||||
* @param indexSpi
|
||||
* @return
|
||||
*/
|
||||
private static String getIndexSpiColumnName(final IndexSpi indexSpi){
|
||||
|
||||
// If indexSpi implements InternationalResource, try to use specific property
|
||||
if(indexSpi instanceof InternationalResource){
|
||||
try{
|
||||
return ((InternationalResource) indexSpi).getResourceString(COMPLEMENTARY_TABLE_HEADER_PROPERTY_KEY);
|
||||
}
|
||||
catch(MissingResourceException e){
|
||||
RhomeoCore.LOGGER.log(Level.FINEST, "Missing ressource to display column name from {0}", InternationalResource.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
// If previous try failed and indexSpi implements InternationalDescription, use InternationalDescription label.
|
||||
if(indexSpi instanceof InternationalDescription) return ((InternationalDescription) indexSpi).getLabel();
|
||||
|
||||
// If no other solution is found, return indexSpi name.
|
||||
return indexSpi.getTitle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.api.data.DataContext;
|
||||
import fr.cenra.rhomeo.api.result.DashboardResultItem;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.data.DashboardResultsManager;
|
||||
import fr.cenra.rhomeo.core.result.ResultStorage;
|
||||
import fr.cenra.rhomeo.core.result.ResultWriter;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
|
||||
|
||||
/**
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXFinalizationPane")
|
||||
@Scope("prototype")
|
||||
public class FXFinalizationPane extends VBox implements InternationalResource {
|
||||
@FXML
|
||||
private Button addToDashboardBtn;
|
||||
|
||||
@FXML
|
||||
private Button publishBtn;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private DashboardResultsManager resultsManager;
|
||||
|
||||
@Autowired
|
||||
private ResultWriter resultWriter;
|
||||
|
||||
@Autowired
|
||||
private ResultStorage resultStorage;
|
||||
|
||||
/**
|
||||
* Stores if the results has been published or not.
|
||||
*/
|
||||
private final BooleanProperty publishedProperty = new SimpleBooleanProperty(false);
|
||||
|
||||
/**
|
||||
* Set to true once current results are attached to dashboard.
|
||||
*/
|
||||
private final BooleanProperty onDashboardProperty = new SimpleBooleanProperty(false);
|
||||
|
||||
public FXFinalizationPane() {
|
||||
Rhomeo.loadFXML(this, FXFinalizationPane.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get back to the dashboard to start a new entry of results.
|
||||
*/
|
||||
@FXML
|
||||
private void newForm() {
|
||||
session.requestWorkflowStep(null);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void exportResults() {
|
||||
final String initialFileName = new StringBuilder(session.getDataContext().getSite().getName())
|
||||
.append('_').append(session.getDataContext().getProtocol().getName()).append(".zip")
|
||||
.toString();
|
||||
|
||||
final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
|
||||
final File outputFile = Rhomeo.askOutputFilePopup(initialFileName, this, zipFilter);
|
||||
if (outputFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TaskManager.INSTANCE.submit("Export de résultats", () -> {
|
||||
final Path outputPath = outputFile.toPath();
|
||||
Files.deleteIfExists(outputPath);
|
||||
try (final FileSystem fs = RhomeoCore.toZipFileSystem(outputPath)) {
|
||||
resultWriter.prepareWriting(fs.getRootDirectories().iterator().next())
|
||||
.writeMetadataText()
|
||||
.writeResults()
|
||||
.writeAdditionalValues();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add current results to the dashboard for the current site / protocol.
|
||||
*/
|
||||
@FXML
|
||||
private void addToDashboard() {
|
||||
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
|
||||
getFormatedResourceString("alert-add-to-dashboard"),
|
||||
ButtonType.YES, ButtonType.NO);
|
||||
alert.setResizable(true);
|
||||
final ButtonType res = alert.showAndWait().get();
|
||||
if (res == ButtonType.YES) {
|
||||
final TaskManager.MockTask task = new TaskManager.MockTask("Sauvegarde des résultats", () -> {
|
||||
resultStorage.store();
|
||||
resultsManager.addDashboardResultsItems(getResults());
|
||||
return true;
|
||||
});
|
||||
|
||||
addToDashboardBtn.disableProperty().bind(task.runningProperty());
|
||||
|
||||
task.setOnSucceeded(evt -> Platform.runLater(() -> {
|
||||
addToDashboardBtn.disableProperty().unbind();
|
||||
addToDashboardBtn.setDisable(true);
|
||||
onDashboardProperty.set(true);
|
||||
}));
|
||||
|
||||
TaskManager.INSTANCE.submit(task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current results.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private List<DashboardResultItem> getResults() {
|
||||
final List<DashboardResultItem> items = new ArrayList<>();
|
||||
|
||||
final Set<Index> results = session.getResults();
|
||||
if (results != null && !results.isEmpty()) {
|
||||
final DataContext context = session.getDataContext();
|
||||
results.forEach(result -> {
|
||||
if (result.getSpi().getName().equals(result.getSpi().getIndicator().getDefaultIndex())) {
|
||||
// Do not keep tracking point index, just results for a year.
|
||||
items.add(new DashboardResultItem(result.getYear(), context.getSite().getName(),
|
||||
result.getSpi().getIndicator().getName(), result.getValue() == null ? null : result.getValue().doubleValue(),
|
||||
publishedProperty.get()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void publishResults(final ActionEvent evt) {
|
||||
final List<DashboardResultItem> results = getResults();
|
||||
final Task publish = publishSiteResults();
|
||||
publishBtn.disableProperty().bind(publish.runningProperty());
|
||||
publish.setOnSucceeded(event -> Platform.runLater(() -> {
|
||||
publishedProperty.set(true);
|
||||
publishBtn.disableProperty().unbind();
|
||||
publishBtn.setDisable(true);
|
||||
if (onDashboardProperty.get()) {
|
||||
TaskManager.INSTANCE.submit("Mise à jour des résultats locaux", () -> {
|
||||
for (final DashboardResultItem item : results)
|
||||
item.setPublished(true);
|
||||
resultsManager.updateResults(results);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
publish.setOnFailed(event -> Platform.runLater(() -> Rhomeo.showAlert(Alert.AlertType.ERROR, "La publication des résultats a échouée !")));
|
||||
|
||||
TaskManager.INSTANCE.submit(publish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish site results on the FTP.
|
||||
* Structure on the FTP is:
|
||||
* <pre>
|
||||
* results
|
||||
* |- county code
|
||||
* |--|- SHA1 geometry site
|
||||
* |--|--|- protocol
|
||||
* |--|--|--|- UUID
|
||||
* |--|--|--|--|- site.geojson
|
||||
* |--|--|--|--|- metadata.json
|
||||
* |--|--|--|--|- result.csv
|
||||
* ...
|
||||
* </pre>
|
||||
*
|
||||
* @return Publication task
|
||||
*/
|
||||
private Task publishSiteResults() {
|
||||
return new TaskManager.MockTask<>("Publication de résultats", () -> {
|
||||
resultsManager.publishResults();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.DashboardResultItem;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
|
||||
import fr.cenra.rhomeo.core.CSVDecoder;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.result.ResultStorage;
|
||||
import fr.cenra.rhomeo.core.result.SimpleLocationIndex;
|
||||
import fr.cenra.rhomeo.core.result.SimpleYearIndex;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* UI to display detailed information about all computed information for a given
|
||||
* year and {@link Indicator}. This includes the value of the main indicator
|
||||
* {@link Index} and the potential ones.
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys) <samuel.andres at geomatys.com>
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXFullSpecificResultPane")
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
public class FXFullSpecificResultPane extends VBox {
|
||||
|
||||
private final IndicatorValuesByYearItem selectedItem;
|
||||
|
||||
private final Indicator indicator;
|
||||
|
||||
private final String siteName;
|
||||
|
||||
@Autowired
|
||||
private ResultStorage resultStorage;
|
||||
|
||||
public FXFullSpecificResultPane(final IndicatorValuesByYearItem selectedItem, final Indicator indicator, final String siteName) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
|
||||
this.selectedItem = selectedItem;
|
||||
this.indicator = indicator;
|
||||
this.siteName = siteName;
|
||||
setPadding(new Insets(10.));
|
||||
setSpacing(10.);
|
||||
setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
|
||||
final String indicatorName = indicator.getName();
|
||||
final IndicatorValuesByYearItem.PublishedValue pub = selectedItem.getValueForIndicator(indicatorName);
|
||||
|
||||
if (pub != null) {
|
||||
final DashboardResultItem dItem = new DashboardResultItem(selectedItem.getYear(), siteName, indicatorName, pub.getValue(), pub.isPublished());
|
||||
final ArrayList<? extends SimpleYearIndex> result = new ArrayList<>();
|
||||
final Optional<Path> mainResults = resultStorage.getMainResults(dItem);
|
||||
if (mainResults.isPresent()) {
|
||||
result.addAll((List) readResult(mainResults.get()));
|
||||
}
|
||||
|
||||
final Optional<Path> otherResults = resultStorage.getAdditionalResults(dItem);
|
||||
if (otherResults.isPresent()) {
|
||||
result.addAll((List) readResult(otherResults.get()));
|
||||
}
|
||||
|
||||
buildTables(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the table view containing the result value list mapping their class.
|
||||
*
|
||||
* @param result
|
||||
* @throws IntrospectionException
|
||||
*/
|
||||
private void buildTables(List<? extends SimpleYearIndex> result) throws IntrospectionException {
|
||||
final List<SimpleLocationIndex> locValues = new ArrayList<>();
|
||||
final Iterator<? extends SimpleYearIndex> it = result.iterator();
|
||||
SimpleYearIndex current;
|
||||
while (it.hasNext()) {
|
||||
current = it.next();
|
||||
if (current instanceof SimpleLocationIndex) {
|
||||
locValues.add((SimpleLocationIndex) current);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
final TableView<SimpleYearIndex> table = buildSimpleTableView(FXCollections.observableList((List)result));
|
||||
getChildren().add(table);
|
||||
}
|
||||
|
||||
if (!locValues.isEmpty()) {
|
||||
final TableView<SimpleLocationIndex> table = buildLocationTableView(FXCollections.observableList(locValues));
|
||||
getChildren().add(table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link TableView} for tuples indexed by year.
|
||||
*
|
||||
* @param tuples
|
||||
* @return
|
||||
* @throws IntrospectionException
|
||||
* @see SimpleYearIndex
|
||||
*/
|
||||
private TableView<SimpleYearIndex> buildSimpleTableView(ObservableList<SimpleYearIndex> values) throws IntrospectionException {
|
||||
final TableView<SimpleYearIndex> table = new TableView<>();
|
||||
addCommonColumns(table);
|
||||
table.setItems(values);
|
||||
initResizePolicy(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure input table resize strategy, by setting preferred height
|
||||
* property to follow table item number.
|
||||
*
|
||||
* @param table The modified table.
|
||||
*/
|
||||
private void initResizePolicy(final TableView table) {
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
table.setMinHeight(50);
|
||||
table.setFixedCellSize(30);
|
||||
table.prefHeightProperty().bind(Bindings.createDoubleBinding(() -> table.getFixedCellSize() * table.getItems().size() + 30, table.getItems(), table.fixedCellSizeProperty()));
|
||||
}
|
||||
|
||||
private void addCommonColumns(final TableView<? extends SimpleYearIndex> table) {
|
||||
final TableColumn<SimpleYearIndex, String> titleColumn = new TableColumn<>("Nom");
|
||||
titleColumn.setEditable(false);
|
||||
titleColumn.setCellValueFactory(FXFullSpecificResultPane::getTitle);
|
||||
|
||||
final TableColumn<SimpleYearIndex, Number> valueColumn = new TableColumn<>("Valeur");
|
||||
valueColumn.setEditable(false);
|
||||
valueColumn.setCellFactory(RhomeoCore.ROUNDING_CELL_FACTORY);
|
||||
valueColumn.setCellValueFactory(FXFullSpecificResultPane::getValue);
|
||||
|
||||
table.getColumns().add((TableColumn)titleColumn);
|
||||
table.getColumns().add((TableColumn)valueColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link TableView} for tuples indexed by year and location.
|
||||
*
|
||||
* @param tuples
|
||||
* @return
|
||||
* @throws IntrospectionException
|
||||
* @see SimpleLocationIndex
|
||||
*/
|
||||
private TableView<SimpleLocationIndex> buildLocationTableView(ObservableList<SimpleLocationIndex> values) throws IntrospectionException {
|
||||
final TableView<SimpleLocationIndex> table = new TableView<>();
|
||||
addCommonColumns(table);
|
||||
|
||||
final TableColumn<SimpleLocationIndex, String> locColumn = new TableColumn<>();
|
||||
if (indicator.getProtocol().getName().equalsIgnoreCase("P04"))
|
||||
locColumn.setText("Habitat");
|
||||
else
|
||||
locColumn.setText("Point de suivi");
|
||||
locColumn.setCellValueFactory(FXFullSpecificResultPane::getLocation);
|
||||
|
||||
table.getColumns().add(1, locColumn);
|
||||
|
||||
table.setItems(values);
|
||||
initResizePolicy(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the results stored in the file which path is given as a parameter.
|
||||
*
|
||||
* The results are retrieved as a list of either instances of {@link SimpleLocationIndex}
|
||||
* or instances of {@link SimpleYearIndex}. The method returns an entry which
|
||||
* the value it the list of results, and the key is the instances' class.
|
||||
*
|
||||
* @param resultsPath
|
||||
* @return
|
||||
* @throws IOException
|
||||
* @throws IntrospectionException
|
||||
* @throws InstantiationException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
private static List<? extends SimpleYearIndex> readResult(final Path resultsPath) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
|
||||
try {
|
||||
return new CSVDecoder(resultsPath, SimpleLocationIndex.class).decode();
|
||||
} catch (Exception ex) {
|
||||
RhomeoCore.LOGGER.info("Impossible de lire en tant que résultat localisé. Essai de lecture en résultat non localisé.");
|
||||
return new CSVDecoder(resultsPath, SimpleYearIndex.class).decode();
|
||||
}
|
||||
}
|
||||
|
||||
private static ObservableValue<String> getTitle(final TableColumn.CellDataFeatures<SimpleYearIndex, String> cdf) {
|
||||
final SimpleYearIndex index = cdf.getValue();
|
||||
if (index == null)
|
||||
return null;
|
||||
return new SimpleStringProperty(index.getName());
|
||||
}
|
||||
|
||||
private static ObservableValue<Number> getValue(final TableColumn.CellDataFeatures<SimpleYearIndex, Number> cdf) {
|
||||
final SimpleYearIndex index = cdf.getValue();
|
||||
if (index == null)
|
||||
return null;
|
||||
return new SimpleObjectProperty<>(index.getValue());
|
||||
}
|
||||
|
||||
private static ObservableValue<String> getLocation(final TableColumn.CellDataFeatures<SimpleLocationIndex, String> cdf) {
|
||||
final SimpleLocationIndex index = cdf.getValue();
|
||||
if (index == null)
|
||||
return null;
|
||||
return new SimpleStringProperty(index.getLocation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
|
||||
/**
|
||||
* Panel for the initial dialog when opening the application.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
public class FXInitialDialogPane extends BorderPane {
|
||||
|
||||
public static final String DISPLAY_RULE_KEY = "display_disclaimer_on_startup";
|
||||
|
||||
@FXML
|
||||
private Label uiVersion;
|
||||
|
||||
@FXML
|
||||
private ImageView uiWarning;
|
||||
|
||||
@FXML
|
||||
private CheckBox displayOnStartup;
|
||||
|
||||
@FXML
|
||||
private Hyperlink uiEMail;
|
||||
|
||||
@FXML
|
||||
private Hyperlink uiHref;
|
||||
|
||||
@FXML
|
||||
private Button uiClose;
|
||||
|
||||
private final Preferences displayPref;
|
||||
|
||||
public FXInitialDialogPane() {
|
||||
Rhomeo.loadFXML(this, FXInitialDialogPane.class);
|
||||
displayPref = Preferences.userNodeForPackage(FXInitialDialogPane.class);
|
||||
displayOnStartup.setSelected(!displayPref.getBoolean(DISPLAY_RULE_KEY, true));
|
||||
displayOnStartup.selectedProperty().addListener((obs, oldValue, newValue) -> {
|
||||
displayPref.putBoolean(DISPLAY_RULE_KEY, !newValue);
|
||||
});
|
||||
|
||||
uiWarning.setImage(Rhomeo.ICON_WARNING_ORANGE);
|
||||
|
||||
uiEMail.setOnAction(evt -> Rhomeo.mail(uiEMail.getText()));
|
||||
uiHref.setOnAction(evt -> {
|
||||
try {
|
||||
Rhomeo.browse(new URL(uiHref.getText()));
|
||||
} catch (final MalformedURLException e) {
|
||||
// mimic task failure
|
||||
TaskManager.INSTANCE.submit("Ouverture d'un lien", () -> {
|
||||
if (e != null)
|
||||
throw e;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
final String version = Rhomeo.getVersion();
|
||||
if (version == null || version.isEmpty())
|
||||
uiVersion.setText("Version de développement");
|
||||
else
|
||||
uiVersion.setText("Version ".concat(version));
|
||||
|
||||
Platform.runLater(() -> uiClose.requestFocus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the dialog box when clicking on the accept button.
|
||||
*/
|
||||
@FXML
|
||||
private void accept() {
|
||||
final Stage stage = (Stage) this.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.geotoolkit.gui.javafx.util.ProgressMonitor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
|
||||
/**
|
||||
* Main application panel.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class FXMainPane extends BorderPane implements InternationalResource {
|
||||
|
||||
@FXML
|
||||
private MenuButton uiMenuButton;
|
||||
|
||||
@Autowired
|
||||
private FXRhomeoWizard wizard;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
public FXMainPane() {
|
||||
Rhomeo.loadFXML(this, FXMainPane.class);
|
||||
initProgressMonitor();
|
||||
|
||||
uiMenuButton.setGraphic(new ImageView(Rhomeo.ICON_BARS_WHITE));
|
||||
uiMenuButton.setFocusTraversable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes JavaFX components after spring components are done loading.
|
||||
*/
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
setCenter(wizard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the {@link ProgressMonitor} before the main menu button.
|
||||
* HACK : Add an item in progress monitor list, allowing to clear list of
|
||||
* tasks in error.
|
||||
*/
|
||||
private void initProgressMonitor() {
|
||||
final ProgressMonitor progressMonitor = new ProgressMonitor(TaskManager.INSTANCE);
|
||||
progressMonitor.getStylesheets().add(FXDashboardPane.class.getResource("/fr/cenra/rhomeo/ProgressMonitor.css").toString());
|
||||
|
||||
final HBox parent = ((HBox) uiMenuButton.getParent());
|
||||
parent.getChildren().add(parent.getChildren().size()-1, progressMonitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open preferences window.
|
||||
*/
|
||||
@FXML
|
||||
private void showPreferences() {
|
||||
final Stage prefStage = Rhomeo.newStage();
|
||||
prefStage.setTitle(getResourceString("preferences"));
|
||||
prefStage.initModality(Modality.APPLICATION_MODAL);
|
||||
final Scene scene = new Scene(session.createPrototype(FXRhomeoPreferencePane.class));
|
||||
prefStage.setScene(scene);
|
||||
prefStage.setMinWidth(530);
|
||||
prefStage.setMinHeight(230);
|
||||
prefStage.show();
|
||||
prefStage.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open references documents page.
|
||||
*/
|
||||
@FXML
|
||||
private void showRefDocuments() {
|
||||
final Stage prefStage = Rhomeo.newStage();
|
||||
prefStage.setTitle(getResourceString("ref-docs"));
|
||||
prefStage.initModality(Modality.APPLICATION_MODAL);
|
||||
final Scene scene = new Scene(session.createPrototype(FXRhomeoReferencePane.class));
|
||||
scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH);
|
||||
prefStage.setScene(scene);
|
||||
prefStage.setMinWidth(800);
|
||||
prefStage.setMinHeight(600);
|
||||
prefStage.show();
|
||||
prefStage.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the about dialog.
|
||||
*/
|
||||
@FXML
|
||||
private void showAboutDialog() {
|
||||
final Stage popup = Rhomeo.createWelcomePopup();
|
||||
popup.initOwner(this.getScene().getWindow());
|
||||
|
||||
popup.show();
|
||||
popup.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quit the application.
|
||||
*/
|
||||
@FXML
|
||||
private void quit() {
|
||||
session.close();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.Version;
|
||||
import fr.cenra.rhomeo.api.data.DataContext;
|
||||
import fr.cenra.rhomeo.api.data.Reference;
|
||||
import fr.cenra.rhomeo.api.data.ReferenceDescription;
|
||||
import fr.cenra.rhomeo.core.RhomeoRuntimeException;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.data.ReferenceManager;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.geotoolkit.gui.javafx.util.FXTableView;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Handle available versions for a reference.
|
||||
*
|
||||
* @param <R> the reference to display available versions.
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
public class FXProtocolReferencesPane<R extends Reference> extends BorderPane {
|
||||
@FXML
|
||||
private Label uiRefNameLbl;
|
||||
|
||||
private final FXTableView<ReferenceLineItem> referencesTable;
|
||||
|
||||
private final ReferenceDescription<R> description;
|
||||
|
||||
private static final String CSS_BUTTON_CLASS = "install-button";
|
||||
|
||||
public FXProtocolReferencesPane(final ReferenceDescription<R> description) throws IntrospectionException {
|
||||
this.description = description;
|
||||
|
||||
Rhomeo.loadFXML(this);
|
||||
uiRefNameLbl.setText("Référentiel "+ description.getTitle());
|
||||
|
||||
referencesTable = new FXTableView<>();
|
||||
referencesTable.setEditable(true);
|
||||
referencesTable.setMinHeight(90);
|
||||
referencesTable.setPrefHeight(getMinHeight());
|
||||
referencesTable.setMaxHeight(280);
|
||||
referencesTable.setFixedCellSize(25);
|
||||
|
||||
final TableColumn<ReferenceLineItem,String> availableCol = new TableColumn<>("Versions disponibles");
|
||||
availableCol.setCellValueFactory(value -> new SimpleStringProperty(String.valueOf(value.getValue().getVersion())));
|
||||
referencesTable.getColumns().add(availableCol);
|
||||
referencesTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
final TableColumn<ReferenceLineItem,Boolean> installedCol = new TableColumn<>("Installées");
|
||||
installedCol.setCellValueFactory(value -> value.getValue().installedProperty);
|
||||
installedCol.setCellFactory(param ->
|
||||
new TableCell<ReferenceLineItem, Boolean>() {
|
||||
@Override
|
||||
protected void updateItem(Boolean item, boolean empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
setAlignment(Pos.CENTER);
|
||||
if (item != null && !item) {
|
||||
final Button installBtn = new Button("Installer");
|
||||
installBtn.getStyleClass().add(CSS_BUTTON_CLASS);
|
||||
installBtn.setOnAction(event -> {
|
||||
final ReferenceManager<R> referenceManager;
|
||||
try {
|
||||
referenceManager = ReferenceManager.getOrCreate(description);
|
||||
} catch (IntrospectionException ex) {
|
||||
throw new RhomeoRuntimeException(ex);
|
||||
}
|
||||
|
||||
final Task installTask = referenceManager.install(((ReferenceLineItem)getTableRow().getItem()).getVersion());
|
||||
installTask.setOnSucceeded(success -> {
|
||||
((ReferenceLineItem)getTableRow().getItem()).setInstalled(true);
|
||||
});
|
||||
installBtn.disableProperty().bind(installTask.runningProperty());
|
||||
TaskManager.INSTANCE.submit(installTask);
|
||||
});
|
||||
setGraphic(installBtn);
|
||||
} else if (!empty && item != null){
|
||||
setGraphic(new ImageView(Rhomeo.ICON_CHECK_GREEN));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
referencesTable.getColumns().add(installedCol);
|
||||
|
||||
final TableColumn<ReferenceLineItem,Boolean> selectedCol = new TableColumn<>("Sélectionnées");
|
||||
selectedCol.setCellValueFactory(value -> value.getValue().selectedProperty);
|
||||
final ToggleGroup group = new ToggleGroup();
|
||||
selectedCol.setCellFactory(param -> new RadioButtonTableCell(group));
|
||||
selectedCol.setEditable(true);
|
||||
referencesTable.getColumns().add(selectedCol);
|
||||
|
||||
final DataContext dc = Session.getInstance().getDataContext();
|
||||
final ReferenceManager<R> referenceManager = ReferenceManager.getOrCreate(description);
|
||||
|
||||
final Set<Version> installedVersions = referenceManager.getInstalledVersions();
|
||||
final Set<Version> distantVersions = referenceManager.getDistantVersions();
|
||||
final HashSet<Version> allVersions = new HashSet<>(installedVersions);
|
||||
allVersions.addAll(distantVersions);
|
||||
|
||||
final Version selectedVersion = dc.getReferences().get(description.getReferenceType());
|
||||
final ObservableList<ReferenceLineItem> items = FXCollections.observableArrayList();
|
||||
for (final Version ver : allVersions) {
|
||||
items.add(new ReferenceLineItem(ver, installedVersions.contains(ver), ver.equals(selectedVersion)));
|
||||
}
|
||||
Collections.sort(items);
|
||||
referencesTable.setItems(FXCollections.observableList(items));
|
||||
referencesTable.setPrefHeight((items.size() + 1) * referencesTable.getFixedCellSize());
|
||||
|
||||
setCenter(referencesTable);
|
||||
}
|
||||
|
||||
public ObservableList<ReferenceLineItem> getItems() {
|
||||
return referencesTable.itemsProperty().get();
|
||||
}
|
||||
|
||||
public Class<R> getReferenceClass() {
|
||||
return description.getReferenceType();
|
||||
}
|
||||
|
||||
private class RadioButtonTableCell extends TableCell<ReferenceLineItem,Boolean> {
|
||||
private RadioButton radioButton;
|
||||
private BooleanProperty selProperty;
|
||||
|
||||
/**
|
||||
* Cell containing a radio button in the given group.
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
public RadioButtonTableCell(final ToggleGroup group) {
|
||||
radioButton = new RadioButton();
|
||||
if (group != null) {
|
||||
radioButton.setToggleGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Boolean item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
// First remove potential binding
|
||||
if (selProperty != null) {
|
||||
radioButton.selectedProperty().unbindBidirectional(selProperty);
|
||||
}
|
||||
setGraphic(radioButton);
|
||||
setAlignment(Pos.CENTER);
|
||||
radioButton.visibleProperty().bind(((ReferenceLineItem)getTableRow().getItem()).installedProperty);
|
||||
|
||||
final ObservableValue<?> obsValue = getTableColumn().getCellObservableValue(getIndex());
|
||||
if (obsValue instanceof BooleanProperty) {
|
||||
selProperty = (BooleanProperty)obsValue;
|
||||
// ensures the radio button selected value will follow the item value
|
||||
radioButton.selectedProperty().bindBidirectional(selProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReferenceLineItem implements Comparable<ReferenceLineItem> {
|
||||
private Version version;
|
||||
private BooleanProperty installedProperty;
|
||||
private BooleanProperty selectedProperty;
|
||||
|
||||
public ReferenceLineItem() {
|
||||
}
|
||||
|
||||
public ReferenceLineItem(Version version, boolean installed, boolean selected) {
|
||||
this.version = version;
|
||||
this.installedProperty = new SimpleBooleanProperty(installed);
|
||||
this.selectedProperty = new SimpleBooleanProperty(selected);
|
||||
}
|
||||
|
||||
public Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Version version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public boolean isInstalled() {
|
||||
return installedProperty.get();
|
||||
}
|
||||
|
||||
public void setInstalled(boolean installed) {
|
||||
this.installedProperty.set(installed);
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selectedProperty.get();
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.selectedProperty.set(selected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ReferenceLineItem o) {
|
||||
if (version == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (o == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return version.compareTo(o.getVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.api.preferences.PreferenceGroup;
|
||||
import fr.cenra.rhomeo.api.ui.PreferencePane;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Preference editor. This panel aggregate editors for all available
|
||||
* {@link PreferenceGroup}. A discovery system allows to find the most
|
||||
* appropriate editor for a given group. If we cannot find any, we use a default
|
||||
* editor.
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys)
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXRhomeoPreferencePane")
|
||||
@Scope("prototype")
|
||||
public class FXRhomeoPreferencePane extends BorderPane implements InternationalResource {
|
||||
|
||||
@Autowired
|
||||
private Map<String, PreferenceGroup> preferenceGroups;
|
||||
|
||||
@Autowired
|
||||
private List<PreferencePane> availableEditors;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
private final String css;
|
||||
|
||||
private final List<PreferencePane> preferencePanes = new ArrayList<>();
|
||||
|
||||
public FXRhomeoPreferencePane() {
|
||||
css = FXDefaultPreferenceGroupPane.class.getResource(FXRhomeoPreferencePane.class.getSimpleName() + ".css").toExternalForm();
|
||||
getStylesheets().add(css);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
|
||||
// Creating one tab for each preference group.
|
||||
final TabPane tabPane = new TabPane();
|
||||
final List<PreferenceGroup> prefGroups = new ArrayList<>(preferenceGroups.values());
|
||||
Collections.sort(prefGroups);
|
||||
|
||||
PreferencePane prefEditor;
|
||||
for (final PreferenceGroup preferenceGroup : prefGroups) {
|
||||
prefEditor = null;
|
||||
for (final PreferencePane editor : availableEditors) {
|
||||
if (editor.getPreferenceGroup().equals(preferenceGroup)) {
|
||||
prefEditor = editor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no specific editor has been found, a default one is used.
|
||||
if (prefEditor == null) {
|
||||
if (preferenceGroup.getKeys().isEmpty()) {
|
||||
// A group without keys does not need to be displayed
|
||||
continue;
|
||||
}
|
||||
|
||||
final FXDefaultPreferenceGroupPane preferencePane = new FXDefaultPreferenceGroupPane();
|
||||
preferencePane.setPreferenceGroup(preferenceGroup);
|
||||
prefEditor = preferencePane;
|
||||
}
|
||||
|
||||
preferencePanes.add(prefEditor);
|
||||
final Tab tab = new Tab(preferenceGroup.getLabel(), prefEditor.getEditor());
|
||||
tab.setTooltip(new Tooltip(preferenceGroup.getDescription()));
|
||||
tabPane.getTabs().add(tab);
|
||||
}
|
||||
setCenter(tabPane);
|
||||
|
||||
// Creating preference controls
|
||||
final Button ok = new Button(getResourceString("validate"));
|
||||
ok.setOnAction(e -> {
|
||||
for (final PreferencePane preferencePane : preferencePanes) {
|
||||
preferencePane.save();
|
||||
}
|
||||
getScene().getWindow().hide();
|
||||
});
|
||||
|
||||
final Button cancel = new Button(getResourceString("cancel"));
|
||||
cancel.setOnAction(e -> getScene().getWindow().hide());
|
||||
|
||||
final HBox hBox = new HBox(cancel, ok);
|
||||
hBox.getStylesheets().add(css);
|
||||
hBox.getStyleClass().add("hBox");
|
||||
setBottom(hBox);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
@Component("fr.cenra.rhomeo.fx.FXRhomeoReferencePane")
|
||||
@Scope("prototype")
|
||||
public class FXRhomeoReferencePane extends BorderPane {
|
||||
|
||||
@FXML
|
||||
private GridPane uiProtocolsBtnGrid;
|
||||
|
||||
@FXML
|
||||
private GridPane uiModelsBtnGrid;
|
||||
|
||||
@Autowired
|
||||
private List<Protocol> protocols;
|
||||
|
||||
public FXRhomeoReferencePane() {
|
||||
Rhomeo.loadFXML(this, FXRhomeoReferencePane.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes JavaFX components after spring components are done loading.
|
||||
*/
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
Collections.sort(protocols);
|
||||
|
||||
int modelIndex = 0;
|
||||
for(int i=0; i<protocols.size(); i++){
|
||||
final Protocol p = protocols.get(i);
|
||||
uiProtocolsBtnGrid.add(Rhomeo.createProtocolDocumentButton(p), i%2, i/2);
|
||||
if (!(p.getName().equals("P04") || p.getName().equals("P08") || p.getName().equals("P09")))
|
||||
uiModelsBtnGrid.add(Rhomeo.createModelButton(p), modelIndex%2, modelIndex++/2);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void close() {
|
||||
final Stage stage = (Stage) this.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,526 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.Version;
|
||||
import fr.cenra.rhomeo.api.data.DataContext;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.ReferenceDescription;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.RhomeoRuntimeException;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.core.data.DataContextManager;
|
||||
import fr.cenra.rhomeo.core.data.ReferenceManager;
|
||||
import fr.cenra.rhomeo.fx.edition.FXDatasetPane;
|
||||
|
||||
import java.beans.IntrospectionException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.BackgroundImage;
|
||||
import javafx.scene.layout.BackgroundPosition;
|
||||
import javafx.scene.layout.BackgroundRepeat;
|
||||
import javafx.scene.layout.BackgroundSize;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.StageStyle;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
* Menu banner for selected protocol page.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class FXRhomeoWizard extends VBox {
|
||||
|
||||
@FXML private Label uiSelectedSiteLbl;
|
||||
@FXML private Label uiSelectedProtocolLbl;
|
||||
|
||||
@FXML private VBox uiHeader;
|
||||
@FXML private BorderPane uiBody;
|
||||
|
||||
@FXML private HBox uiProtocolChosenBox;
|
||||
@FXML private GridPane uiProtocolStepsGrid;
|
||||
|
||||
@FXML private Button uiRefUsedBtn;
|
||||
@FXML private Button uiStopProcessBtn;
|
||||
|
||||
@FXML private Button uiStep1Btn;
|
||||
@FXML private Button uiStep2Btn;
|
||||
@FXML private Button uiStep3Btn;
|
||||
@FXML private Button uiStep4Btn;
|
||||
|
||||
@Autowired
|
||||
private DataContextManager contextManager;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
List<ProtocolEditorSpi> editorSpis;
|
||||
|
||||
private DataContext context;
|
||||
private Site site;
|
||||
private Protocol protocol;
|
||||
private ProtocolEditorSpi editorSpi;
|
||||
|
||||
private final Map<WorkflowStep, Button> stepBtns;
|
||||
|
||||
private final String stopBtnOriginalText;
|
||||
|
||||
public FXRhomeoWizard() {
|
||||
Rhomeo.loadFXML(this, FXRhomeoWizard.class);
|
||||
// Index buttons by step
|
||||
final Map<WorkflowStep, Button> tmpMap = new HashMap<>();
|
||||
tmpMap.put(WorkflowStep.DATASET, uiStep1Btn);
|
||||
tmpMap.put(WorkflowStep.PROCESS, uiStep2Btn);
|
||||
tmpMap.put(WorkflowStep.RESULT, uiStep3Btn);
|
||||
tmpMap.put(WorkflowStep.FINALIZATION, uiStep4Btn);
|
||||
stepBtns = Collections.unmodifiableMap(tmpMap);
|
||||
for (final Map.Entry<WorkflowStep, Button> entry : stepBtns.entrySet()) {
|
||||
entry.getValue().setOnAction(evt -> session.requestWorkflowStep(entry.getKey()));
|
||||
}
|
||||
|
||||
uiHeader.managedProperty().bind(uiHeader.visibleProperty());
|
||||
|
||||
final ImageView im = new ImageView(Rhomeo.ICON_EXCHANGE_GRAY);
|
||||
im.setRotate(90);
|
||||
uiRefUsedBtn.setGraphic(im);
|
||||
uiStopProcessBtn.setGraphic(new ImageView(Rhomeo.ICON_STOP_RED));
|
||||
// HACK #77 : User want a change on the button which stops process on a
|
||||
// specific step, so we keep reference to its original text now.
|
||||
stopBtnOriginalText = uiStopProcessBtn.getText();
|
||||
|
||||
// Prepare display of arrows linking step buttons. Each arrow state is bound
|
||||
// to the step button following it.
|
||||
final ImageView imgArrow1 = new ImageView(Rhomeo.ICON_LONG_ARROW_RIGHT);
|
||||
imgArrow1.setDisable(true);
|
||||
imgArrow1.visibleProperty().bind(uiStep2Btn.visibleProperty());
|
||||
imgArrow1.managedProperty().bind(imgArrow1.visibleProperty());
|
||||
final ImageView imgArrow2 = new ImageView(Rhomeo.ICON_LONG_ARROW_RIGHT);
|
||||
imgArrow2.setDisable(true);
|
||||
imgArrow2.visibleProperty().bind(uiStep3Btn.visibleProperty());
|
||||
imgArrow2.managedProperty().bind(imgArrow2.visibleProperty());
|
||||
final ImageView imgArrow3 = new ImageView(Rhomeo.ICON_LONG_ARROW_RIGHT);
|
||||
imgArrow3.setDisable(true);
|
||||
imgArrow3.visibleProperty().bind(uiStep4Btn.visibleProperty());
|
||||
imgArrow3.managedProperty().bind(imgArrow3.visibleProperty());
|
||||
// Limit height of arrow image, which is really too big (lot of empty space).
|
||||
final Rectangle2D arrowDisplay = new Rectangle2D(
|
||||
0, Rhomeo.ICON_LONG_ARROW_RIGHT.getHeight() / 4,
|
||||
Rhomeo.ICON_LONG_ARROW_RIGHT.getWidth(), Rhomeo.ICON_LONG_ARROW_RIGHT.getHeight() / 2);
|
||||
imgArrow1.setViewport(arrowDisplay);
|
||||
imgArrow2.setViewport(arrowDisplay);
|
||||
imgArrow3.setViewport(arrowDisplay);
|
||||
|
||||
uiProtocolStepsGrid.add(imgArrow1, 2, 0);
|
||||
uiProtocolStepsGrid.add(imgArrow2, 4, 0);
|
||||
uiProtocolStepsGrid.add(imgArrow3, 6, 0);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
session.workflowStepProperty().addListener(this::stepChanged);
|
||||
unloadDataContext();
|
||||
}
|
||||
|
||||
private void loadDataContext() {
|
||||
this.context = session.getDataContext();
|
||||
this.site = context.getSite();
|
||||
this.protocol = context.getProtocol();
|
||||
initializeReferences();
|
||||
for (final ProtocolEditorSpi spi : editorSpis) {
|
||||
if (spi.getProtocol() == protocol) {
|
||||
editorSpi =spi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate step buttons for workflow parts marked as non available
|
||||
// by the input SPI.
|
||||
if (editorSpi != null && !editorSpi.getStepsToIgnore().isEmpty()) {
|
||||
for (final WorkflowStep step : editorSpi.getStepsToIgnore()) {
|
||||
final Button btn = stepBtns.get(step);
|
||||
if (btn != null) {
|
||||
btn.setVisible(false);
|
||||
btn.setManaged(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uiSelectedSiteLbl.setText(site.getName());
|
||||
uiSelectedProtocolLbl.setText(protocol.getName());
|
||||
uiProtocolChosenBox.getChildren().add(uiSelectedProtocolLbl);
|
||||
uiRefUsedBtn.setDisable(protocol.getReferenceTypes().isEmpty());
|
||||
|
||||
final List<Indicator> indicators = session.getIndicators();
|
||||
indicators.stream()
|
||||
.filter(indicator -> indicator.getProtocol() == protocol)
|
||||
.sorted()
|
||||
.forEach(indicator -> {
|
||||
final Label lbl = new Label(indicator.getName());
|
||||
lbl.getStyleClass().add(Rhomeo.CSS_INDICATOR_LABEL);
|
||||
uiProtocolChosenBox.getChildren().add(lbl);
|
||||
});
|
||||
|
||||
final Button pdfBtn = Rhomeo.createProtocolDocumentButton(protocol);
|
||||
pdfBtn.setText(null);
|
||||
pdfBtn.setPrefHeight(28);
|
||||
pdfBtn.setMaxHeight(USE_PREF_SIZE);
|
||||
pdfBtn.setMinHeight(USE_PREF_SIZE);
|
||||
uiProtocolChosenBox.getChildren().add(pdfBtn);
|
||||
uiHeader.setVisible(true);
|
||||
|
||||
// Generate protocol background
|
||||
final String bgPath = "/fr/cenra/rhomeo/images/" + protocol.getName().toLowerCase() + ".png";
|
||||
if (FXRhomeoWizard.class.getResource(bgPath) != null) {
|
||||
try (final InputStream stream = FXRhomeoWizard.class.getResourceAsStream(bgPath)) {
|
||||
final Background backgroundProtocol = new Background(new BackgroundImage(
|
||||
new Image(stream),
|
||||
BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT,
|
||||
new BackgroundPosition(Side.LEFT, 0, true, Side.BOTTOM, 0, true), BackgroundSize.DEFAULT));
|
||||
setBackground(backgroundProtocol);
|
||||
} catch (IOException e) {
|
||||
Rhomeo.LOGGER.log(Level.FINE, "Cannot load a background image !", e);
|
||||
}
|
||||
}
|
||||
|
||||
// #59 Reset results if user change reference version.
|
||||
session.getDataContext().getReferences().addListener((Observable obs) -> {
|
||||
if (session.getWorkflowStep() == WorkflowStep.RESULT || session.getWorkflowStep() == WorkflowStep.FINALIZATION)
|
||||
session.requestWorkflowStep(WorkflowStep.PROCESS);
|
||||
|
||||
session.setResults(null);
|
||||
stepBtns.get(WorkflowStep.RESULT).setDisable(true);
|
||||
stepBtns.get(WorkflowStep.FINALIZATION).setDisable(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void unloadDataContext() {
|
||||
final Site tmpSite = site;
|
||||
context = null;
|
||||
site = null;
|
||||
protocol = null;
|
||||
uiProtocolChosenBox.getChildren().clear();
|
||||
uiHeader.setVisible(false);
|
||||
editorSpi = null;
|
||||
|
||||
for (final Button stepBtn : stepBtns.values()) {
|
||||
stepBtn.setDisable(true);
|
||||
stepBtn.setVisible(true);
|
||||
stepBtn.getStyleClass().removeAll(Rhomeo.CSS_CURRENT_STEP);
|
||||
}
|
||||
|
||||
final FXDashboardPane dashboard = session.getBean(FXDashboardPane.class);
|
||||
dashboard.siteProperty().set(tmpSite);
|
||||
|
||||
// TODO : set site
|
||||
uiBody.setCenter(dashboard);
|
||||
setBackground(Background.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* On {@link FXRhomeoWizard#uiRefUsedBtn} action.
|
||||
*/
|
||||
@FXML
|
||||
private void showUsedRef() {
|
||||
final DataContext dc = session.getDataContext();
|
||||
if (dc == null) {
|
||||
return;
|
||||
}
|
||||
final Protocol protocol = dc.getProtocol();
|
||||
if (protocol == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Stage stage = Rhomeo.newStage();
|
||||
stage.setTitle("Choix des référentiels");
|
||||
final VBox vbox = new VBox(10);
|
||||
vbox.getStyleClass().add("white-box");
|
||||
|
||||
protocol.getReferenceTypes().forEach(description -> {
|
||||
try {
|
||||
final FXProtocolReferencesPane protocolReferences = new FXProtocolReferencesPane(description);
|
||||
vbox.getChildren().add(protocolReferences);
|
||||
VBox.setVgrow(protocolReferences, Priority.SOMETIMES);
|
||||
} catch (IntrospectionException ex) {
|
||||
throw new RhomeoRuntimeException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
final Button validBtn = new Button("Valider");
|
||||
validBtn.getStyleClass().add("white-button");
|
||||
validBtn.setOnAction(event -> {
|
||||
// #59 If user already computed indices, changing reference versions
|
||||
// would delete his results, so we warn him.
|
||||
if (!stepBtns.get(WorkflowStep.RESULT).isDisabled()) {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Si vous changez de version de référentiel maintenant, vos résultats seront supprimés. Continuer ?", ButtonType.NO, ButtonType.YES);
|
||||
alert.setResizable(true);
|
||||
if (ButtonType.NO.equals(alert.showAndWait().orElse(ButtonType.NO)))
|
||||
return;
|
||||
}
|
||||
vbox.getChildren().stream()
|
||||
.filter(child -> child instanceof FXProtocolReferencesPane)
|
||||
.map(child -> (FXProtocolReferencesPane)child)
|
||||
.forEach(protoRefs ->
|
||||
protoRefs.getItems().stream()
|
||||
.filter(o -> o instanceof FXProtocolReferencesPane.ReferenceLineItem && ((FXProtocolReferencesPane.ReferenceLineItem) o).isSelected())
|
||||
.forEach(o -> dc.getReferences().put(protoRefs.getReferenceClass(), ((FXProtocolReferencesPane.ReferenceLineItem) o).getVersion()))
|
||||
);
|
||||
try {
|
||||
contextManager.writeDataContext(dc);
|
||||
} catch (IOException ex) {
|
||||
throw new RhomeoRuntimeException(ex);
|
||||
} finally {
|
||||
stage.close();
|
||||
}
|
||||
});
|
||||
|
||||
final Button cancelBtn = new Button("Annuler");
|
||||
cancelBtn.setCancelButton(true);
|
||||
cancelBtn.setOnAction(evt -> stage.close());
|
||||
cancelBtn.getStyleClass().add("white-button");
|
||||
|
||||
final HBox validHbox = new HBox(5, cancelBtn, validBtn);
|
||||
validHbox.setAlignment(Pos.CENTER_RIGHT);
|
||||
vbox.getChildren().add(validHbox);
|
||||
|
||||
final Scene scene = new Scene(vbox);
|
||||
scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH);
|
||||
scene.getStylesheets().add("/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.css");
|
||||
stage.setScene(scene);
|
||||
stage.setMinHeight(protocol.getReferenceTypes().size() * 130 + 80);
|
||||
stage.setHeight(stage.getMinHeight());
|
||||
stage.setResizable(true);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
stage.sizeToScene();
|
||||
stage.showAndWait();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void stopProcess() {
|
||||
// HACK #78 : Before stopping session, we check dataset state, for the
|
||||
// user to be able to save its data before stopping.
|
||||
final Node center = uiBody.getCenter();
|
||||
if (center instanceof FXDatasetPane) {
|
||||
Task<Boolean> saveTask = ((FXDatasetPane) center).checkSaveChange().orElse(null);
|
||||
if (saveTask != null)
|
||||
saveTask.setOnSucceeded(evt -> {
|
||||
if (Boolean.TRUE.equals(evt.getSource().getValue()))
|
||||
Platform.runLater(() -> session.requestWorkflowStep(null));
|
||||
});
|
||||
else
|
||||
session.requestWorkflowStep(null);
|
||||
} else
|
||||
session.requestWorkflowStep(null);
|
||||
}
|
||||
|
||||
private void stepChanged(final ObservableValue<? extends WorkflowStep> obs, final WorkflowStep oldStep, final WorkflowStep newStep) {
|
||||
// HACK #77 : User want a change on stop button look and feel at final step.
|
||||
// We reset its state before doing anything else.
|
||||
uiStopProcessBtn.setText(stopBtnOriginalText);
|
||||
uiStopProcessBtn.setGraphic(new ImageView(Rhomeo.ICON_STOP_RED));
|
||||
|
||||
if (newStep == null) {
|
||||
// Clear and hide edition display. Go back to the dashboard.
|
||||
unloadDataContext();
|
||||
|
||||
} else {
|
||||
|
||||
if (oldStep == null) {
|
||||
// New edition started
|
||||
loadDataContext();
|
||||
} else {
|
||||
Button btn = stepBtns.get(oldStep);
|
||||
if (btn != null && btn.getStyleClass().contains(Rhomeo.CSS_CURRENT_STEP)) {
|
||||
btn.getStyleClass().remove(Rhomeo.CSS_CURRENT_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
// Init current step UI
|
||||
Button btn = stepBtns.get(newStep);
|
||||
btn.setDisable(false);
|
||||
btn.getStyleClass().add(Rhomeo.CSS_CURRENT_STEP);
|
||||
|
||||
Node body = createNode(newStep);
|
||||
if (body == null) {
|
||||
switch (newStep) {
|
||||
case DATASET:
|
||||
body = session.getBean(FXDatasetPane.class);
|
||||
break;
|
||||
|
||||
case PROCESS:
|
||||
body = session.getBean(FXDefaultProcessingPane.class);
|
||||
break;
|
||||
|
||||
case RESULT:
|
||||
body = session.getBean(FXDefaultResultPane.class);
|
||||
break;
|
||||
|
||||
case FINALIZATION:
|
||||
body = session.getBean(FXFinalizationPane.class);
|
||||
// HACK #77 : Change look and feel of stop button.
|
||||
uiStopProcessBtn.setText("Retour au tableau de bord");
|
||||
uiStopProcessBtn.setGraphic(new ImageView(Rhomeo.ICON));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uiBody.setCenter(body);
|
||||
}
|
||||
}
|
||||
|
||||
private Node createNode(final WorkflowStep step) {
|
||||
final Node node;
|
||||
if (editorSpi != null) {
|
||||
node = editorSpi.getEditor(step);
|
||||
} else {
|
||||
node = null;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize references lists for further use.
|
||||
* Note : For each reference of the current protocol, we update list of
|
||||
* installed and available versions. If none is installed, we propose to the
|
||||
* user to install the most up to date.
|
||||
*
|
||||
* @throws IntrospectionException
|
||||
*/
|
||||
private void initializeReferences() {
|
||||
if (protocol == null || protocol.getReferenceTypes().isEmpty())
|
||||
return;
|
||||
|
||||
for (final ReferenceDescription description : protocol.getReferenceTypes()) {
|
||||
try {
|
||||
final ReferenceManager referenceManager = ReferenceManager.getOrCreate(description);
|
||||
final Task<ReferenceManager> task = referenceManager.refresh();
|
||||
task.setOnSucceeded(event -> {
|
||||
// If we haven't any version installed for this reference, then we propose to install the newest.
|
||||
if (referenceManager.getInstalledVersions().isEmpty() && !referenceManager.getDistantVersions().isEmpty()) {
|
||||
Platform.runLater(() -> {
|
||||
final StringBuilder builder = new StringBuilder("Aucune version du référentiel suivant n'est installée :")
|
||||
.append(System.lineSeparator())
|
||||
.append(description.getTitle())
|
||||
.append(System.lineSeparator())
|
||||
.append(System.lineSeparator())
|
||||
.append("Voulez-vous télécharger la plus récente ?");
|
||||
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, builder.toString(), ButtonType.NO, ButtonType.YES);
|
||||
alert.setHeaderText("Référentiel non installé");
|
||||
alert.setResizable(true);
|
||||
// If agreed, we download newest version. When it's done, we set it as used version.
|
||||
if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) {
|
||||
final Version max = Collections.max((Set<Version>) referenceManager.getDistantVersions());
|
||||
TaskManager.INSTANCE.submit(referenceManager.install(max)).setOnSucceeded(evt -> {
|
||||
final DataContext dc = session.getDataContext();
|
||||
if (dc != null)
|
||||
Platform.runLater(() -> dc.getReferences().put(description.getReferenceType(), max));
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
final Version selectedVersion = context.getReferences().get(description.getReferenceType());
|
||||
// If at least one local version exists for this reference and nothing has been selected, then select the biggest one.
|
||||
// This way, for each reference a version should be selected.
|
||||
if (selectedVersion == null && !referenceManager.getInstalledVersions().isEmpty()) {
|
||||
final Version max = Collections.max((Set<Version>) referenceManager.getInstalledVersions());
|
||||
context.getReferences().put(description.getReferenceType(), max);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(event -> {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot update reference versions !", event.getSource().getException());
|
||||
final Version selectedVersion = context.getReferences().get(description.getReferenceType());
|
||||
// If at least one local version exists for this reference and nothing has been selected, then select the biggest one.
|
||||
// This way, for each reference a version should be selected.
|
||||
if (selectedVersion == null && !referenceManager.getInstalledVersions().isEmpty()) {
|
||||
final Version max = Collections.max((Set<Version>) referenceManager.getInstalledVersions());
|
||||
context.getReferences().put(description.getReferenceType(), max);
|
||||
}
|
||||
});
|
||||
|
||||
TaskManager.INSTANCE.submit(task);
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "Cannot initialize reference !", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
/**
|
||||
* Created by cedr on 06/04/16.
|
||||
*/
|
||||
public class FXSplashScreen extends GridPane {
|
||||
@FXML public GridPane uiLoadingPane;
|
||||
@FXML public Label uiProgressLabel;
|
||||
@FXML public ProgressBar uiProgressBar;
|
||||
@FXML public Button uiCancel;
|
||||
|
||||
@FXML
|
||||
public void closeApp() {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
|
||||
import java.util.HashMap;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.ScatterChart;
|
||||
import javafx.scene.chart.XYChart;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.TablePosition;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.springframework.stereotype.Component;
|
||||
import fr.cenra.rhomeo.api.ui.DashboardMenuItem;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class GraphDashboardItem implements DashboardMenuItem {
|
||||
|
||||
private static final String GRAPH_TITLE = "Évolution des valeurs indicatrices";
|
||||
|
||||
@Override
|
||||
public Optional<MenuItem> createItem(final TableView<IndicatorValuesByYearItem> target) {
|
||||
final MenuItem graphItem = new MenuItem("Graphique d'évolution");
|
||||
graphItem.setOnAction(evt -> displayGraph(target));
|
||||
graphItem.visibleProperty().bind(Bindings.size(target.getSelectionModel().getSelectedIndices()).greaterThan(1));
|
||||
return Optional.of(graphItem);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the evolution graph in a modal window.
|
||||
*/
|
||||
private static void displayGraph(final TableView<IndicatorValuesByYearItem> source) {
|
||||
if (source.getScene() == null || source.getScene().getWindow() == null)
|
||||
return;
|
||||
final Stage chartStage = new Stage(StageStyle.UTILITY);
|
||||
chartStage.initModality(Modality.WINDOW_MODAL);
|
||||
chartStage.initOwner(source.getScene().getWindow());
|
||||
chartStage.setTitle(GRAPH_TITLE);
|
||||
chartStage.setScene(new Scene(createChart(source)));
|
||||
|
||||
chartStage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a point chart to display selected indicator values from dashboard.
|
||||
* @return A chart to display selected values.
|
||||
*/
|
||||
private static XYChart<Number, Number> createChart(final TableView<IndicatorValuesByYearItem> source) {
|
||||
final Map<String, XYChart.Series<Number, Number>> series = new HashMap<>();
|
||||
final ObservableList<TablePosition> selectedCells = source.getSelectionModel().getSelectedCells();
|
||||
Double cellData;
|
||||
int year;
|
||||
IndicatorSiteColumn col;
|
||||
int minYear = Integer.MAX_VALUE;
|
||||
int maxYear = Integer.MIN_VALUE;
|
||||
double minValue = Double.MAX_VALUE;
|
||||
double maxValue = Double.MIN_VALUE;
|
||||
for (final TablePosition pos : selectedCells) {
|
||||
if (pos.getTableColumn() instanceof IndicatorSiteColumn) {
|
||||
col = (IndicatorSiteColumn) pos.getTableColumn();
|
||||
cellData = col.getCellData(pos.getRow());
|
||||
if (cellData != null && Double.isFinite(cellData)) {
|
||||
year = source.getItems().get(pos.getRow()).getYear();
|
||||
minYear = StrictMath.min(year, minYear);
|
||||
maxYear = StrictMath.max(year, maxYear);
|
||||
minValue = StrictMath.min(cellData, minValue);
|
||||
maxValue = StrictMath.max(cellData, maxValue);
|
||||
|
||||
series.computeIfAbsent(col.getIndicator().getTitle(), (title) -> {
|
||||
final XYChart.Series<Number, Number> s = new XYChart.Series<>();
|
||||
s.setName(title);
|
||||
return s;
|
||||
}).getData().add(new XYChart.Data<>(year, cellData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final XYChart<Number, Number> chart = new ScatterChart<>(
|
||||
new NumberAxis("Année", minYear - 1, maxYear + 1, 5),
|
||||
new NumberAxis("Valeur", minValue - 0.5, maxValue + 0.5, 0.5)
|
||||
);
|
||||
|
||||
chart.setTitle(GRAPH_TITLE);
|
||||
chart.getData().addAll(series.values());
|
||||
chart.getData().sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
|
||||
chart.setPadding(new Insets(10));
|
||||
return chart;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.Version;
|
||||
import fr.cenra.rhomeo.api.data.ReferenceDescription;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.DashboardResultItem;
|
||||
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.result.ResultStorage;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Function;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
|
||||
/**
|
||||
* Table column for indicators.
|
||||
* Column present in the {@link FXDashboardResultsTable}.
|
||||
*
|
||||
* @author Cédric Briançon (Geomatys)
|
||||
*/
|
||||
public class IndicatorSiteColumn extends TableColumn<IndicatorValuesByYearItem, Double> implements Comparable<IndicatorSiteColumn> {
|
||||
|
||||
private final Indicator indicator;
|
||||
|
||||
private final ChangeListener<ObservableList> itemPropertyChanged;
|
||||
|
||||
private final InvalidationListener referenceCheckTrigger;
|
||||
|
||||
private final ObjectProperty<Task> checkTaskProperty;
|
||||
|
||||
/**
|
||||
* HACK : Cannot find any property to put graphic right to text in a column
|
||||
* header, so we put a label in the header to contain both.
|
||||
*/
|
||||
private final Label header;
|
||||
|
||||
public IndicatorSiteColumn(final Indicator indicator) {
|
||||
this.indicator = indicator;
|
||||
header = new Label(indicator.getName());
|
||||
header.setContentDisplay(ContentDisplay.RIGHT);
|
||||
setGraphic(header);
|
||||
|
||||
itemPropertyChanged = this::itemPropertyChanged;
|
||||
referenceCheckTrigger = obs -> checkReferences();
|
||||
|
||||
tableViewProperty().addListener(this::tableChanged);
|
||||
tableViewProperty().addListener(referenceCheckTrigger);
|
||||
|
||||
checkTaskProperty = new SimpleObjectProperty<>();
|
||||
checkTaskProperty.addListener((obs, oldVal, newVal) -> {
|
||||
if (oldVal != null && oldVal.isRunning())
|
||||
oldVal.cancel();
|
||||
if (newVal != null && !newVal.isRunning())
|
||||
TaskManager.INSTANCE.submit(newVal);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public Indicator getIndicator() {
|
||||
return indicator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IndicatorSiteColumn o) {
|
||||
if (o == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (indicator == null) {
|
||||
return 1;
|
||||
}
|
||||
return indicator.compareTo(o.getIndicator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update listeners when parent table changes.
|
||||
* @param obs
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
*/
|
||||
private void tableChanged(final ObservableValue<? extends TableView> obs, TableView oldVal, TableView newVal) {
|
||||
if (oldVal != null) {
|
||||
oldVal.itemsProperty().removeListener(itemPropertyChanged);
|
||||
oldVal.itemsProperty().removeListener(referenceCheckTrigger);
|
||||
}
|
||||
|
||||
if (newVal != null) {
|
||||
newVal.itemsProperty().addListener(itemPropertyChanged);
|
||||
newVal.itemsProperty().addListener(referenceCheckTrigger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update listeners when parent table items change.
|
||||
* @param obs
|
||||
* @param oldVal
|
||||
* @param newVal
|
||||
*/
|
||||
private void itemPropertyChanged(final ObservableValue<? extends ObservableList> obs, ObservableList oldVal, ObservableList newVal) {
|
||||
if (oldVal != null)
|
||||
oldVal.removeListener(referenceCheckTrigger);
|
||||
|
||||
if (newVal != null)
|
||||
newVal.addListener(referenceCheckTrigger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse column indices to find all reference versions used for their computing.
|
||||
* If we find multiple versions for a same reference, we will display a little
|
||||
* warning to the user.
|
||||
*/
|
||||
private void checkReferences() {
|
||||
final ObservableList<IndicatorValuesByYearItem> items;
|
||||
if (getTableView() == null || (items = getTableView().getItems()) == null || items.isEmpty())
|
||||
return;
|
||||
|
||||
final Site site;
|
||||
if (getTableView() instanceof FXDashboardResultsTable)
|
||||
site = ((FXDashboardResultsTable)getTableView()).siteProperty().get();
|
||||
else
|
||||
site = null;
|
||||
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
final ReferenceChecker checker = new ReferenceChecker(site, items);
|
||||
checker.setOnSucceeded(evt -> Platform.runLater(() -> {
|
||||
final Map<ReferenceDescription, Map<Version, Set<Integer>>> md = checker.getValue();
|
||||
if (multipleVersionsInUse(md)) {
|
||||
final ImageView imgView = new ImageView(Rhomeo.ICON_WARNING_ORANGE);
|
||||
final Tooltip tt = new Tooltip("Les versions de référentiels ne sont pas homogènes.");
|
||||
tt.setContentDisplay(ContentDisplay.BOTTOM);
|
||||
tt.getStyleClass().add("info-popup");
|
||||
tt.setGraphic(createConflictView(md));
|
||||
final Label label = new Label(null, imgView);
|
||||
label.setTooltip(tt);
|
||||
header.setGraphic(label);
|
||||
}
|
||||
}));
|
||||
|
||||
checker.setOnFailed(evt -> Platform.runLater(() -> {
|
||||
// TODO. For now, do as if nothing happened
|
||||
}));
|
||||
|
||||
checkTaskProperty.set(checker);
|
||||
}
|
||||
|
||||
private VBox createConflictView(final Map<ReferenceDescription, Map<Version, Set<Integer>>> md) {
|
||||
final VBox vbox = new VBox(10);
|
||||
vbox.setFillWidth(true);
|
||||
vbox.setStyle("font-size: 12pt;");
|
||||
for (Map.Entry<ReferenceDescription, Map<Version, Set<Integer>>> entry : md.entrySet()) {
|
||||
if (entry.getValue().size() < 2)
|
||||
continue;
|
||||
final BorderPane bp = new BorderPane();
|
||||
final Label title = new Label(entry.getKey().getTitle());
|
||||
title.getStyleClass().add("section-title");
|
||||
bp.setTop(title);
|
||||
final GridPane gp = new GridPane();
|
||||
bp.setCenter(gp);
|
||||
gp.getColumnConstraints().addAll(
|
||||
new ColumnConstraints(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Priority.NEVER, HPos.LEFT, true),
|
||||
new ColumnConstraints(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.CENTER, true)
|
||||
);
|
||||
final Map<Version, Set<Integer>> vMap = entry.getValue();
|
||||
int rowIndex = 0;
|
||||
for (Map.Entry<Version, Set<Integer>> vEntry : vMap.entrySet()) {
|
||||
gp.add(new Label(vEntry.getKey().toString()), 0, rowIndex);
|
||||
final StringJoiner sj = new StringJoiner(", ");
|
||||
vEntry.getValue().forEach(val -> sj.add(String.valueOf(val)));
|
||||
gp.add(new Label(sj.toString()), 1, rowIndex++);
|
||||
}
|
||||
vbox.getChildren().add(bp);
|
||||
}
|
||||
|
||||
return vbox;
|
||||
}
|
||||
|
||||
private boolean multipleVersionsInUse(final Map<ReferenceDescription, Map<Version, Set<Integer>>> metadata) {
|
||||
for (final Map.Entry<ReferenceDescription, Map<Version, Set<Integer>>> entry : metadata.entrySet()) {
|
||||
if (entry.getValue().size() > 1)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read metadata associated to given items and column indicator, to find all
|
||||
* versions used for references.
|
||||
*/
|
||||
private class ReferenceChecker extends Task<Map<ReferenceDescription, Map<Version, Set<Integer>>>> {
|
||||
|
||||
final List<IndicatorValuesByYearItem> items;
|
||||
final Site site;
|
||||
|
||||
ReferenceChecker(final Site site, final List<IndicatorValuesByYearItem> items) {
|
||||
this.site = site;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<ReferenceDescription, Map<Version, Set<Integer>>> call() throws Exception {
|
||||
final Session session = Session.getInstance();
|
||||
final ResultStorage storage = session.getBean(ResultStorage.class);
|
||||
|
||||
final Map<ReferenceDescription, Map<Version, Set<Integer>>> refVersions = new HashMap<>();
|
||||
for (final IndicatorValuesByYearItem item : items) {
|
||||
if (isCancelled())
|
||||
break;
|
||||
final IndicatorValuesByYearItem.PublishedValue tmpVal = item.getValueForIndicator(indicator.getName());
|
||||
if (tmpVal != null) {
|
||||
final DashboardResultItem tmpItem = new DashboardResultItem(item.getYear(), site.getName(), indicator.getName(), tmpVal.getValue(), tmpVal.isPublished());
|
||||
storage.readMetadata(tmpItem)
|
||||
.map(sc -> sc.toDataContext(session))
|
||||
.map(ctx -> session.transform(ctx.getReferences()))
|
||||
.ifPresent(map -> merge(map, refVersions, item.getYear()));
|
||||
}
|
||||
}
|
||||
|
||||
return refVersions;
|
||||
}
|
||||
|
||||
void merge(final Map<ReferenceDescription, Version> source, final Map<ReferenceDescription, Map<Version, Set<Integer>>> dest, int year) {
|
||||
for (final Map.Entry<ReferenceDescription, Version> entry : source.entrySet()) {
|
||||
dest.computeIfAbsent(entry.getKey(), MAP_CREATOR)
|
||||
.computeIfAbsent(entry.getValue(), SET_CREATOR)
|
||||
.add(year);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Function<Version, Set<Integer>> SET_CREATOR = input -> new HashSet<>();
|
||||
private static final Function<ReferenceDescription, Map<Version, Set<Integer>>> MAP_CREATOR = input -> new HashMap<>();
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
|
||||
import fr.cenra.rhomeo.api.ui.DashboardMenuItem;
|
||||
import fr.cenra.rhomeo.core.RhomeoRuntimeException;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p04.I04;
|
||||
import fr.cenra.rhomeo.protocol.p04.I07;
|
||||
import fr.cenra.rhomeo.protocol.p08.I12;
|
||||
import fr.cenra.rhomeo.protocol.p09.I13;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TablePosition;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys) <samuel.andres at geomatys.com>
|
||||
*/
|
||||
@Component
|
||||
public class MultiIndicatorsDashboardItem implements DashboardMenuItem {
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Override
|
||||
public Optional<MenuItem> createItem(final TableView<IndicatorValuesByYearItem> table) {
|
||||
|
||||
if (!(table instanceof FXDashboardResultsTable))
|
||||
return Optional.empty();
|
||||
|
||||
final MenuItem graphItem = new MenuItem("Voir tous les indicateurs");
|
||||
graphItem.setOnAction(evt -> {
|
||||
try {
|
||||
displayStage((FXDashboardResultsTable) table);
|
||||
} catch (IOException | IntrospectionException | InstantiationException | IllegalAccessException ex) {
|
||||
throw new RhomeoRuntimeException("Unable to display results.", ex);
|
||||
}
|
||||
});
|
||||
graphItem.visibleProperty().bind(Bindings.size(table.getSelectionModel().getSelectedIndices()).isEqualTo(1)
|
||||
.and(isValidIndice(table.getSelectionModel().getSelectedCells())));
|
||||
|
||||
return Optional.of(graphItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customized binding to test indicator column.
|
||||
*
|
||||
* @param op
|
||||
* @return
|
||||
*/
|
||||
public static BooleanBinding isValidIndice(final ObservableList<TablePosition> op) {
|
||||
if (op == null) {
|
||||
throw new NullPointerException("List cannot be null.");
|
||||
}
|
||||
|
||||
return new BooleanBinding() {
|
||||
{
|
||||
super.bind(op);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.unbind(op);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean computeValue() {
|
||||
if(op.size()!=1) return false;
|
||||
final TablePosition cell = op.get(0);
|
||||
final TableColumn tableColumn = cell.getTableColumn();
|
||||
if(tableColumn instanceof IndicatorSiteColumn && ((IndicatorSiteColumn) tableColumn).getIndicator()!=null){
|
||||
final String ind = ((IndicatorSiteColumn) tableColumn).getIndicator().getName();
|
||||
if (I04.NAME.equals(ind) || I07.NAME.equals(ind) || I12.NAME.equals(ind) || I13.NAME.equals(ind)) {
|
||||
return tableColumn.getCellData(cell.getRow()) != null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
//@ReturnsUnmodifiableCollection
|
||||
public ObservableList<?> getDependencies() {
|
||||
return FXCollections.singletonObservableList(op);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the evolution graph in a modal window.
|
||||
*/
|
||||
private void displayStage(final FXDashboardResultsTable table) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
|
||||
|
||||
if (table.getScene() == null
|
||||
|| table.getScene().getWindow() == null
|
||||
|| table.getSelectionModel().getSelectedCells()==null
|
||||
|| table.getSelectionModel().getSelectedCells().size()!=1)
|
||||
return;
|
||||
|
||||
final Site site = table.siteProperty().get();
|
||||
final TableColumn<IndicatorValuesByYearItem, ?> tableColumn = table.getSelectionModel().getSelectedCells().get(0).getTableColumn();
|
||||
|
||||
if(site!=null && site.getName()!=null && tableColumn!=null && tableColumn instanceof IndicatorSiteColumn){
|
||||
|
||||
final Indicator indicator = ((IndicatorSiteColumn) tableColumn).getIndicator();
|
||||
final IndicatorValuesByYearItem selectedItem = table.getSelectionModel().getSelectedItem();
|
||||
|
||||
final Stage chartStage = new Stage(StageStyle.TRANSPARENT);
|
||||
chartStage.initModality(Modality.WINDOW_MODAL);
|
||||
chartStage.initOwner(table.getScene().getWindow());
|
||||
|
||||
final FXFullSpecificResultPane fullResultPane = session.createPrototype(FXFullSpecificResultPane.class,
|
||||
selectedItem,
|
||||
indicator,
|
||||
site.getName());
|
||||
|
||||
final Label label = new Label(indicator.getName() + " ("+selectedItem.getYear()+")");
|
||||
label.setAlignment(Pos.CENTER);
|
||||
label.setMaxWidth(Double.MAX_VALUE);
|
||||
label.getStyleClass().add("text-bold");
|
||||
|
||||
final BorderPane root = new BorderPane();
|
||||
root.setPrefSize(400, Region.USE_COMPUTED_SIZE);
|
||||
root.getStyleClass().add("white-box");
|
||||
root.setTop(label);
|
||||
root.setCenter(fullResultPane);
|
||||
|
||||
final Button closeBtn = new Button("Fermer");
|
||||
closeBtn.setCancelButton(true);
|
||||
closeBtn.setOnAction(evt -> chartStage.close());
|
||||
closeBtn.getStyleClass().add("white-button");
|
||||
final HBox hBox = new HBox(closeBtn);
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
root.setBottom(hBox);
|
||||
|
||||
final Scene scene = new Scene(root);
|
||||
scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH);
|
||||
scene.getStylesheets().add("/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.css");
|
||||
|
||||
chartStage.setMaxHeight(400);
|
||||
chartStage.setMaxWidth(700);
|
||||
chartStage.setScene(scene);
|
||||
chartStage.sizeToScene();
|
||||
chartStage.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,396 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.DataContext;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.api.data.TrackingPoint;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p02.P02;
|
||||
import fr.cenra.rhomeo.protocol.p05.P05;
|
||||
import fr.cenra.rhomeo.protocol.p06.P06;
|
||||
import fr.cenra.rhomeo.protocol.p07.P07;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TablePosition;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* This pane is a factorisation of common attributes and functionalities of
|
||||
* {@link P02}, {@link P05}, {@link P06} and {@link P07} {@link StatementEditor}s.
|
||||
*
|
||||
* It displays a summary of a "current" {@link TrackingPoint} one the one hand,
|
||||
* and a list of {@link T} on the other hand.
|
||||
*
|
||||
* The pane inherits Borderpane and must contain at least :
|
||||
*
|
||||
* 1- {@link TrackingPoint} attribute summary :
|
||||
*
|
||||
* - a {@link TextField} ({@link AbstractCustomStatementEditor#uiName}) mapping
|
||||
* the name of the current {@link TrackingPoint}.
|
||||
* - a {@link DatePicker} field ({@link AbstractCustomStatementEditor#uiDate}),
|
||||
* mapping the date of the current selected statement.
|
||||
* - a {@link TextArea} field ({@link AbstractCustomStatementEditor#uiRemarks}),
|
||||
* mapping the remarks of the current selected statement.
|
||||
*
|
||||
* 2- A {@link StatementTable} of the {@link T}s of the selected {@link TrackingPoint}.
|
||||
*
|
||||
* /!\ Inherited classes should be {@link Component}s, as a post-construct phase
|
||||
* and an autowired field are defined here.
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys) <samuel.andres at geomatys.com>
|
||||
* @param <T>
|
||||
*/
|
||||
public abstract class AbstractCustomStatementEditor<T extends Statement> extends BorderPane implements StatementEditor<T> {
|
||||
|
||||
@Autowired
|
||||
protected Session session;
|
||||
|
||||
// Tracking point attributes
|
||||
@FXML protected TextField uiName;
|
||||
@FXML protected DatePicker uiDate;
|
||||
@FXML protected TextArea uiRemarks;
|
||||
|
||||
// Statement control
|
||||
@FXML protected BorderPane uiObsContainer;
|
||||
@FXML protected Button uiDelete;
|
||||
protected StatementTable<T> uiStatementTable;
|
||||
|
||||
protected final Class<T> dataType;
|
||||
|
||||
/**
|
||||
* An internal field we use to know if the typed name has changed when user
|
||||
* exit the name field.
|
||||
*/
|
||||
private String oldName = null;
|
||||
/**
|
||||
* An internal field we use to know if the typed date has changed when user
|
||||
* exit the date field.
|
||||
*/
|
||||
private LocalDate oldDate = null;
|
||||
/**
|
||||
* An internal field we use to know if the typed remarks have changed when
|
||||
* user exit the remarks field.
|
||||
*/
|
||||
private String oldRemarks = null;
|
||||
|
||||
private final StringProperty firstEditionColumn = new SimpleStringProperty();
|
||||
|
||||
protected AbstractCustomStatementEditor(final Class<T> dataType) {
|
||||
super();
|
||||
ArgumentChecks.ensureNonNull("Data type", dataType);
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default {@link PostConstruct} AbstractCustomStatementEditor.
|
||||
*/
|
||||
@PostConstruct
|
||||
protected void init() {
|
||||
try {
|
||||
final Class<? extends Statement> dataType = session.getDataContext().getProtocol().getDataType();
|
||||
if (getDataType().isAssignableFrom(dataType)) {
|
||||
// Default behaviour : ignore some statement attributes.
|
||||
uiStatementTable = new StatementTable(dataType, getFieldsToIgnore());
|
||||
uiStatementTable.setMinHeight(30);
|
||||
uiStatementTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
final InvalidationListener listener = (Observable obs) -> {
|
||||
for (final TableColumn col : uiStatementTable.getColumns()) {
|
||||
if (col.isEditable() && (col instanceof PropertyColumn)) {
|
||||
firstEditionColumn.set(((PropertyColumn)col).descriptor.getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
uiStatementTable.getColumns().addListener(listener);
|
||||
listener.invalidated(uiStatementTable.getColumns());
|
||||
|
||||
uiObsContainer.setCenter((Node) uiStatementTable);
|
||||
|
||||
uiName.focusedProperty().addListener(this::nameFocusChanged);
|
||||
uiDate.focusedProperty().addListener(this::dateFocusChanged);
|
||||
uiRemarks.focusedProperty().addListener(this::remarksFocusChanged);
|
||||
|
||||
uiStatementTable.itemsProperty().addListener(this::itemsChanged);
|
||||
itemsChanged(uiStatementTable.itemsProperty(), null, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot initialize statement table !", e);
|
||||
final Label label = new Label("Impossible d'afficher la table des relevés.");
|
||||
label.getStyleClass().add(Rhomeo.CSS_ERROR_FIELD);
|
||||
uiObsContainer.setCenter(label);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<T> getItems() {
|
||||
return uiStatementTable.getItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setItems(ObservableList<T> items) {
|
||||
uiStatementTable.setItems(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectProperty<ObservableList<T>> itemsProperty() {
|
||||
return uiStatementTable.itemsProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return A list of properties from {@link #dataType} which must not be
|
||||
* included into edition table.
|
||||
*/
|
||||
protected String[] getFieldsToIgnore() {
|
||||
return new String[]{"trackingPoint", "date", "remarks"};
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour dataType return behaviour.
|
||||
*
|
||||
* Returns the data type of the {@link Protocol} of the current {@link Session}
|
||||
* {@link DataContext}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Class<T> getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour to create a new {@link T}.
|
||||
*
|
||||
* Creates a new statement initializing common attributes from {@link TrackingPoint}.
|
||||
*
|
||||
* @return
|
||||
* @throws ReflectiveOperationException
|
||||
*/
|
||||
protected T initStatement() throws ReflectiveOperationException {
|
||||
// TODO : try to acquire a prototype ?
|
||||
final T data = (T) session.getDataContext().getProtocol().getDataType().newInstance();
|
||||
|
||||
if (uiName.getText() != null && !uiName.getText().trim().isEmpty()) {
|
||||
data.setTrackingPoint(uiName.getText());
|
||||
}
|
||||
|
||||
if (uiDate.getValue() != null) {
|
||||
data.setDate(uiDate.getValue());
|
||||
}
|
||||
|
||||
data.setRemarks(uiRemarks.getText());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour on adding {@link T} event.
|
||||
*
|
||||
* Creates a new statement and focus on it.
|
||||
*
|
||||
* @param evt
|
||||
* @throws ReflectiveOperationException
|
||||
*/
|
||||
@FXML
|
||||
protected void addStatement(final ActionEvent evt) throws ReflectiveOperationException {
|
||||
if (uiStatementTable == null || uiStatementTable.getItems() == null)
|
||||
return;
|
||||
|
||||
final T data = initStatement();
|
||||
|
||||
uiStatementTable.getItems().add(data);
|
||||
final TablePosition pos = new TablePosition(uiStatementTable, uiStatementTable.getItems().size() - 1, null);
|
||||
|
||||
// HACK : It appears that table cells are refreshed on next pulse, so we wait for it before asking for focus.
|
||||
TaskManager.INSTANCE.submit(() -> {
|
||||
synchronized (pos) {
|
||||
try {
|
||||
pos.wait(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
Platform.runLater(() -> Rhomeo.editNextCell(pos));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour of deleting {@link T} event.
|
||||
*
|
||||
* @param evt
|
||||
*/
|
||||
@FXML
|
||||
protected void deleteStatements(final ActionEvent evt) {
|
||||
if (uiStatementTable == null || uiStatementTable.getItems() == null)
|
||||
return;
|
||||
|
||||
// Defensive copy
|
||||
final List<Integer> indices = new ArrayList<>(uiStatementTable.getSelectionModel().getSelectedIndices());
|
||||
for (final int index : indices) {
|
||||
uiStatementTable.getItems().remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusOn(T item, String propertyName) {
|
||||
uiStatementTable.focusOn(item, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour on changing current {@link TrackingPoint}.
|
||||
*
|
||||
* @param obs
|
||||
* @param oldItems
|
||||
* @param newItems
|
||||
*/
|
||||
protected void itemsChanged(final ObservableValue<? extends List> obs, final List<T> oldItems, final List<T> newItems) {
|
||||
uiDelete.disableProperty().unbind();
|
||||
if (newItems != null && !newItems.isEmpty()) {
|
||||
final T first = newItems.get(0);
|
||||
uiName.setText(first.getTrackingPoint());
|
||||
uiDate.setValue(first.getDate());
|
||||
uiRemarks.setText(first.getRemarks());
|
||||
|
||||
if (uiStatementTable != null) {
|
||||
uiStatementTable.getSelectionModel().selectFirst();
|
||||
}
|
||||
|
||||
} else {
|
||||
uiName.setText(null);
|
||||
uiDate.setValue(null);
|
||||
uiRemarks.clear();
|
||||
}
|
||||
|
||||
if (uiStatementTable != null) {
|
||||
uiDelete.disableProperty().bind(uiStatementTable.getSelectionModel().selectedItemProperty().isNull());
|
||||
}
|
||||
|
||||
uiName.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour when the focus has changed on {@link AbstractCustomStatementEditor#uiName}.
|
||||
*
|
||||
* @param obs
|
||||
* @param oldValue
|
||||
* @param newValue
|
||||
*/
|
||||
protected void nameFocusChanged(final ObservableValue<? extends Boolean> obs, final Boolean oldValue, final Boolean newValue) {
|
||||
final String name = uiName.getText();
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
oldName = name;
|
||||
} else if (Boolean.FALSE.equals(newValue) && !Rhomeo.equivalent(oldName, name)) {
|
||||
// Update statements. Use defensive copy because each change of name can update list of available items.
|
||||
final Statement[] toUpdate = uiStatementTable.getItems().toArray(new Statement[uiStatementTable.getItems().size()]);
|
||||
for (int i = 0 ; i < toUpdate.length ; i++) {
|
||||
toUpdate[i].setTrackingPoint(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour when the focus has changed on {@link AbstractCustomStatementEditor#uiDate}.
|
||||
*
|
||||
* @param obs
|
||||
* @param oldValue
|
||||
* @param newValue
|
||||
*/
|
||||
protected void dateFocusChanged(final ObservableValue<? extends Boolean> obs, final Boolean oldValue, final Boolean newValue) {
|
||||
final LocalDate date = uiDate.getValue();
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
oldDate = date;
|
||||
} else if (!(oldDate == date || oldDate != null && date != null && oldDate.isEqual(date))) {
|
||||
// Update statements. Use defensive copy because each change of name can update list of available items.
|
||||
final Statement[] toUpdate = uiStatementTable.getItems().toArray(new Statement[uiStatementTable.getItems().size()]);
|
||||
for (int i = 0 ; i < toUpdate.length ; i++) {
|
||||
toUpdate[i].setDate(date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behaviour when the focus has changed on {@link AbstractCustomStatementEditor#uiName}.
|
||||
*
|
||||
* @param obs
|
||||
* @param oldValue
|
||||
* @param newValue
|
||||
*/
|
||||
protected void remarksFocusChanged(final ObservableValue<? extends Boolean> obs, final Boolean oldValue, final Boolean newValue) {
|
||||
final String remarks = uiRemarks.getText();
|
||||
if (Boolean.TRUE.equals(newValue)) {
|
||||
oldRemarks = remarks;
|
||||
} else if (Boolean.FALSE.equals(newValue) && !Rhomeo.equivalent(oldRemarks, remarks)) {
|
||||
for (final Statement st : uiStatementTable.getItems()) {
|
||||
st.setRemarks(remarks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import javafx.scene.control.TableCell;
|
||||
|
||||
/**
|
||||
* A table cell which knows what value to commit when editing.
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public abstract class AutoCommitableTableCell<T, S> extends TableCell<T, S> {
|
||||
/**
|
||||
* Trigger a commit, as {@link #commitEdit(java.lang.Object) }. But its the
|
||||
* cell which determines the value to commit.
|
||||
*/
|
||||
abstract void commitEdit();
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class ByNameAndDateFilter implements Predicate<Statement>, Comparable<ByNameAndDateFilter>, Externalizable {
|
||||
|
||||
protected static final long serialVersionUID = 1L;
|
||||
|
||||
protected String name;
|
||||
protected LocalDate date;
|
||||
|
||||
public ByNameAndDateFilter() {}
|
||||
|
||||
public ByNameAndDateFilter(String name, LocalDate date) {
|
||||
this.name = name;
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Statement t) {
|
||||
if (!Objects.equals(name, t.getTrackingPoint()))
|
||||
return false;
|
||||
return RhomeoCore.checkEquality(date, t.getDate());
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public LocalDate getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ByNameAndDateFilter o) {
|
||||
final int nameComparison = getName().compareTo(o.getName());
|
||||
if (nameComparison != 0)
|
||||
return nameComparison;
|
||||
return getDate().compareTo(o.getDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 61 * hash + Objects.hashCode(this.name);
|
||||
hash = 61 * hash + Objects.hashCode(this.date);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final ByNameAndDateFilter other = (ByNameAndDateFilter) obj;
|
||||
if (!Objects.equals(this.name, other.name))
|
||||
return false;
|
||||
return date == other.date || date != null && other.date != null && date.isEqual(other.date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeUTF(name == null? "" : name);
|
||||
RhomeoCore.writeDate(date, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
name = in.readUTF();
|
||||
date = RhomeoCore.readDate(in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import com.sun.javafx.PlatformUtil;
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.preferences.PreferenceGroup;
|
||||
import fr.cenra.rhomeo.api.ui.PreferencePane;
|
||||
import fr.cenra.rhomeo.core.CSVMapper;
|
||||
import fr.cenra.rhomeo.core.CSVMappingBuilder;
|
||||
import fr.cenra.rhomeo.preferences.csv.CSVKey;
|
||||
import fr.cenra.rhomeo.preferences.csv.CSVPreferences;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.Window;
|
||||
import javafx.util.StringConverter;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A panel to edit CSV format preferences for dataset import / export. You can
|
||||
* also acquire a {@link CSVMappingBuilder} from a configuration typed in a popup
|
||||
* by calling {@link #showConfigurationForm(javafx.stage.Window) }.
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class CSVConfigurationPane extends VBox implements PreferencePane<CSVKey> {
|
||||
|
||||
private static final String INFO_TEXT = new StringBuilder("L'échappement des chaînes de caractère est optionnel. Le caractère d'échappement est \" et doit entourer la chaine complète.")
|
||||
.append(System.lineSeparator())
|
||||
.append("Pour utiliser le caractère \" dans les messages échappés, il doit être doublé.")
|
||||
.append(System.lineSeparator())
|
||||
.append("Ex : \"Ceci est un texte \"\"échappé\"\".\"")
|
||||
.toString();
|
||||
|
||||
@FXML
|
||||
private ComboBox<Charset> uiEncoding;
|
||||
|
||||
@FXML
|
||||
private TextField uiText;
|
||||
|
||||
@FXML
|
||||
private CheckBox uiTab;
|
||||
|
||||
@FXML
|
||||
private Label uiInfo;
|
||||
|
||||
@FXML
|
||||
private Button uiCancel;
|
||||
|
||||
@FXML
|
||||
private Button uiApply;
|
||||
|
||||
@Autowired
|
||||
private CSVPreferences prefs;
|
||||
|
||||
private CSVMappingBuilder builder;
|
||||
|
||||
private CSVConfigurationPane() {
|
||||
super();
|
||||
Rhomeo.loadFXML(this);
|
||||
|
||||
Charset defaultCharset;
|
||||
if (PlatformUtil.isWindows())
|
||||
try {
|
||||
defaultCharset = Charset.forName("windows-1252");
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.FINE, "No charset found for windows-1252.", e);
|
||||
defaultCharset = StandardCharsets.ISO_8859_1;
|
||||
}
|
||||
else
|
||||
defaultCharset = StandardCharsets.UTF_8;
|
||||
|
||||
Rhomeo.initComboBox(uiEncoding, FXCollections.observableArrayList(Charset.availableCharsets().values()).sorted(), defaultCharset);
|
||||
uiEncoding.setConverter(new CharsetStringConverter());
|
||||
|
||||
uiText.disableProperty().bind(uiTab.selectedProperty());
|
||||
uiText.textProperty().addListener(this::textChanged);
|
||||
|
||||
uiInfo.setGraphic(new ImageView(Rhomeo.ICON_INFO_BLUE));
|
||||
uiInfo.setText(INFO_TEXT);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
init(prefs.getPreference(CSVPreferences.CHARSET_KEY), prefs.getPreference(CSVPreferences.SEPARATOR_KEY));
|
||||
}
|
||||
|
||||
private void init(final String charset, final String separator) {
|
||||
// Restore default values from preferences.
|
||||
if (separator.equals("\t")) {
|
||||
uiTab.setSelected(true);
|
||||
} else {
|
||||
uiText.setText(separator);
|
||||
}
|
||||
|
||||
if (charset != null)
|
||||
uiEncoding.setValue(Charset.forName(charset));
|
||||
}
|
||||
|
||||
private void initButtons() {
|
||||
uiApply.setVisible(true);
|
||||
uiCancel.setVisible(true);
|
||||
|
||||
uiApply.setOnAction(this::createBuilder);
|
||||
uiApply.disableProperty().bind(Bindings.createBooleanBinding(() ->
|
||||
uiText.getCharacters().length() != 1 && uiEncoding.getValue() == null
|
||||
, uiText.textProperty(), uiEncoding.valueProperty()));
|
||||
uiCancel.setCancelButton(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that typed separator is a single character.
|
||||
*
|
||||
* @param obs
|
||||
* @param oldValue
|
||||
* @param newValue
|
||||
*/
|
||||
private void textChanged(final ObservableValue<? extends String> obs, final String oldValue, final String newValue) {
|
||||
if (newValue != null && newValue.length() > 1) {
|
||||
uiText.setText(newValue.substring(newValue.length() -1));
|
||||
}
|
||||
}
|
||||
|
||||
private void createBuilder(final ActionEvent e) {
|
||||
if (uiEncoding.getValue() == null || (!uiTab.isSelected() && uiText.getText() == null || uiText.getText().isEmpty()))
|
||||
return;
|
||||
|
||||
final char separator = uiTab.isSelected()? '\t' : uiText.getText().charAt(uiText.getText().length() -1);
|
||||
builder = new CSVMappingBuilder()
|
||||
.withSeparator(separator)
|
||||
.withEncoding(uiEncoding.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a dialog allowing user to configure the basic parameters for CSV
|
||||
* mapping (separator, encoding).
|
||||
*
|
||||
* /!\ MUST BE CALLED FROM FX-THREAD.
|
||||
*
|
||||
* @return A csv mapper builder if user confirmed its configuration, Nothing
|
||||
* if he cancelled.
|
||||
*/
|
||||
public static Optional<CSVMappingBuilder> showConfigurationForm() {
|
||||
return showConfigurationForm(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a dialog allowing user to configure the basic parameters for CSV
|
||||
* mapping (separator, encoding).
|
||||
*
|
||||
* /!\ MUST BE CALLED FROM FX-THREAD.
|
||||
*
|
||||
* @param owner The owner of the dialog to display. If the owner is null,
|
||||
* then displayed stage is application modal. Otherwise, the modality is
|
||||
* limited only on owner window.
|
||||
* @return A csv mapper builder if user confirmed its configuration, Nothing
|
||||
* if he cancelled.
|
||||
*/
|
||||
public static Optional<CSVMappingBuilder> showConfigurationForm(final Window owner) {
|
||||
final CSVConfigurationPane content = new CSVConfigurationPane();
|
||||
content.init(StandardCharsets.UTF_8.name(), String.valueOf(CSVMapper.DEFAULT_SEPARATOR));
|
||||
content.initButtons();
|
||||
|
||||
final Stage stage = new Stage(StageStyle.UTILITY);
|
||||
final EventHandler<ActionEvent> btnListener = (evt) -> stage.close();
|
||||
content.uiApply.addEventHandler(ActionEvent.ACTION, btnListener);
|
||||
content.uiCancel.addEventHandler(ActionEvent.ACTION, btnListener);
|
||||
|
||||
if (owner != null) {
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
} else {
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
}
|
||||
|
||||
stage.setScene(new Scene(content));
|
||||
stage.setTitle("Paramètres de lecture");
|
||||
stage.showAndWait();
|
||||
return Optional.ofNullable(content.builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (uiText.getText() != null && !uiText.getText().isEmpty()) {
|
||||
final char separator = uiTab.isSelected() ? '\t' : uiText.getText().charAt(uiText.getText().length() - 1);
|
||||
prefs.setPreference(CSVPreferences.SEPARATOR_KEY, String.valueOf(separator));
|
||||
}
|
||||
if (uiEncoding.getValue() != null)
|
||||
prefs.setPreference(CSVPreferences.CHARSET_KEY, uiEncoding.getValue().name());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceGroup<CSVKey> getPreferenceGroup() {
|
||||
return prefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getEditor() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple converter between string and charset types.
|
||||
*/
|
||||
private static class CharsetStringConverter extends StringConverter<Charset> {
|
||||
|
||||
@Override
|
||||
public String toString(Charset object) {
|
||||
if (object == null)
|
||||
return null;
|
||||
return object.displayName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Charset fromString(String string) {
|
||||
if (string == null || (string = string.trim()).isEmpty())
|
||||
return null;
|
||||
return Charset.forName(string);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Dataset;
|
||||
import fr.cenra.rhomeo.api.data.ObjectWrapper;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.api.process.ProcessContext;
|
||||
import fr.cenra.rhomeo.api.ui.FilterTable;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.core.CSVDecoder;
|
||||
import fr.cenra.rhomeo.core.CSVEncoder;
|
||||
import fr.cenra.rhomeo.core.CSVMapper;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.fx.validation.FXValidationPane;
|
||||
import fr.cenra.rhomeo.preferences.csv.CSVPreferences;
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.IntegerBinding;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.SelectionMode;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Path;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.geotoolkit.internal.GeotkFX;
|
||||
import org.geotoolkit.util.collection.CloseableIterator;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import fr.cenra.rhomeo.api.ui.FilterTableSpi;
|
||||
import fr.cenra.rhomeo.core.state.StateManager;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javafx.beans.Observable;
|
||||
|
||||
/**
|
||||
* Default editor for {@link Datasset} edition. For {@link Statement} edition,
|
||||
* we try to inject a {@link StatementEditor} specific to the input {@link Protocol#getDataType()
|
||||
* }.
|
||||
*
|
||||
* IMPORTANT : Input {@link StatementEditor} does not use directly {@link Dataset#getItems()
|
||||
* }, to avoid sudden changes in the editor caused by an event sourced from
|
||||
* dataset. However, to keep a responsive dataset, we mirror editor data in
|
||||
* dataset on the fly. It is needed for state saving (see {@link StateManager) and other mechanisms as validation.
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
@Lazy
|
||||
public class FXDatasetPane extends VBox {
|
||||
|
||||
private static final Pattern CSV_SUFFIX = Pattern.compile("(?i)\\.csv$");
|
||||
|
||||
@Autowired
|
||||
private CSVPreferences csvPrefs;
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private List<FilterTableSpi> pointTableSpis;
|
||||
|
||||
private FilterTable<Predicate> uiGroupTable;
|
||||
|
||||
private StatementEditor<Statement> uiStatementEditor;
|
||||
|
||||
@Autowired
|
||||
private FXValidationPane uiValidator;
|
||||
|
||||
@FXML
|
||||
private Button uiDisplayDocumentation;
|
||||
@FXML
|
||||
private Button uiDeleteDataset;
|
||||
|
||||
@FXML
|
||||
private BorderPane uiPointListContainer;
|
||||
|
||||
@FXML
|
||||
private Button uiAddPoint;
|
||||
@FXML
|
||||
private Button uiDeletePoints;
|
||||
|
||||
@FXML
|
||||
private BorderPane uiPointEditorContainer;
|
||||
|
||||
@FXML
|
||||
private BorderPane uiValidationContainer;
|
||||
|
||||
@FXML
|
||||
private Button uiExportDataset;
|
||||
|
||||
@FXML
|
||||
private Button uiProceed;
|
||||
|
||||
protected final ResourceBundle bundle;
|
||||
|
||||
private final ListChangeListener itemListener = this::itemsChanged;
|
||||
|
||||
private ObservableList<Statement> dataList;
|
||||
|
||||
private final AtomicBoolean changeDetected = new AtomicBoolean(false);
|
||||
|
||||
public FXDatasetPane() {
|
||||
bundle = ResourceBundle.getBundle(FXDatasetPane.class.getCanonicalName());
|
||||
Rhomeo.loadFXML(this, FXDatasetPane.class);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
ArgumentChecks.ensureNonNull("Session containing data", session);
|
||||
ArgumentChecks.ensureNonNull("Data context", session.getDataContext());
|
||||
ArgumentChecks.ensureNonNull("Target protocol", session.getDataContext().getProtocol());
|
||||
ArgumentChecks.ensureNonNull("Target dataset", session.getDataset());
|
||||
|
||||
for (final FilterTableSpi spi : pointTableSpis) {
|
||||
if (spi.getProtocol() == session.getDataContext().getProtocol()) {
|
||||
uiGroupTable = spi.createEmptyTable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uiGroupTable == null) {
|
||||
uiGroupTable = (FilterTable) new FXTrackingPointTable(session);
|
||||
}
|
||||
|
||||
uiGroupTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||
uiGroupTable.setMinHeight(50);
|
||||
uiGroupTable.setFixedCellSize(25);
|
||||
final IntegerBinding size = Bindings.size(uiGroupTable.getItems());
|
||||
uiGroupTable.prefHeightProperty().bind(
|
||||
Bindings.createDoubleBinding(() -> {
|
||||
if (size.get() < 1) {
|
||||
return 50d;
|
||||
}
|
||||
return size.get() * uiGroupTable.getFixedCellSize() + 30;
|
||||
}, size, uiGroupTable.fixedCellSizeProperty())
|
||||
);
|
||||
|
||||
Optional<StatementEditor> optEditor = session.findEditor(session.getDataContext().getProtocol().getDataType());
|
||||
if (optEditor.isPresent()) {
|
||||
uiStatementEditor = optEditor.get();
|
||||
if (uiStatementEditor instanceof Region) {
|
||||
((Region)uiStatementEditor).setMinHeight(50);
|
||||
}
|
||||
uiStatementEditor.itemsProperty().addListener(this::itemsPropertyChanged);
|
||||
uiGroupTable.getSelectionModel().selectedItemProperty().addListener((obs, oldGroup, newGroup) -> {
|
||||
if (newGroup == null) {
|
||||
uiStatementEditor.setItems(FXCollections.observableArrayList());
|
||||
} else {
|
||||
uiStatementEditor.setItems(FXCollections.observableArrayList(session.getDataset().getItems().filtered(newGroup)));
|
||||
}
|
||||
});
|
||||
|
||||
uiStatementEditor.setItems(FXCollections.observableArrayList());
|
||||
uiPointEditorContainer.setCenter((Node) uiStatementEditor);
|
||||
} else {
|
||||
uiAddPoint.setVisible(false);
|
||||
uiAddPoint.setManaged(false);
|
||||
}
|
||||
|
||||
uiPointListContainer.setCenter(uiGroupTable);
|
||||
uiPointEditorContainer.managedProperty().bind(uiPointEditorContainer.visibleProperty());
|
||||
|
||||
uiValidationContainer.setCenter(uiValidator);
|
||||
uiValidator.getWarningList().getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
uiValidator.getWarningList().getSelectionModel().selectedItemProperty().addListener(this::selectedErrorChanged);
|
||||
uiValidator.getErrorList().getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
|
||||
uiValidator.getErrorList().getSelectionModel().selectedItemProperty().addListener(this::selectedErrorChanged);
|
||||
/*
|
||||
* HACK : Error list force focus on itself after click. So we
|
||||
* have to repeat the selectedErrorChanged on mouse released.
|
||||
*/
|
||||
final EventHandler<MouseEvent> handler = evt -> {
|
||||
final ConstraintViolation violation;
|
||||
if (evt.getSource() == uiValidator.getWarningList()) {
|
||||
violation = uiValidator.getWarningList().getSelectionModel().getSelectedItem();
|
||||
} else if (evt.getSource() == uiValidator.getErrorList()) {
|
||||
violation = uiValidator.getErrorList().getSelectionModel().getSelectedItem();
|
||||
} else {
|
||||
violation = null;
|
||||
}
|
||||
|
||||
if (violation != null)
|
||||
selectedErrorChanged(null, null, violation);
|
||||
};
|
||||
uiValidator.getErrorList().addEventHandler(MouseEvent.MOUSE_RELEASED, handler);
|
||||
uiValidator.getWarningList().addEventHandler(MouseEvent.MOUSE_RELEASED, handler);
|
||||
|
||||
/*
|
||||
* Disability
|
||||
*/
|
||||
BooleanBinding emptyDataset = Bindings.isEmpty(session.getDataset().getItems());
|
||||
uiDeleteDataset.disableProperty().bind(emptyDataset);
|
||||
uiExportDataset.disableProperty().bind(emptyDataset);
|
||||
uiProceed.disableProperty().bind(emptyDataset);
|
||||
|
||||
uiDeletePoints.disableProperty().bind(uiGroupTable.getSelectionModel().selectedItemProperty().isNull());
|
||||
|
||||
// Icons
|
||||
uiDeleteDataset.setGraphic(new ImageView(Rhomeo.ICON_TRASH_BLUE));
|
||||
uiDeletePoints.setGraphic(new ImageView(Rhomeo.ICON_TRASH_BLUE));
|
||||
uiDisplayDocumentation.setGraphic(new ImageView(Rhomeo.ICON_FILE_TEXT));
|
||||
|
||||
session.getDataset().getItems().addListener(this::changeDetected);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void addPoint(ActionEvent event) {
|
||||
// No tracking point selected. However, a null value will allow creation
|
||||
// of a fresh one. We force editor visibility.
|
||||
uiGroupTable.getSelectionModel().clearSelection();
|
||||
uiStatementEditor.setItems(FXCollections.observableArrayList());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void deleteDataset(ActionEvent event) {
|
||||
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, bundle.getString("dataset.delete.confirm"), ButtonType.NO, ButtonType.YES);
|
||||
alert.setResizable(true);
|
||||
if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) {
|
||||
// issue #79 : focused filter is not suppressed.
|
||||
uiGroupTable.getSelectionModel().clearSelection();
|
||||
session.getDataset().getItems().clear();
|
||||
if (uiStatementEditor != null)
|
||||
uiStatementEditor.setItems(FXCollections.observableArrayList());
|
||||
uiValidator.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void deletePoints(ActionEvent event) {
|
||||
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, bundle.getString("point.delete.confirm"), ButtonType.NO, ButtonType.YES);
|
||||
alert.setResizable(true);
|
||||
if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) {
|
||||
// Defensive copy
|
||||
final ArrayList<Predicate> selected = new ArrayList<>(uiGroupTable.getSelectionModel().getSelectedItems());
|
||||
// HACK : filter table won't delete entries if they're focused.
|
||||
uiGroupTable.getSelectionModel().clearSelection();
|
||||
if (!selected.isEmpty()) {
|
||||
Predicate filter = selected.get(0);
|
||||
for (int i = 1; i < selected.size(); i++) {
|
||||
filter = filter.or(selected.get(i));
|
||||
}
|
||||
session.getDataset().getItems().removeIf(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void displayDocumentation(ActionEvent event) {
|
||||
final Protocol protocol = session.getDataContext().getProtocol();
|
||||
Rhomeo.generateModelZip(protocol, uiDisplayDocumentation);
|
||||
}
|
||||
|
||||
@FXML
|
||||
Task<Boolean> exportDataset(ActionEvent event) {
|
||||
final File chosen = chooseCSV(uiExportDataset.getText(), true);
|
||||
if (chosen != null) {
|
||||
final TaskManager.MockTask export = new TaskManager.MockTask(bundle.getString("dataset.export.task"), () -> {
|
||||
final char sep;
|
||||
final String separator = csvPrefs.getPreference(CSVPreferences.SEPARATOR_KEY);
|
||||
if (separator == null || separator.isEmpty())
|
||||
sep = CSVMapper.DEFAULT_SEPARATOR;
|
||||
else
|
||||
sep = separator.charAt(separator.length() - 1);
|
||||
|
||||
// Check if we must transform data when encoding.
|
||||
Class dataType = session.getDataContext().getProtocol().getDataType();
|
||||
Iterator toEncode = session.getDataset().getItems().iterator();
|
||||
Optional<ObjectWrapper> opt = session.getWrapper(dataType);
|
||||
if (opt.isPresent()) {
|
||||
dataType = opt.get().getOutputType();
|
||||
toEncode = new Transformer(opt.get(), toEncode);
|
||||
}
|
||||
|
||||
final CSVEncoder encoder = new CSVEncoder(
|
||||
chosen.toPath(), dataType, sep, Charset.forName(csvPrefs.getPreference(CSVPreferences.CHARSET_KEY)), null);
|
||||
encoder.encode(toEncode, true);
|
||||
return true;
|
||||
});
|
||||
|
||||
export.setOnSucceeded(evt -> changeDetected.set(false));
|
||||
|
||||
export.setOnFailed(evt -> Platform.runLater(() -> {
|
||||
GeotkFX.newExceptionDialog(bundle.getString("dataset.export.error"), export.getException());
|
||||
}));
|
||||
|
||||
export.runningProperty().addListener((obs, oldValue, newValue) -> {
|
||||
setDisable(newValue);
|
||||
// TODO : show a loading popup
|
||||
});
|
||||
|
||||
return TaskManager.INSTANCE.submit(export);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@FXML
|
||||
void importDataset(ActionEvent event) {
|
||||
Task previousTask = null;
|
||||
if (!session.getDataset().getItems().isEmpty()) {
|
||||
final String contentText = bundle.getString("save.before.import");
|
||||
final Alert saveCurrent = new Alert(Alert.AlertType.WARNING, contentText, ButtonType.CANCEL, ButtonType.NO, ButtonType.YES);
|
||||
saveCurrent.setResizable(true);
|
||||
ButtonType choice = saveCurrent.showAndWait().orElse(ButtonType.CANCEL);
|
||||
if (ButtonType.CANCEL.equals(choice)) {
|
||||
return;
|
||||
} else if (ButtonType.YES.equals(choice)) {
|
||||
previousTask = exportDataset(event);
|
||||
if (previousTask == null) {
|
||||
return; // user cancelled saving. Stop the entire process.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final File input = chooseCSV(bundle.getString("dataset.import.choice"), false);
|
||||
if (input == null)
|
||||
return;
|
||||
|
||||
/* On dataset change, the trigger serving to notify user on possible
|
||||
* unsaved entries. But, as we are just importing data, we must keep
|
||||
* the same state as before the task.
|
||||
*/
|
||||
final boolean previousStatus = changeDetected.get();
|
||||
final TaskManager.MockTask<String> importTask = new TaskManager.MockTask(bundle.getString("dataset.import.task"), () -> {
|
||||
final HashSet<String> ignorable = new HashSet<>(2);
|
||||
ignorable.add("trackingPoint");
|
||||
ignorable.add("remarks");
|
||||
final char sep;
|
||||
final String separator = csvPrefs.getPreference(CSVPreferences.SEPARATOR_KEY);
|
||||
if (separator == null || separator.isEmpty())
|
||||
sep = CSVMapper.DEFAULT_SEPARATOR;
|
||||
else
|
||||
sep = separator.charAt(separator.length() - 1);
|
||||
final CSVDecoder decoder = new CSVDecoder(
|
||||
input.toPath(),
|
||||
session.getDataContext().getProtocol().getDataType(),
|
||||
sep,
|
||||
Charset.forName(csvPrefs.getPreference(CSVPreferences.CHARSET_KEY)),
|
||||
ignorable
|
||||
);
|
||||
|
||||
// Check doublons.
|
||||
int integrated = 0;
|
||||
int doublons = 0;
|
||||
final ObservableList dataset = session.getDataset().getItems();
|
||||
final HashSet doublonDetector = new HashSet(dataset);
|
||||
try (final CloseableIterator it = decoder.decodeLazy()) {
|
||||
Object read;
|
||||
while (it.hasNext()) {
|
||||
read = it.next();
|
||||
if (doublonDetector.add(read)) {
|
||||
integrated++;
|
||||
} else {
|
||||
doublons++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update dataset with new elements.
|
||||
doublonDetector.removeAll(dataset);
|
||||
final TaskManager.MockTask t = new TaskManager.MockTask(() -> dataset.addAll(doublonDetector));
|
||||
Platform.runLater(t);
|
||||
t.get();
|
||||
|
||||
if (doublons <= 0)
|
||||
return String.format(bundle.getString("dataset.import.result.no-doublon"), integrated);
|
||||
else
|
||||
return String.format(bundle.getString("dataset.import.result"), integrated, doublons);
|
||||
});
|
||||
|
||||
importTask.setOnFailed(evt -> Platform.runLater(() -> {
|
||||
GeotkFX.newExceptionDialog(bundle.getString("dataset.import.error"), importTask.getException());
|
||||
}));
|
||||
|
||||
importTask.setOnSucceeded(evt -> Platform.runLater(() -> {
|
||||
changeDetected.set(previousStatus);
|
||||
Rhomeo.showAlert(Alert.AlertType.INFORMATION, importTask.getValue());
|
||||
}));
|
||||
|
||||
if (previousTask != null) {
|
||||
final SimpleObjectProperty<Worker.State> previousState = new SimpleObjectProperty<>();
|
||||
previousState.addListener((obs, oldState, newState) -> {
|
||||
if (Worker.State.SUCCEEDED.equals(newState)) {
|
||||
TaskManager.INSTANCE.submit(importTask);
|
||||
}
|
||||
});
|
||||
previousState.bind(previousTask.stateProperty());
|
||||
} else {
|
||||
TaskManager.INSTANCE.submit(importTask);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void nextStep(ActionEvent event) {
|
||||
Task<Boolean> previousTask = checkSaveChange().orElse(null);
|
||||
|
||||
final Runnable proceed = () -> {
|
||||
setProcessContext(session);
|
||||
|
||||
/*
|
||||
* Check if we're able to jump to the next step. If not, we try to guess
|
||||
* why, and inform user of what is wrong.
|
||||
*/
|
||||
if (!session.requestWorkflowStep(WorkflowStep.PROCESS)) {
|
||||
if (session.getDataContext().getReferences().size() < session.getDataContext().getProtocol().getReferenceTypes().size()) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Les versions des référentiels associés au protocole ne sont pas définies !");
|
||||
} else {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante. Veuillez vérifier votre lot de données.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (previousTask != null) {
|
||||
previousTask.setOnSucceeded(evt -> {
|
||||
if (Boolean.TRUE.equals(evt.getSource().getValue()))
|
||||
Platform.runLater(proceed);
|
||||
});
|
||||
} else {
|
||||
proceed.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If changes have been detected in currently edited dataset, ask to user if
|
||||
* he wants to save them, and launch export process if he agrees.
|
||||
*
|
||||
* /!\ You must call this method from FX thread.
|
||||
*
|
||||
* @return An empty object if there's no change to save. Otherwise, a task
|
||||
* is returned, which returns false if user cancelled saving, or true if
|
||||
* data has been successfully written.
|
||||
*/
|
||||
public Optional<Task<Boolean>> checkSaveChange() {
|
||||
if (!changeDetected.get()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final String contentText = bundle.getString("save.before.next");
|
||||
final Alert saveCurrent = new Alert(Alert.AlertType.CONFIRMATION, contentText, ButtonType.CANCEL, ButtonType.NO, ButtonType.YES);
|
||||
saveCurrent.setResizable(true);
|
||||
ButtonType choice = saveCurrent.showAndWait().orElse(ButtonType.CANCEL);
|
||||
|
||||
if (ButtonType.YES.equals(choice)) {
|
||||
final Task exportTask = exportDataset(null);
|
||||
if (exportTask != null)
|
||||
return Optional.of(exportTask);
|
||||
} else if (ButtonType.NO.equals(choice)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final TaskManager.MockTask t = new TaskManager.MockTask(() -> false);
|
||||
t.run();
|
||||
return Optional.of(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a change is detected on input dataset, we notify user that changes
|
||||
* occurred and some data may not have been saved.
|
||||
*
|
||||
* @param obs
|
||||
*/
|
||||
private void changeDetected(final Observable obs) {
|
||||
/* If dataset has been emptied, we clear change state. We check it here,
|
||||
* so it is valid either if user deleted it manually or via "clear dataset"
|
||||
* button.
|
||||
*/
|
||||
if (session.getDataset().getItems().isEmpty())
|
||||
changeDetected.set(false);
|
||||
else
|
||||
changeDetected.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* When we change items of inner {@link StatementEditor}, we update
|
||||
* listeners registered on it.
|
||||
*
|
||||
* @param obs
|
||||
*/
|
||||
private void itemsPropertyChanged(final Observable obs) {
|
||||
if (dataList != null)
|
||||
dataList.removeListener(itemListener);
|
||||
|
||||
dataList = uiStatementEditor.getItems();
|
||||
if (dataList != null) {
|
||||
dataList.addListener(itemListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report changes from editor list to dataset.
|
||||
* @param c
|
||||
*/
|
||||
private void itemsChanged(final ListChangeListener.Change<Statement> c) {
|
||||
ObservableList dataset = session.getDataset().getItems();
|
||||
while (c.next()) {
|
||||
if (c.wasRemoved()) {
|
||||
// Delete strict reference, because if the user has added multiple new
|
||||
// objects in the table, they could be equal.
|
||||
final ArrayList removedCopy = new ArrayList(c.getRemoved());
|
||||
dataset.removeIf(item -> {
|
||||
final Iterator it = removedCopy.iterator();
|
||||
while (it.hasNext()) {
|
||||
if (item == it.next()) {
|
||||
it.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (c.wasAdded()) {
|
||||
dataset.addAll(c.getAddedSubList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to set {@link Session}'s {@link ProcessContext}.
|
||||
*
|
||||
* @param session
|
||||
*/
|
||||
public static void setProcessContext(final Session session){
|
||||
final ProcessContext processContext;
|
||||
if (session.getProcessContext() != null) {
|
||||
processContext = session.getProcessContext();
|
||||
// We keep previous selection, but clean tracking points which does
|
||||
// not exist anymore. After this operation, if we've got no more
|
||||
// point selected, we reset selection.
|
||||
// processContext.getTrackingPoints().retainAll(session.getDataset().getTrackingPoints());
|
||||
// if (processContext.getTrackingPoints().isEmpty()) {
|
||||
// processContext.getTrackingPoints().addAll(session.getDataset().getTrackingPoints());
|
||||
// }
|
||||
} else {
|
||||
// Create a process context. By default, we activate all available indicators for the edited protocol.
|
||||
processContext = new ProcessContext(session.getDataset(), session.getDataContext());
|
||||
session.getIndicators().stream()
|
||||
.filter(i -> i.getProtocol() == session.getDataContext().getProtocol())
|
||||
.forEach(i -> processContext.getIndicators().add(i));
|
||||
//processContext.getTrackingPoints().addAll(session.getDataset().getTrackingPoints());
|
||||
}
|
||||
|
||||
session.setProcessContext(processContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* On error selection, we try to retrieve the origin statement, then focus
|
||||
* on it in the statement editor.
|
||||
*
|
||||
* @param obs
|
||||
* @param oldValue
|
||||
* @param newValue
|
||||
*/
|
||||
private void selectedErrorChanged(final ObservableValue<? extends ConstraintViolation> obs, final ConstraintViolation oldValue, final ConstraintViolation newValue) {
|
||||
if (newValue != null) {
|
||||
if (newValue.getLeafBean() instanceof Statement) {
|
||||
final Statement st = (Statement) newValue.getLeafBean();
|
||||
/*
|
||||
* Find group related to the target statement. We
|
||||
* need it to initialize statement editor.
|
||||
*/
|
||||
for (final Predicate p : uiGroupTable.getItems()) {
|
||||
if (p.test(st)) {
|
||||
uiGroupTable.getSelectionModel().select(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final Iterator<Path.Node> it = newValue.getPropertyPath().iterator();
|
||||
String pName = null;
|
||||
while (it.hasNext()) {
|
||||
pName = it.next().getName();
|
||||
}
|
||||
uiStatementEditor.focusOn(st, pName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a file chooser to allow user to choose a CSV file to open or save.
|
||||
* @param title The title to display on the file chooser header.
|
||||
* @param saveMode True to show file chooser in save mode (see {@link FileChooser#showSaveDialog(javafx.stage.Window) }).
|
||||
* False to use opening mode (See {@link FileChooser#showOpenDialog(javafx.stage.Window) }.
|
||||
*
|
||||
* @return The file selected by user. Can be null if user cancelled.
|
||||
*/
|
||||
private File chooseCSV(final String title, final boolean saveMode) {
|
||||
final FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(title);
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Fichier CSV", "*.csv"));
|
||||
final File previousPath = Rhomeo.getPreviousPath(FXDatasetPane.class);
|
||||
if (previousPath != null) {
|
||||
chooser.setInitialDirectory(previousPath);
|
||||
}
|
||||
|
||||
File result = saveMode?
|
||||
chooser.showSaveDialog(getScene().getWindow()):
|
||||
chooser.showOpenDialog(getScene().getWindow());
|
||||
|
||||
if (result != null) {
|
||||
Rhomeo.setPreviousPath(FXDatasetPane.class, result.getParentFile());
|
||||
if (!CSV_SUFFIX.matcher(result.getName()).find()) {
|
||||
result = new File(result.getParentFile(), result.getName().concat(".csv"));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class Transformer<I, O> implements Iterator<O> {
|
||||
|
||||
final ObjectWrapper<I, O> wrapper;
|
||||
final Iterator<I> source;
|
||||
|
||||
public Transformer(ObjectWrapper<I, O> wrapper, Iterator<I> source) {
|
||||
this.wrapper = wrapper;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return source.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public O next() {
|
||||
return wrapper.apply(source.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditorSpi;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p02.list.PhysionomyFlore;
|
||||
import fr.cenra.rhomeo.protocol.p02.FloreStatement;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXFloreEditor extends AbstractCustomStatementEditor<FloreStatement> {
|
||||
|
||||
@FXML
|
||||
private ComboBox<PhysionomyFlore> uiPhysio;
|
||||
|
||||
public FXFloreEditor() {
|
||||
super(FloreStatement.class);
|
||||
Rhomeo.loadFXML(this, FXFloreEditor.class);
|
||||
|
||||
Rhomeo.initComboBox(uiPhysio, FXCollections.observableList(Arrays.asList(PhysionomyFlore.values())));
|
||||
uiPhysio.valueProperty().addListener(this::physionomyChanged);
|
||||
uiPhysio.setConverter(new StringConverter<PhysionomyFlore>() {
|
||||
@Override
|
||||
public String toString(PhysionomyFlore object) {
|
||||
if (object == null)
|
||||
return null;
|
||||
return new StringBuilder(object.getCode()).append(" - ").append(object.getValue()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PhysionomyFlore fromString(String string) {
|
||||
if (string == null || (string = string.trim()).isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return RhomeoCore.getByCode(PhysionomyFlore.class, string.split("\\s")[0]).orElse(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
if (uiStatementTable != null)
|
||||
uiStatementTable.sortColumns(Arrays.asList("cd_nom", "abundance"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getFieldsToIgnore() {
|
||||
final String[] toIgnore = super.getFieldsToIgnore();
|
||||
final String[] overrided = new String[toIgnore.length + 2];
|
||||
overrided[0] = "physionomy";
|
||||
overrided[1] = "strate";
|
||||
System.arraycopy(toIgnore, 0, overrided, 2, toIgnore.length);
|
||||
return overrided;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FloreStatement initStatement() throws ReflectiveOperationException {
|
||||
final FloreStatement data = super.initStatement();
|
||||
if (uiPhysio.getValue() instanceof PhysionomyFlore) {
|
||||
((FloreStatement) data).setPhysionomy(uiPhysio.getValue().getCode());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* UI LISTENERS
|
||||
*/
|
||||
private void physionomyChanged(final ObservableValue<? extends PhysionomyFlore> obs, final PhysionomyFlore oldPhysio, final PhysionomyFlore newPhysio) {
|
||||
final String newCode = newPhysio == null ? null : newPhysio.getCode();
|
||||
if (uiStatementTable == null || uiStatementTable.getItems() == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < uiStatementTable.getItems().size(); i++) {
|
||||
uiStatementTable.getItems().get(i).physionomyProperty().set(newCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemsChanged(final ObservableValue<? extends List> obs, final List<FloreStatement> oldItems, final List<FloreStatement> newItems) {
|
||||
super.itemsChanged(obs, oldItems, newItems);
|
||||
if (newItems != null && !newItems.isEmpty()) {
|
||||
uiPhysio.setValue(RhomeoCore.getByCode(PhysionomyFlore.class, newItems.get(0).getPhysionomy()).orElse(null));
|
||||
} else {
|
||||
uiPhysio.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class Spi implements StatementEditorSpi<FloreStatement> {
|
||||
|
||||
@Autowired
|
||||
Session session;
|
||||
|
||||
@Override
|
||||
public StatementEditor<FloreStatement> createEditor() {
|
||||
return session.createPrototype(FXFloreEditor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<FloreStatement> getDataType() {
|
||||
return FloreStatement.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditorSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p05.OrthopteraStatement;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Samuel Andrés (Geomatys) <samuel.andres at geomatys.com>
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXOrthopteraEditor extends AbstractCustomStatementEditor<OrthopteraStatement> {
|
||||
|
||||
public FXOrthopteraEditor() {
|
||||
super(OrthopteraStatement.class);
|
||||
Rhomeo.loadFXML(this, FXFloreEditor.class);
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class Spi implements StatementEditorSpi<OrthopteraStatement> {
|
||||
|
||||
@Autowired
|
||||
Session session;
|
||||
|
||||
@Override
|
||||
public StatementEditor<OrthopteraStatement> createEditor() {
|
||||
return session.createPrototype(FXOrthopteraEditor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<OrthopteraStatement> getDataType() {
|
||||
return OrthopteraStatement.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditorSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p07.P07Statement;
|
||||
import java.util.Arrays;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXP07Editor extends AbstractCustomStatementEditor<P07Statement> {
|
||||
|
||||
public FXP07Editor() {
|
||||
super(P07Statement.class);
|
||||
Rhomeo.loadFXML(this, FXFloreEditor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
if (uiStatementTable != null)
|
||||
uiStatementTable.sortColumns(Arrays.asList("cd_nom", "number"));
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class Spi implements StatementEditorSpi<P07Statement> {
|
||||
|
||||
@Autowired
|
||||
Session session;
|
||||
|
||||
@Override
|
||||
public StatementEditor<P07Statement> createEditor() {
|
||||
return session.createPrototype(FXP07Editor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<P07Statement> getDataType() {
|
||||
return P07Statement.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.api.ui.FilterTable;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p01.P01;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.transformation.FilteredList;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.util.Callback;
|
||||
|
||||
/**
|
||||
* A simple table whose aim is to display tracking point information.
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class FXTrackingPointTable extends FilterTable<ByNameAndDateFilter> {
|
||||
|
||||
private static final Callback<TableColumn<ByNameAndDateFilter, LocalDate>, TableCell<ByNameAndDateFilter, LocalDate>> DATE_CELL_FACTORY = FXTrackingPointTable::createDateCell;
|
||||
|
||||
private final Session session;
|
||||
private final TableColumn<ByNameAndDateFilter, Integer> nbColumn;
|
||||
|
||||
private final FilteredList<Statement> dataset;
|
||||
|
||||
public FXTrackingPointTable(final Session session) {
|
||||
this.session = session;
|
||||
final TableColumn<ByNameAndDateFilter, String> nameColumn = new TableColumn("Nom");
|
||||
nameColumn.setCellValueFactory(input -> new SimpleStringProperty(input.getValue().getName()));
|
||||
|
||||
final TableColumn<ByNameAndDateFilter, LocalDate> dateColumn = new TableColumn("Date");
|
||||
dateColumn.setCellValueFactory(input -> (ObservableValue) new SimpleObjectProperty<>(input.getValue().getDate()));
|
||||
dateColumn.setCellFactory(DATE_CELL_FACTORY);
|
||||
|
||||
// HACK for column name (we should not do it that way, but time...)
|
||||
String nbTitle = "Nombre";
|
||||
if (session.getDataContext() != null && session.getDataContext().getProtocol() != null) {
|
||||
final Protocol p = session.getDataContext().getProtocol();
|
||||
if (p instanceof P01)
|
||||
nbTitle = "Nombre d'horizons";
|
||||
else
|
||||
nbTitle = "Nombre d'observations";
|
||||
}
|
||||
nbColumn = new TableColumn(nbTitle);
|
||||
nbColumn.setCellValueFactory(this::getSubsetSize);
|
||||
|
||||
getColumns().addAll(nameColumn, dateColumn, nbColumn);
|
||||
setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
setItems(FXCollections.observableArrayList());
|
||||
|
||||
dataset = session.getDataset().getItems().filtered(null); // Prepare a filtered list to allow user setting filters.
|
||||
dataset.addListener(this::datasetChanged);
|
||||
addFilters(dataset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<? super Statement> getPreFilter() {
|
||||
return dataset.getPredicate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreFilter(final Predicate<Statement> filter) {
|
||||
dataset.setPredicate(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectProperty<Predicate<? super Statement>> preFilterProperty() {
|
||||
return dataset.predicateProperty();
|
||||
}
|
||||
|
||||
private ObservableValue<Integer> getSubsetSize(final TableColumn.CellDataFeatures<ByNameAndDateFilter, Integer> cellFeature) {
|
||||
// If given value and current dataset are not null, we set column value as tracking point subset size.
|
||||
if (cellFeature.getValue() != null && session != null && session.getDataset() != null) {
|
||||
return (ObservableValue) Bindings.size(dataset.filtered(cellFeature.getValue()));
|
||||
} else {
|
||||
return new SimpleObjectProperty<>(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Every time session dataset changes, we update list of available groups.
|
||||
* @param c Changes which occurred on surveyed data.
|
||||
*/
|
||||
private void datasetChanged(final ListChangeListener.Change<? extends Statement> c) {
|
||||
while (c.next()) {
|
||||
if (c.wasUpdated()) {
|
||||
removeObsolete();
|
||||
addFilters(c.getList().subList(c.getFrom(), c.getTo()));
|
||||
} else {
|
||||
if (c.wasRemoved()) {
|
||||
removeObsolete();
|
||||
}
|
||||
|
||||
addFilters(c.getAddedSubList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete from table all useless entries. I.e all entries which don't have
|
||||
* any statement associated anymore, and not being currently edited.
|
||||
*/
|
||||
private void removeObsolete() {
|
||||
/* HACK : To find obsolete groups, we just browse count column, and
|
||||
* delete matching items for each count equal to 0.
|
||||
*/
|
||||
final Iterator<ByNameAndDateFilter> it = getItems().iterator();
|
||||
Integer nb;
|
||||
ByNameAndDateFilter current;
|
||||
while (it.hasNext()) {
|
||||
current = it.next();
|
||||
// Even if the current group is empty, we don't delete
|
||||
// it if user is working with it.
|
||||
if (current.equals(getSelectionModel().getSelectedItem()))
|
||||
continue;
|
||||
nb = nbColumn.getCellData(current);
|
||||
if (nb == null || nb == 0)
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add filters matching given data name/date pairs.
|
||||
* @param dataset Data which will serve to build list of filters to add.
|
||||
*/
|
||||
private void addFilters(final List<? extends Statement> dataset) {
|
||||
if (dataset == null || dataset.isEmpty())
|
||||
return;
|
||||
|
||||
final HashSet<ByNameAndDateFilter> set = new HashSet<>();
|
||||
for (final Statement st : dataset) {
|
||||
set.add(new ByNameAndDateFilter(st.getTrackingPoint(), st.getDate()));
|
||||
}
|
||||
|
||||
synchronized (this) {
|
||||
set.removeAll(getItems()); // Ensure no doublon is added.
|
||||
getItems().addAll(set);
|
||||
}
|
||||
}
|
||||
|
||||
private static TableCell<ByNameAndDateFilter, LocalDate> createDateCell(final TableColumn<ByNameAndDateFilter, LocalDate> column) {
|
||||
return new TableCell<ByNameAndDateFilter, LocalDate>() {
|
||||
@Override
|
||||
protected void updateItem(LocalDate item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty)
|
||||
setText(null);
|
||||
else if (item == null)
|
||||
setText(" - ");
|
||||
else
|
||||
setText(item.toString());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.MethodDescriptor;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class ObjectTable<T> extends TableView<T> {
|
||||
|
||||
private final Class<T> dataType;
|
||||
private final BeanInfo beanInfo;
|
||||
|
||||
/**
|
||||
* Create a new table to expose data type properties.
|
||||
*
|
||||
* @param objectType Type of row item objects.
|
||||
* @param blackList A set of properties to ignore when building table columns.
|
||||
*
|
||||
* @throws IntrospectionException
|
||||
*/
|
||||
public ObjectTable(final Class objectType, String... blackList) throws IntrospectionException {
|
||||
ArgumentChecks.ensureNonNull("Data type", objectType);
|
||||
dataType = objectType;
|
||||
beanInfo = Introspector.getBeanInfo(objectType, Object.class);
|
||||
|
||||
final Set<String> toIgnore;
|
||||
if (blackList == null || blackList.length <= 0) {
|
||||
toIgnore = Collections.EMPTY_SET;
|
||||
} else {
|
||||
toIgnore = new HashSet(Arrays.asList(blackList));
|
||||
}
|
||||
|
||||
// Index methods by name to find them easily
|
||||
final MethodDescriptor[] mds = beanInfo.getMethodDescriptors();
|
||||
final Map<String, Method> methods = new HashMap<>(mds.length);
|
||||
for (final MethodDescriptor md : mds) {
|
||||
methods.put(md.getName(), md.getMethod());
|
||||
}
|
||||
|
||||
final boolean bundleAvailable = InternationalResource.class.isAssignableFrom(dataType);
|
||||
boolean editable = false;
|
||||
for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) {
|
||||
if (toIgnore.contains(desc.getName()))
|
||||
continue;
|
||||
|
||||
try {
|
||||
if (bundleAvailable) {
|
||||
try {
|
||||
desc.setDisplayName(InternationalResource.getResourceString((Class) dataType, desc.getName(), "label"));
|
||||
desc.setShortDescription(InternationalResource.getResourceString((Class) dataType, desc.getName(), "tooltip"));
|
||||
} catch (Exception e) {
|
||||
RhomeoCore.LOGGER.log(Level.FINE, "No bundle available for property ".concat(desc.getName()), e);
|
||||
}
|
||||
}
|
||||
final PropertyColumn<T> col = new PropertyColumn<>(desc, methods.get(desc.getName().concat("Property")));
|
||||
if (col.isEditable())
|
||||
editable = true;
|
||||
getColumns().add(col);
|
||||
} catch (IllegalArgumentException e) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot create a table column for property ".concat(desc.getName()), e);
|
||||
}
|
||||
}
|
||||
|
||||
setEditable(editable);
|
||||
setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to sort table columns according to the names given in input list. Names
|
||||
* in input list must be a property name, or (only for columns not working with object properties)
|
||||
* the text of the column. Order in the list will define the order of the columns.
|
||||
*
|
||||
* @param orderedNames Order to apply on this table columns.
|
||||
*/
|
||||
public void sortColumns(final String... orderedNames) {
|
||||
if (orderedNames == null || orderedNames.length < 1)
|
||||
return;
|
||||
sortColumns(Arrays.asList(orderedNames));
|
||||
}
|
||||
|
||||
public void sortColumns(final List<String> orderedNames) {
|
||||
if (orderedNames == null || orderedNames.isEmpty())
|
||||
return;
|
||||
|
||||
getColumns().sort((TableColumn<T, ?> o1, TableColumn<T, ?> o2) -> {
|
||||
final String firstName, secondName;
|
||||
if (o1 instanceof PropertyColumn) {
|
||||
firstName = ((PropertyColumn)o1).descriptor.getName();
|
||||
} else {
|
||||
firstName = o1.getText();
|
||||
}
|
||||
if (o2 instanceof PropertyColumn) {
|
||||
secondName = ((PropertyColumn)o2).descriptor.getName();
|
||||
} else {
|
||||
secondName = o2.getText();
|
||||
}
|
||||
|
||||
final int firstOccur = orderedNames.indexOf(firstName);
|
||||
final int secondOccur = orderedNames.indexOf(secondName);
|
||||
// Second property not in the given list. No change in ordering.
|
||||
if (secondOccur < 0)
|
||||
return -1;
|
||||
// First property not found. Moved to the back.
|
||||
else if (firstOccur < 0)
|
||||
return 1;
|
||||
else
|
||||
return orderedNames.indexOf(firstName) - orderedNames.indexOf(secondName);
|
||||
});
|
||||
}
|
||||
|
||||
public Class<T> getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public void focusOn(T item, String propertyName) {
|
||||
final int rowIndex = getItems().indexOf(item);
|
||||
if (rowIndex >= 0) {
|
||||
if (propertyName == null || (propertyName = propertyName.trim()).isEmpty()) {
|
||||
getSelectionModel().select(item);// No property given. Select row.
|
||||
} else {
|
||||
// Try to find the column of the property, to focus / edit queried cell.
|
||||
for (final TableColumn col : getColumns()) {
|
||||
if (col instanceof PropertyColumn && ((PropertyColumn) col).descriptor.getName().equals(propertyName)) {
|
||||
if (col.isEditable()) {
|
||||
edit(rowIndex, col);
|
||||
} else {
|
||||
getFocusModel().focus(rowIndex, col);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditorSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p01.PedologyStatement;
|
||||
import java.util.ResourceBundle;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TableView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class P01Editor extends AbstractCustomStatementEditor<PedologyStatement> {
|
||||
|
||||
public P01Editor() {
|
||||
super(PedologyStatement.class);
|
||||
Rhomeo.loadFXML(this, P01Editor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
super.init();
|
||||
if (uiStatementTable != null) {
|
||||
uiStatementTable.setMinHeight(30);
|
||||
uiStatementTable.setMaxSize(Double.MAX_VALUE, USE_COMPUTED_SIZE);
|
||||
uiStatementTable.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
|
||||
uiStatementTable.sortColumns("numberH", "depth", "limits", "color", "value", "chroma", "texture", "structure", "rawElements", "roots", "stains", "abundance", "size", "shape", "humidity", "compactness", "plasticity", "adhesiveness", "friability", "alterationMo", "vonPost");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteStatements(ActionEvent evt) {
|
||||
final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, ResourceBundle.getBundle(P01Editor.class.getCanonicalName()).getString("statement.delete.confirm"), ButtonType.NO, ButtonType.YES);
|
||||
alert.setResizable(true);
|
||||
if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) {
|
||||
super.deleteStatements(evt);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class Spi implements StatementEditorSpi<PedologyStatement> {
|
||||
|
||||
@Autowired
|
||||
Session session;
|
||||
|
||||
@Override
|
||||
public StatementEditor<PedologyStatement> createEditor() {
|
||||
return session.createPrototype(P01Editor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<PedologyStatement> getDataType() {
|
||||
return PedologyStatement.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Dataset;
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.process.ProcessContext;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import static fr.cenra.rhomeo.fx.edition.FXDatasetPane.setProcessContext;
|
||||
import fr.cenra.rhomeo.protocol.p04.I04;
|
||||
import fr.cenra.rhomeo.protocol.p04.I07;
|
||||
import fr.cenra.rhomeo.protocol.p04.P04Statement;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.DoubleBinding;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validator;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class P04Editor extends BorderPane {
|
||||
|
||||
@FXML
|
||||
private Button uiFinalize;
|
||||
|
||||
@FXML
|
||||
private BorderPane uiTableContainer;
|
||||
|
||||
@FXML
|
||||
private Button uiDelete;
|
||||
|
||||
private final Session session;
|
||||
|
||||
private final StatementTable<P04Statement> table;
|
||||
DoubleBinding sizeBinding = null;
|
||||
|
||||
public P04Editor(final Session session) throws IntrospectionException {
|
||||
ArgumentChecks.ensureNonNull("Session", session);
|
||||
this.session = session;
|
||||
Rhomeo.loadFXML(this);
|
||||
table = new StatementTable<>(P04Statement.class, "trackingPoint", "date", "remarks");
|
||||
table.sortColumns("year", "habitat", "humine", "acids", "pt", "cot");
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
uiTableContainer.setCenter(table);
|
||||
if (session.getDataset() != null) {
|
||||
table.setItems(session.getDataset().getItems());
|
||||
table.setFixedCellSize(32);
|
||||
sizeBinding = Bindings.createDoubleBinding(() -> {
|
||||
final int elementNb = table.getItems().size() < 1? 1 : table.getItems().size();
|
||||
return table.getFixedCellSize() * elementNb + 30; // Header size is not counted in element numbers, so we add a hard-coded size for it.
|
||||
}, table.getItems(), table.fixedCellSizeProperty());
|
||||
table.prefHeightProperty().bind(sizeBinding);
|
||||
}
|
||||
|
||||
uiDelete.disableProperty().bind(table.getSelectionModel().selectedItemProperty().isNull());
|
||||
uiFinalize.disableProperty().bind(Bindings.isEmpty(table.getItems()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
void addStatement(ActionEvent event) throws ReflectiveOperationException {
|
||||
session.getDataset().getItems().add(session.getDataContext().getProtocol().getDataType().newInstance());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void deleteSelection(ActionEvent event) {
|
||||
// Defensive copy
|
||||
final Collection<Statement> selectedItems = new ArrayList<>(table.getSelectionModel().getSelectedItems());
|
||||
// Delete strict reference, because if the user has added multiple new
|
||||
// objects in the table, they could be equal.
|
||||
if (!selectedItems.isEmpty()) {
|
||||
session.getDataset().getItems().removeIf(item -> {
|
||||
for (final Statement selected : selectedItems) {
|
||||
if (selected == item)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void validate(ActionEvent event) {
|
||||
final Validator validator = session.getBean(Validator.class);
|
||||
final Set<ConstraintViolation<Dataset>> result = validator.validate(session.getDataset());
|
||||
if (result != null && !result.isEmpty()) {
|
||||
final StringBuilder builder = new StringBuilder("La saisie n'est pas valide : ");
|
||||
int index;
|
||||
for (final ConstraintViolation cv : result) {
|
||||
builder.append(System.lineSeparator()).append(" - ");
|
||||
index = -1;
|
||||
final Object leafBean = cv.getLeafBean();
|
||||
for (int i = 0 ; i < table.getItems().size() ; i++) {
|
||||
if (leafBean == table.getItems().get(i)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= 0) {
|
||||
builder.append("Ligne ").append(index + 1).append(" : ");
|
||||
}
|
||||
builder.append(cv.getMessage());
|
||||
}
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, builder.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get P04 indicators
|
||||
Indicator i04 = null;
|
||||
Indicator i07 = null;
|
||||
for (final Indicator i : session.getIndicators()) {
|
||||
if (I04.NAME.equals(i.getName())) {
|
||||
i04 = i;
|
||||
if (i07 != null)
|
||||
break;
|
||||
} else if (I07.NAME.equals(i.getName())) {
|
||||
i07 = i;
|
||||
if (i04 != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Ask indicators to convert seized data into indices.
|
||||
final List<fr.cenra.rhomeo.api.process.Process> processes = new ArrayList<>();
|
||||
if (i04 != null) {
|
||||
i04.createProcesses(new ProcessContext(session.getDataset(), session.getDataContext())).forEach(p -> {
|
||||
TaskManager.INSTANCE.submit(p);
|
||||
processes.add(p);
|
||||
});
|
||||
}
|
||||
|
||||
if (i07 != null) {
|
||||
i07.createProcesses(new ProcessContext(session.getDataset(), session.getDataContext())).forEach(p -> {
|
||||
TaskManager.INSTANCE.submit(p);
|
||||
processes.add(p);
|
||||
});
|
||||
}
|
||||
|
||||
// Aggregate / sort results.
|
||||
final Task<Set<Index>> t = new TaskManager.MockTask("Traitement des résultats...", () -> {
|
||||
Set<Index> indices = new HashSet<>();
|
||||
for (final fr.cenra.rhomeo.api.process.Process p : processes) {
|
||||
try {
|
||||
indices.addAll(p.get());
|
||||
} catch (Exception ex) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "A process failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
});
|
||||
|
||||
// Proceed to finalization.
|
||||
t.setOnSucceeded((evt) -> {
|
||||
session.setResults(t.getValue());
|
||||
session.requestWorkflowStep(WorkflowStep.FINALIZATION);
|
||||
});
|
||||
|
||||
t.setOnFailed((evt) -> Rhomeo.showAlert(Alert.AlertType.WARNING, "Une erreur s'est produite. Impossible de procéder à la finalisation."));
|
||||
|
||||
TaskManager.INSTANCE.submit(t);
|
||||
|
||||
setProcessContext(session);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.protocol.p04.P04;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import javafx.scene.Node;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class P04EditorSpi implements ProtocolEditorSpi {
|
||||
|
||||
@Autowired
|
||||
@Qualifier(P04.NAME)
|
||||
Protocol p04;
|
||||
|
||||
@Autowired
|
||||
Session session;
|
||||
|
||||
final Set<WorkflowStep> toIgnore;
|
||||
|
||||
private P04EditorSpi() {
|
||||
final HashSet<WorkflowStep> steps = new HashSet<>(2);
|
||||
steps.add(WorkflowStep.PROCESS);
|
||||
steps.add(WorkflowStep.RESULT);
|
||||
toIgnore = Collections.unmodifiableSet(steps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol() {
|
||||
return p04;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getEditor(WorkflowStep step) {
|
||||
Node result = null;
|
||||
switch (step) {
|
||||
case DATASET: {
|
||||
try {
|
||||
result = new P04Editor(session);
|
||||
} catch (IntrospectionException ex) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "Cannot initialize edition panel for P04", ex);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PROCESS:
|
||||
case RESULT:
|
||||
throw new IllegalStateException("Queried step is invalid for this protocol wizard !");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<WorkflowStep> getStepsToIgnore() {
|
||||
return toIgnore;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditor;
|
||||
import fr.cenra.rhomeo.api.ui.StatementEditorSpi;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.protocol.p06.Habitat;
|
||||
import fr.cenra.rhomeo.protocol.p06.OdonataStatement;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class P06Editor extends AbstractCustomStatementEditor<OdonataStatement> {
|
||||
|
||||
@FXML
|
||||
private ComboBox<Habitat> uiHabitat;
|
||||
|
||||
public P06Editor() {
|
||||
super(OdonataStatement.class);
|
||||
Rhomeo.loadFXML(this, P06Editor.class);
|
||||
|
||||
Rhomeo.initComboBox(uiHabitat, FXCollections.observableList(Arrays.asList(Habitat.values())));
|
||||
uiHabitat.valueProperty().addListener(this::habitatChanged);
|
||||
uiHabitat.setConverter(new StringConverter<Habitat>() {
|
||||
@Override
|
||||
public String toString(Habitat object) {
|
||||
if (object == null)
|
||||
return null;
|
||||
return new StringBuilder(object.getCode()).append(" - ").append(object.getValue()).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Habitat fromString(String string) {
|
||||
if (string == null || (string = string.trim()).isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return RhomeoCore.getByCode(Habitat.class, string.split("\\s")[0]).orElse(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(){
|
||||
super.init();
|
||||
if (uiStatementTable != null)
|
||||
uiStatementTable.sortColumns(Arrays.asList("cd_nom", "behavior"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getFieldsToIgnore() {
|
||||
String[] fieldsToIgnore = super.getFieldsToIgnore();
|
||||
fieldsToIgnore = Arrays.copyOf(fieldsToIgnore, fieldsToIgnore.length +1);
|
||||
fieldsToIgnore[fieldsToIgnore.length -1] = "habitat";
|
||||
return fieldsToIgnore;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected OdonataStatement initStatement() throws ReflectiveOperationException {
|
||||
final OdonataStatement data = super.initStatement();
|
||||
if (uiHabitat.getValue() instanceof Habitat) {
|
||||
data.setHabitat(uiHabitat.getValue().getCode());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* UI LISTENERS
|
||||
*/
|
||||
|
||||
private void habitatChanged(final ObservableValue<? extends Habitat> obs, final Habitat oldPhysio, final Habitat newPhysio) {
|
||||
final String newCode = newPhysio == null? null : newPhysio.getCode();
|
||||
if (uiStatementTable == null || uiStatementTable.getItems() == null)
|
||||
return;
|
||||
for (int i = 0 ; i < uiStatementTable.getItems().size() ; i++) {
|
||||
uiStatementTable.getItems().get(i).habitatProperty().set(newCode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemsChanged(final ObservableValue<? extends List> obs, final List<OdonataStatement> oldItems, final List<OdonataStatement> newItems) {
|
||||
super.itemsChanged(obs, oldItems, newItems);
|
||||
|
||||
if (newItems == null || newItems.isEmpty()) {
|
||||
uiHabitat.setValue(null);
|
||||
} else {
|
||||
uiHabitat.setValue(RhomeoCore.getByCode(Habitat.class, newItems.get(0).getHabitat()).orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class Spi implements StatementEditorSpi<OdonataStatement> {
|
||||
|
||||
@Autowired
|
||||
Session session;
|
||||
|
||||
@Override
|
||||
public StatementEditor<OdonataStatement> createEditor() {
|
||||
return session.createPrototype(P06Editor.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<OdonataStatement> getDataType() {
|
||||
return OdonataStatement.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,728 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import com.vividsolutions.jts.geom.Geometry;
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.Version;
|
||||
import fr.cenra.rhomeo.api.annotations.RefersTo;
|
||||
import fr.cenra.rhomeo.api.data.Reference;
|
||||
import fr.cenra.rhomeo.api.data.Warning;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.data.reference.Taxref;
|
||||
import fr.cenra.rhomeo.core.util.RelationResolver;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WritableValue;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TablePosition;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.util.StringConverter;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validator;
|
||||
import org.apache.sis.parameter.ParameterBuilder;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.apache.sis.util.Numbers;
|
||||
import org.geotoolkit.font.FontAwesomeIcons;
|
||||
import org.geotoolkit.gui.javafx.parameter.FXValueEditor;
|
||||
import org.geotoolkit.gui.javafx.parameter.FXValueEditorSpi;
|
||||
import org.geotoolkit.gui.javafx.util.ComboBoxCompletion;
|
||||
import org.opengis.parameter.ParameterDescriptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* A column whose aim is to display and offer edition capabilities on a specific
|
||||
* POJO property. The display / edition capabilities are computed using reflection.
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
* @param <T> Type of object found in a row of the parent table.
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class PropertyColumn<T> extends TableColumn<T, Object> {
|
||||
|
||||
static final KeyHandler KEY_HANDLER = new KeyHandler();
|
||||
|
||||
static final StringConverter BASIC_CONVERTER = new BasicConverter();
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@Autowired
|
||||
RelationResolver resolver;
|
||||
|
||||
/**
|
||||
* Descriptor of the property to manage in the column (property extracted
|
||||
* from a row item).
|
||||
*/
|
||||
final PropertyDescriptor descriptor;
|
||||
/**
|
||||
* A boolean indicating if {@link #observableGetter} implements {@link WritableValue}
|
||||
* interface.
|
||||
*/
|
||||
final boolean isWritableValue;
|
||||
/**
|
||||
* A method extracted from input {@link PropertyDescriptor} to write target
|
||||
* property.
|
||||
*/
|
||||
final Method writeMethod;
|
||||
/**
|
||||
* A method extracted from input {@link PropertyDescriptor} to read target
|
||||
* property.
|
||||
*/
|
||||
final Method readMethod;
|
||||
/**
|
||||
* A method with no parameter, whose aim is to send back an observable
|
||||
* object on target property.
|
||||
*/
|
||||
final Method observableGetter;
|
||||
/**
|
||||
* An annotation extracted from getters to know if the property to manage
|
||||
* is referring to another object.
|
||||
*/
|
||||
final RefersTo referent;
|
||||
|
||||
public final SimpleObjectProperty<FXValueEditor> editorProperty = new SimpleObjectProperty<>();
|
||||
|
||||
// Hack for issue #75
|
||||
final boolean isSpecies;
|
||||
|
||||
/**
|
||||
* A weak bound to the converter used for transformation of column values
|
||||
* in text. We use weak reference here mainly because of application
|
||||
* {@link Reference} which are loaded asynchronously (user can change used
|
||||
* version, etc.). Also, it avoid to keep a (possibly) consequent memory
|
||||
* chunk when not needed.
|
||||
*/
|
||||
private WeakReference<StringConverter> textConverterRef;
|
||||
|
||||
public PropertyColumn(final PropertyDescriptor desc, final Method observableGetter) {
|
||||
super();
|
||||
ArgumentChecks.ensureNonNull("Property descriptor", desc);
|
||||
Session.getInstance().injectDependencies(this);
|
||||
descriptor = desc;
|
||||
writeMethod = desc.getWriteMethod();
|
||||
readMethod = desc.getReadMethod();
|
||||
this.observableGetter = observableGetter;
|
||||
/*
|
||||
* Analyze given parameters to determine how to handle cell content.
|
||||
*/
|
||||
boolean tmpIsProperty = false;
|
||||
boolean isObservableValue = false;
|
||||
if (observableGetter != null) {
|
||||
Class<?> observableType = observableGetter.getReturnType();
|
||||
if (WritableValue.class.isAssignableFrom(observableType)) {
|
||||
tmpIsProperty = true;
|
||||
} else if (ObservableValue.class.isAssignableFrom(observableType)) {
|
||||
isObservableValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
isWritableValue = tmpIsProperty;
|
||||
|
||||
if (isWritableValue || isObservableValue) {
|
||||
setCellValueFactory((param) -> {
|
||||
try {
|
||||
return (ObservableValue) observableGetter.invoke(param.getValue());
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot acces observable property ".concat(desc.getName()), ex);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
} else if (readMethod != null) {
|
||||
readMethod.setAccessible(true);
|
||||
setCellValueFactory((param) -> {
|
||||
try {
|
||||
return new SimpleObjectProperty<>(readMethod.invoke(param.getValue()));
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot acces property ".concat(desc.getName()), ex);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot extract values from given property descriptor !");
|
||||
}
|
||||
|
||||
RefersTo tmpReferent = null;
|
||||
if (readMethod != null) {
|
||||
tmpReferent = readMethod.getAnnotation(RefersTo.class);
|
||||
}
|
||||
|
||||
if (tmpReferent == null && observableGetter != null) {
|
||||
tmpReferent = observableGetter.getAnnotation(RefersTo.class);
|
||||
}
|
||||
referent = tmpReferent;
|
||||
|
||||
// Hack #75
|
||||
isSpecies = referent == null? false : referent.property().equals(Taxref.ATT_CD_NOM);
|
||||
|
||||
/* When editing a property pointing on a reference, we listen on data
|
||||
* context changes to update choice list when user change the version
|
||||
* used.
|
||||
*/
|
||||
if (referent != null && Reference.class.isAssignableFrom(referent.type())) {
|
||||
try {
|
||||
Session.getInstance().getDataContext().getReferences().addListener((MapChangeListener.Change<? extends Class<Reference>, ? extends Version> change) -> {
|
||||
if (referent.type().equals(change.getKey())) {
|
||||
initEditor();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.FINE, "Cannot listen on data context changes.", e);
|
||||
}
|
||||
}
|
||||
|
||||
initEditor();
|
||||
|
||||
setCellFactory(this::createPropertyCell);
|
||||
|
||||
editableProperty().bind(editorProperty.isNotNull());
|
||||
setText(desc.getDisplayName());
|
||||
|
||||
String remarks = desc.getShortDescription();
|
||||
if (remarks != null && !remarks.equals(desc.getDisplayName()) && !(remarks = remarks.trim()).isEmpty()) {
|
||||
final Label label = new Label(FontAwesomeIcons.ICON_INFO_CIRCLE);
|
||||
label.setTooltip(new Tooltip(remarks));
|
||||
label.setStyle("-fx-font-family: FontAwesome; -fx-font-size:16;");
|
||||
setGraphic(label);
|
||||
}
|
||||
|
||||
// # 86 : Compare species using their name
|
||||
if (isSpecies) {
|
||||
setComparator((o1, o2) -> {
|
||||
if (o1 == o2) // both null or same reference
|
||||
return 0;
|
||||
else if (o1 == null)
|
||||
return 1;
|
||||
else if (o2 == null)
|
||||
return -1;
|
||||
else if (o1.equals(o2))
|
||||
return 0;
|
||||
|
||||
final StringConverter converter = getOrCreateTextConverter();
|
||||
final String s1 = converter.toString(o1);
|
||||
final String s2 = converter.toString(o2);
|
||||
if (s1 == s2) // both null or same reference
|
||||
return 0;
|
||||
else if (s1 == null)
|
||||
return 1;
|
||||
else if (s2 == null)
|
||||
return -1;
|
||||
|
||||
return s1.compareTo(s2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void initEditor() {
|
||||
/* If there is writing possibility, we try to setup an editor for the
|
||||
* column cells.
|
||||
*/
|
||||
if (isWritableValue || writeMethod != null) {
|
||||
Set possibleValues = null;
|
||||
if (referent != null) {
|
||||
try {
|
||||
possibleValues = resolver.getPossibleValues(referent);
|
||||
} catch (Exception ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot load choice list for property ".concat(descriptor.getName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
final ParameterDescriptor parameterDescriptor;
|
||||
final ParameterBuilder builder = new ParameterBuilder().addName(descriptor.getName()).setRequired(false);
|
||||
if (possibleValues != null) {
|
||||
parameterDescriptor = builder.createEnumerated((Class) Numbers.primitiveToWrapper(descriptor.getPropertyType()), possibleValues.toArray(), null);
|
||||
} else {
|
||||
parameterDescriptor = builder.create(descriptor.getPropertyType(), null);
|
||||
}
|
||||
|
||||
Optional<FXValueEditor> editor = FXValueEditorSpi.findEditor(parameterDescriptor);
|
||||
if (editor.isPresent()) {
|
||||
// If given editor is a combo box, we activate auto-completion on it.
|
||||
final FXValueEditor tmpEditor = editor.get();
|
||||
Optional<ComboBox> optCombo = Rhomeo.findComboBox(tmpEditor.getComponent());
|
||||
if (optCombo.isPresent()) {
|
||||
ComboBox cBox = optCombo.get();
|
||||
// See ComboBoxListViewSkin
|
||||
cBox.getProperties().put("comboBoxRowsToMeasureWidth", 7);
|
||||
setConverter(cBox);
|
||||
try {
|
||||
cBox.setEditable(true);
|
||||
} catch (RuntimeException e) {
|
||||
// A runtime exception is launched if the editable property is already bound
|
||||
// Just log and let the property bound and handled by something else.
|
||||
Rhomeo.LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
|
||||
}
|
||||
ComboBoxCompletion.autocomplete(cBox);
|
||||
// HACK : Force auto-completion to reset when starting edition.
|
||||
cBox.focusedProperty().addListener((obs, oldVal, newVal) -> {
|
||||
if (newVal)
|
||||
cBox.fireEvent(new KeyEvent(KeyEvent.KEY_RELEASED, "", "", KeyCode.UNDEFINED, false, false, false, false));
|
||||
});
|
||||
} else {
|
||||
Rhomeo.prepareSpinners(tmpEditor.getComponent());
|
||||
}
|
||||
editorProperty.set(tmpEditor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyCell createPropertyCell(TableColumn<T, Object> column) {
|
||||
return new PropertyCell();
|
||||
}
|
||||
|
||||
public PropertyDescriptor getTarget() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
private void setConverter(ComboBox cBox) {
|
||||
final StringConverter baseConverter = getOrCreateTextConverter();
|
||||
final StringConverter converter = new StringConverter() {
|
||||
@Override
|
||||
public String toString(Object object) {
|
||||
return baseConverter.toString(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromString(String string) {
|
||||
if (string == null || string.isEmpty())
|
||||
return null;
|
||||
final Object firstGuess = baseConverter.fromString(string);
|
||||
if (firstGuess == null) {
|
||||
for (final Object o : cBox.getItems()) {
|
||||
if (Objects.equals(string, o.toString()))
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
cBox.setConverter(converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a converter for the elements of this column.
|
||||
* @return A converter to display this column elements as text. Never null.
|
||||
*/
|
||||
private StringConverter getOrCreateTextConverter() {
|
||||
StringConverter converter = textConverterRef == null? null : textConverterRef.get();
|
||||
if (converter != null)
|
||||
return converter;
|
||||
|
||||
if (referent != null) {
|
||||
try {
|
||||
final Set targets = resolver.getTargetObjects(referent);
|
||||
final Function extractor = RelationResolver.acquireExtractor(referent);
|
||||
final HashMap<Object, String> refMap = new HashMap<>(targets.size());
|
||||
for (final Object o : targets) {
|
||||
refMap.put(extractor.apply(o), o.toString());
|
||||
}
|
||||
|
||||
converter = new StringConverter() {
|
||||
@Override
|
||||
public String toString(Object object) {
|
||||
if (object == null || (isSpecies && object.equals(0)))
|
||||
return null;
|
||||
final String str = refMap.get(object);
|
||||
if (str != null)
|
||||
return str;
|
||||
return BASIC_CONVERTER.toString(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromString(String string) {
|
||||
if (string == null || string.isEmpty())
|
||||
return null;
|
||||
|
||||
for (final Map.Entry<Object, String> entry : refMap.entrySet()) {
|
||||
if (string.equals(entry.getValue())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
return BASIC_CONVERTER.fromString(string);
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.FINE, "Cannot tune combobox cells.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (converter == null) {
|
||||
return BASIC_CONVERTER;
|
||||
} else {
|
||||
textConverterRef = new WeakReference<>(converter);
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A cell for rendering and edition of a property.
|
||||
*/
|
||||
private class PropertyCell extends AutoCommitableTableCell<T, Object> {
|
||||
|
||||
/**
|
||||
* A boolean to know if we're in edition state or not. We store a local
|
||||
* variable here, because when calling {@link TableCell#startEdit() },
|
||||
* the edition flag is updated AFTER item update. If we do not want to
|
||||
* update the view twice on state change, we have to locally store edition
|
||||
* state, then calling super.startEdit().
|
||||
*/
|
||||
boolean edition = false;
|
||||
|
||||
EventHandler<MouseEvent> enteredOnError;
|
||||
|
||||
final ChangeListener<Boolean> focusListener = this::focusChanged;
|
||||
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
edition = false;
|
||||
super.cancelEdit();
|
||||
updateItem(getTableColumn().getCellData(getTableRow().getIndex()), false);
|
||||
removeKeyHandling();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitEdit() {
|
||||
commitEdit(editorProperty.get().valueProperty().getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitEdit(Object newValue) {
|
||||
if (isWritableValue) {
|
||||
WritableValue property = (WritableValue) getTableColumn().getCellObservableValue(getTableRow().getIndex());
|
||||
property.setValue(newValue);
|
||||
} else {
|
||||
try {
|
||||
writeMethod.invoke(getTableRow().getItem(), newValue);
|
||||
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot commit value !", ex);
|
||||
}
|
||||
}
|
||||
|
||||
edition = false;
|
||||
super.commitEdit(newValue);
|
||||
removeKeyHandling();
|
||||
editorProperty.get().getComponent().removeEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEdit() {
|
||||
edition = true; // Set to true for view update.
|
||||
super.startEdit();
|
||||
if (!(edition = isEditing()))
|
||||
return;
|
||||
|
||||
final FXValueEditor editor = editorProperty.get();
|
||||
if (editor == null ||
|
||||
!(isWritableValue || writeMethod != null)) {
|
||||
throw new IllegalStateException("Cannot start edition on not editable cell.");
|
||||
}
|
||||
|
||||
// Init editor value from cell content.
|
||||
editor.valueProperty().setValue(getItem());
|
||||
|
||||
addKeyHandling();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Object item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (edition && !empty) {
|
||||
setText(null);
|
||||
Node node = editorProperty.get().getComponent();
|
||||
if (node instanceof Region) {
|
||||
final Region r = (Region) node;
|
||||
r.setMinSize(0, 0);
|
||||
r.prefWidthProperty().bind(prefWidthProperty());
|
||||
r.prefHeightProperty().bind(prefHeightProperty());
|
||||
}
|
||||
setGraphic(node);
|
||||
Optional<ComboBox> opt = Rhomeo.findComboBox(node);
|
||||
if (opt.isPresent()) {
|
||||
node = opt.get();
|
||||
}
|
||||
|
||||
node.requestFocus();
|
||||
node.focusedProperty().addListener(focusListener);
|
||||
|
||||
} else {
|
||||
removeGraphic();
|
||||
if (empty) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(getOrCreateTextConverter().toString(item));
|
||||
}
|
||||
}
|
||||
|
||||
checkValue(item, empty);
|
||||
}
|
||||
|
||||
private void focusChanged(final ObservableValue<? extends Boolean> obs, final Boolean oldValue, final boolean newValue) {
|
||||
if (edition && !newValue) {
|
||||
commitEdit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the graphic attached to the cell, freeing its previously bound
|
||||
* properties in the same time.
|
||||
*/
|
||||
private void removeGraphic() {
|
||||
Node graphic = getGraphic();
|
||||
if (graphic != null) {
|
||||
if (graphic instanceof Region) {
|
||||
final Region r = (Region) graphic;
|
||||
r.prefWidthProperty().unbind();
|
||||
r.prefHeightProperty().unbind();
|
||||
}
|
||||
|
||||
Optional<ComboBox> opt = Rhomeo.findComboBox(graphic);
|
||||
if (opt.isPresent()) {
|
||||
graphic = opt.get();
|
||||
}
|
||||
graphic.focusedProperty().removeListener(focusListener);
|
||||
}
|
||||
setGraphic(null);
|
||||
}
|
||||
|
||||
public void checkValue(final Object value, final boolean isEmpty) {
|
||||
final Object rowItem;
|
||||
if (isEmpty || getTableRow() == null) {
|
||||
rowItem = null;
|
||||
} else {
|
||||
rowItem = getTableRow().getItem();
|
||||
}
|
||||
|
||||
final Set<ConstraintViolation> result;
|
||||
if (rowItem == null) { // Empty cell
|
||||
result = Collections.EMPTY_SET;
|
||||
} else {
|
||||
result = validator.validateValue((Class) rowItem.getClass(),
|
||||
PropertyColumn.this.descriptor.getName(),
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
int warningNumber = 0;
|
||||
for (final ConstraintViolation cv : result) {
|
||||
if (cv.getConstraintDescriptor().getPayload().contains(Warning.class))
|
||||
warningNumber++;
|
||||
}
|
||||
|
||||
if (warningNumber >= result.size()) {
|
||||
if (!getStyleClass().contains(Rhomeo.CSS_WARNING_FIELD)) {
|
||||
getStyleClass().add(Rhomeo.CSS_WARNING_FIELD);
|
||||
}
|
||||
} else if (!getStyleClass().contains(Rhomeo.CSS_ERROR_FIELD)) {
|
||||
getStyleClass().add(Rhomeo.CSS_ERROR_FIELD);
|
||||
}
|
||||
|
||||
// Activate a tootltip to give user information about what happens.
|
||||
createErrorTooltip(result).ifPresent(tip -> setTooltip(tip));
|
||||
} else {
|
||||
setTooltip(null);
|
||||
getStyleClass().removeAll(Rhomeo.CSS_ERROR_FIELD, Rhomeo.CSS_WARNING_FIELD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tooltip listing error message of input violations.
|
||||
* @param errors Errors to display.
|
||||
* @return An empty optional if input list was null or empty. A tooltip
|
||||
* with input error messages otherwise.
|
||||
*/
|
||||
private Optional<Tooltip> createErrorTooltip(final Collection<ConstraintViolation> errors) {
|
||||
if (errors == null || errors.isEmpty())
|
||||
return Optional.empty();
|
||||
final String text;
|
||||
if (errors.size() == 1) {
|
||||
text = errors.iterator().next().getMessage();
|
||||
} else {
|
||||
final Iterator<ConstraintViolation> it = errors.iterator();
|
||||
final StringBuilder builder = new StringBuilder("∙ ");
|
||||
if (it.hasNext())
|
||||
builder.append(it.next().getMessage());
|
||||
while (it.hasNext()) {
|
||||
builder.append(System.lineSeparator()).append("∙ ").append(it.next().getMessage());
|
||||
}
|
||||
text = builder.toString();
|
||||
}
|
||||
|
||||
final Tooltip tip = new Tooltip(text);
|
||||
tip.getStyleClass().add("info-popup");
|
||||
|
||||
return Optional.of(tip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link #KEY_HANDLER} to listen on key events on the given
|
||||
* cell. The event listener is meant to listen for actions on a cell
|
||||
* being in edition mode.
|
||||
*
|
||||
* Note : If the handler was already listening on another cell, its
|
||||
* unregistered before setting input cell.
|
||||
*
|
||||
* @param target The table cell to activate Key event handling on.
|
||||
*/
|
||||
private void addKeyHandling() {
|
||||
if (KEY_HANDLER.target != null) {
|
||||
removeKeyHandling();
|
||||
}
|
||||
|
||||
KEY_HANDLER.target = this;
|
||||
addEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER);
|
||||
addEventFilter(KeyEvent.ANY, KEY_HANDLER);
|
||||
editorProperty.get().getComponent().addEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link #KEY_HANDLER} to stop listening on given cell.
|
||||
*
|
||||
* @param target The cell to deactivate key event handling for.
|
||||
*/
|
||||
private void removeKeyHandling() {
|
||||
if (KEY_HANDLER != null && KEY_HANDLER.target != null) {
|
||||
KEY_HANDLER.target.removeEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER);
|
||||
KEY_HANDLER.target.removeEventFilter(KeyEvent.ANY, KEY_HANDLER);
|
||||
editorProperty.get().getComponent().removeEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER);
|
||||
KEY_HANDLER.target = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on keyboard key release on a given table cell. Default listeners are :
|
||||
* - ENTER : commit edit
|
||||
* - TAB : commit edit then edit next column. If no more column is available,
|
||||
* start editing first column of the next row.
|
||||
* - ESC : cancel edit.
|
||||
*/
|
||||
static class KeyHandler implements EventHandler<KeyEvent> {
|
||||
|
||||
AutoCommitableTableCell target;
|
||||
|
||||
@Override
|
||||
public void handle(KeyEvent evt) {
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
if (KeyEvent.KEY_RELEASED.equals(evt.getEventType())) {
|
||||
switch (evt.getCode()) {
|
||||
case ENTER:
|
||||
target.commitEdit();
|
||||
evt.consume();
|
||||
break;
|
||||
case TAB:
|
||||
final TableView tableView = target.getTableView();
|
||||
TablePosition currentCell = tableView.getEditingCell();
|
||||
if (currentCell == null)
|
||||
currentCell = new TablePosition(tableView, 0, null);
|
||||
target.commitEdit();
|
||||
Rhomeo.editNextCell(currentCell);
|
||||
evt.consume();
|
||||
break;
|
||||
case ESCAPE:
|
||||
target.cancelEdit();
|
||||
evt.consume();
|
||||
}
|
||||
} else {
|
||||
// Filter events
|
||||
switch (evt.getCode()) {
|
||||
case TAB:
|
||||
evt.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BasicConverter extends StringConverter<Object> {
|
||||
|
||||
@Override
|
||||
public String toString(Object object) {
|
||||
if (object instanceof Geometry) {
|
||||
return "Objet trop complexe pour affichage";
|
||||
} else if (object != null && Numbers.isFloat(object.getClass())) {
|
||||
return RhomeoCore.SECOND_DECIMAL_FORMAT.format(object);
|
||||
} else {
|
||||
return String.valueOf(object);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.edition;
|
||||
|
||||
import fr.cenra.rhomeo.api.data.Statement;
|
||||
import java.beans.IntrospectionException;
|
||||
import javafx.collections.transformation.SortedList;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
* @param <T> Type of row items.
|
||||
*/
|
||||
public class StatementTable<T extends Statement> extends ObjectTable<T> {
|
||||
|
||||
public StatementTable(Class objectType, String... blackList) throws IntrospectionException {
|
||||
super(objectType, blackList);
|
||||
itemsProperty().addListener((obs, oldValue, newValue) -> {
|
||||
if (newValue instanceof SortedList) {
|
||||
((SortedList)newValue).comparatorProperty().bind(comparatorProperty());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p08;
|
||||
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferential;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
|
||||
import java.util.ArrayList;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableRow;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.geotoolkit.gui.javafx.util.FXTableView;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
|
||||
/**
|
||||
* Table to download referential data for a site.
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
public class FXGeoReferentialTable extends VBox {
|
||||
|
||||
private static final Image CHECKED = new Image("/fr/cenra/rhomeo/fx/p08/u74.png");
|
||||
|
||||
private final GeoReferentials geoRef;
|
||||
private final Site site;
|
||||
private final TableView<GeoReferential> table = new FXTableView<>();
|
||||
private final Button downloadButton = new Button("Télécharger");
|
||||
private final ProgressIndicator progress = new ProgressIndicator();
|
||||
private final String[] requiredTypes;
|
||||
|
||||
final TableColumn<GeoReferential,Boolean> colAvailable;
|
||||
|
||||
public FXGeoReferentialTable(GeoReferentials geoRef,Site site, String ... requieredTypes){
|
||||
this.geoRef = geoRef;
|
||||
this.site = site;
|
||||
this.requiredTypes = requieredTypes;
|
||||
|
||||
progress.setProgress(-1);
|
||||
progress.setVisible(false);
|
||||
progress.setPrefSize(32, 32);
|
||||
progress.setMinSize(32, 32);
|
||||
|
||||
downloadButton.getStyleClass().add("create-button");
|
||||
downloadButton.setStyle("-fx-text-fill: #FFFFFF;");
|
||||
downloadButton.managedProperty().bind(downloadButton.visibleProperty());
|
||||
downloadButton.setPadding(new Insets(5, 20, 5, 20));
|
||||
final HBox box = new HBox(progress,downloadButton);
|
||||
box.setPadding(new Insets(5, 5, 5, 5));
|
||||
box.setAlignment(Pos.CENTER_RIGHT);
|
||||
getChildren().addAll(table, box);
|
||||
|
||||
//table structure
|
||||
final TableColumn<GeoReferential, Integer> colAnnees = new TableColumn<>("Années disponibles");
|
||||
colAvailable = new TableColumn<>("Années téléchargées sur le poste");
|
||||
final TableColumn<GeoReferential,Boolean> colDownload = new TableColumn<>("Années à télécharger");
|
||||
colAnnees.setCellValueFactory(param -> new SimpleIntegerProperty(param.getValue().getYear()).asObject());
|
||||
colAvailable.setCellValueFactory((TableColumn.CellDataFeatures<GeoReferential, Boolean> param) -> param.getValue().completed(requiredTypes));
|
||||
colAvailable.setCellFactory((TableColumn<GeoReferential, Boolean> param) -> new AvailableCell());
|
||||
colDownload.setCellValueFactory((TableColumn.CellDataFeatures<GeoReferential, Boolean> param) -> param.getValue().toDownloadProperty());
|
||||
colDownload.setCellFactory((TableColumn<GeoReferential, Boolean> param) -> new DownloadCell());
|
||||
|
||||
table.getColumns().addAll(colAnnees, colAvailable, colDownload);
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
|
||||
|
||||
downloadButton.setOnAction(this::downloadRef);
|
||||
|
||||
updateRefTable();
|
||||
}
|
||||
|
||||
private void updateRefTable(){
|
||||
final Task<ObservableList<GeoReferential>> update = new Task<ObservableList<GeoReferential>>() {
|
||||
@Override
|
||||
protected ObservableList<GeoReferential> call() throws Exception {
|
||||
updateTitle("Mise à jour des références");
|
||||
final ObservableList<GeoReferential> referentials = geoRef.getReferentials(site);
|
||||
//remove referentials which don't have all required data
|
||||
for (int i = referentials.size() - 1; i >= 0; i--) {
|
||||
if (!referentials.get(i).areAvailable(requiredTypes)) {
|
||||
referentials.remove(i);
|
||||
}
|
||||
}
|
||||
return referentials;
|
||||
}
|
||||
};
|
||||
|
||||
update.setOnSucceeded(evt -> Platform.runLater(() -> {
|
||||
table.setItems(update.getValue());
|
||||
}));
|
||||
|
||||
update.setOnFailed(evt -> Platform.runLater(() -> {
|
||||
final Throwable ex = evt.getSource().getException();
|
||||
table.setPlaceholder(new Label(ex.getLocalizedMessage()));
|
||||
table.setItems(FXCollections.emptyObservableList());
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
|
||||
}));
|
||||
|
||||
progress.visibleProperty().bind(update.runningProperty());
|
||||
TaskManager.INSTANCE.submit(update);
|
||||
}
|
||||
|
||||
private void downloadRef(ActionEvent event) {
|
||||
|
||||
final Task<?> task = new Task() {
|
||||
@Override
|
||||
protected Object call() throws Exception {
|
||||
int selectedYears = 0;
|
||||
final ArrayList<Integer> failedYears = new ArrayList();
|
||||
for (GeoReferential ref : table.getItems()) {
|
||||
if (ref.toDownloadProperty().get()) {
|
||||
selectedYears++;
|
||||
updateTitle("Téléchargement ".concat(String.valueOf(ref.getYear())));
|
||||
try {
|
||||
geoRef.downloadReferential(site, ref.getYear());
|
||||
} catch (IllegalArgumentException e) {
|
||||
failedYears.add(ref.getYear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedYears < 1) {
|
||||
Platform.runLater(() -> {
|
||||
final Alert alert = new Alert(Alert.AlertType.WARNING, "Aucune année séléctionnée pour le téléchargement.", ButtonType.CLOSE);
|
||||
alert.setResizable(true);
|
||||
alert.show();
|
||||
});
|
||||
|
||||
} else if (!failedYears.isEmpty()) {
|
||||
final String msg;
|
||||
if (failedYears.size() < selectedYears) {
|
||||
final StringJoiner builder = new StringJoiner(", ", "Aucun référentiel n'est disponible pour la zone demandée en ", ".");
|
||||
failedYears.stream()
|
||||
.map(year -> String.valueOf(year))
|
||||
.forEach(year -> builder.add(year));
|
||||
msg = builder.toString();
|
||||
} else {
|
||||
msg = "Aucun référentiel n'est disponible pour les années demandées";
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
final Alert alert = new Alert(Alert.AlertType.WARNING, msg, ButtonType.CLOSE);
|
||||
alert.setResizable(true);
|
||||
alert.show();
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
progress.visibleProperty().bind(task.runningProperty());
|
||||
downloadButton.visibleProperty().bind(task.runningProperty().not());
|
||||
// TODO : display message on illegal argument exception : no data available.
|
||||
final EventHandler e = (EventHandler) (Event event1) -> updateRefTable();
|
||||
|
||||
task.setOnSucceeded(e);
|
||||
task.setOnFailed(evt -> {
|
||||
if (evt.getSource().getException() instanceof IllegalArgumentException) {
|
||||
final Alert alert = new Alert(Alert.AlertType.WARNING, "Aucun référentiel n'est disponible pour la zone demandée en ", ButtonType.CLOSE);
|
||||
alert.setResizable(true);
|
||||
alert.show();
|
||||
}
|
||||
e.handle(evt);
|
||||
});
|
||||
task.setOnCancelled(e);
|
||||
TaskManager.INSTANCE.submit(task);
|
||||
}
|
||||
|
||||
public ObservableList<GeoReferential> getItems() {
|
||||
return table.getItems();
|
||||
}
|
||||
|
||||
private static class AvailableCell extends TableCell<GeoReferential, Boolean>{
|
||||
|
||||
public AvailableCell() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Boolean item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if(Boolean.TRUE.equals(item)){
|
||||
setGraphic(new ImageView(CHECKED));
|
||||
}else{
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadCell extends TableCell<GeoReferential, Boolean> {
|
||||
|
||||
private final CheckBox checkBox = new CheckBox();
|
||||
|
||||
private DownloadCell() {
|
||||
setGraphic(checkBox);
|
||||
|
||||
checkBox.setOnAction((ActionEvent event) -> {
|
||||
TableRow row = tableRowProperty().getValue();
|
||||
if (row == null)
|
||||
return;
|
||||
final GeoReferential r = (GeoReferential) row.getItem();
|
||||
if (r != null) {
|
||||
r.toDownloadProperty().set(checkBox.isSelected());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(Boolean item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
checkBox.setVisible(!empty);
|
||||
checkBox.setSelected(Boolean.TRUE.equals(item));
|
||||
|
||||
TableRow row = tableRowProperty().getValue();
|
||||
if (row == null)
|
||||
return;
|
||||
final GeoReferential r = (GeoReferential) row.getItem();
|
||||
if (r != null) {
|
||||
checkBox.setVisible(!colAvailable.getCellData(r));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p08;
|
||||
|
||||
import com.vividsolutions.jts.geom.Geometry;
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.process.ProcessContext;
|
||||
import fr.cenra.rhomeo.core.BeanUtils;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.core.data.county.CountyRepository;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferential;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
|
||||
import fr.cenra.rhomeo.core.util.GeometryUtils;
|
||||
import fr.cenra.rhomeo.protocol.p08.I12;
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.geotoolkit.data.FeatureStoreUtilities;
|
||||
import org.geotoolkit.data.bean.BeanFeature;
|
||||
import org.geotoolkit.display2d.GO2Utilities;
|
||||
import org.geotoolkit.feature.Feature;
|
||||
import org.geotoolkit.feature.FeatureBuilder;
|
||||
import org.geotoolkit.gui.javafx.render2d.FXMapFrame;
|
||||
import org.geotoolkit.map.FeatureMapLayer;
|
||||
import org.geotoolkit.map.MapBuilder;
|
||||
import org.geotoolkit.map.MapContext;
|
||||
import org.geotoolkit.map.MapItem;
|
||||
import org.geotoolkit.map.MapLayer;
|
||||
import org.geotoolkit.style.MutableStyleFactory;
|
||||
import org.opengis.feature.Property;
|
||||
import org.opengis.filter.FilterFactory2;
|
||||
import org.opengis.filter.expression.PropertyName;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXP08DatasetPane extends VBox {
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private GeoReferentials geoRef;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(I12.NAME)
|
||||
private Indicator indicator;
|
||||
|
||||
@FXML
|
||||
private Button next;
|
||||
|
||||
@FXML
|
||||
private BorderPane tableSpace;
|
||||
|
||||
@Autowired(required = false)
|
||||
private CountyRepository countyRepository;
|
||||
|
||||
private FXGeoReferentialTable table;
|
||||
|
||||
public FXP08DatasetPane() {
|
||||
Rhomeo.loadFXML(this);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
table = new FXGeoReferentialTable(geoRef, session.getDataContext().getSite(),
|
||||
GeoReferentials.TYPE_ZONE_HYDRO,
|
||||
GeoReferentials.TYPE_TACHE_ARTIF,
|
||||
GeoReferentials.TYPE_TACHE_URBAINE);
|
||||
tableSpace.setCenter(table);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void nextStep(ActionEvent event) {
|
||||
|
||||
//check there is at least one year of data available
|
||||
int nbAvailable = 0;
|
||||
final ObservableList<GeoReferential> items = table.getItems();
|
||||
for(GeoReferential ref : items){
|
||||
//we need zone hydro,tache artif and tache_urbaine
|
||||
if(ref.zoneHydroLocal().get()
|
||||
&& ref.tacheArtifLocal().get()
|
||||
&& ref.tacheUrbaineLocal().get()) nbAvailable++;
|
||||
}
|
||||
|
||||
if (nbAvailable <= 0) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : Aucun réferentiel téléchargé.");
|
||||
return;
|
||||
}
|
||||
|
||||
final ProcessContext ctx = new ProcessContext(session.getDataset(), session.getDataContext());
|
||||
ctx.getIndicators().add(indicator);
|
||||
session.setProcessContext(ctx);
|
||||
|
||||
if (!session.requestWorkflowStep(WorkflowStep.PROCESS)) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante. Veuillez vérifier votre lot de données.");
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showMap() throws Exception {
|
||||
final Site currentSite = session.getDataContext().getSite();
|
||||
// Should not be null when called here
|
||||
if (currentSite == null) {
|
||||
throw new IllegalArgumentException("Currently selected site was null here, but shouldn't be");
|
||||
}
|
||||
|
||||
// If counties are available, we display them on the map.
|
||||
final MapContext context = MapBuilder.createContext();
|
||||
final MutableStyleFactory sf = GO2Utilities.STYLE_FACTORY;
|
||||
|
||||
if (countyRepository != null) {
|
||||
try {
|
||||
context.layers().add(MapBuilder.createFeatureLayer(countyRepository.getFeatures(), sf.style(sf.lineSymbolizer())
|
||||
));
|
||||
} catch (Exception e) {
|
||||
Rhomeo.LOGGER.log(Level.WARNING, "Cannot show county layer", e);
|
||||
}
|
||||
}
|
||||
|
||||
final BeanFeature feature = new BeanFeature(currentSite, BeanUtils.createMapping(Site.class, null));
|
||||
final MapLayer layer = MapBuilder.createFeatureLayer(FeatureStoreUtilities.collection(feature), sf.style(sf.polygonSymbolizer(null, sf.fill(Color.red), null)));
|
||||
layer.setName("Site");
|
||||
context.layers().add(layer);
|
||||
context.setAreaOfInterest(layer.getBounds());
|
||||
|
||||
Geometry buffer = GeometryUtils.computeBuffer(currentSite.getGeometry());
|
||||
final FeatureBuilder fb = new FeatureBuilder(feature.getType());
|
||||
for (final Property p : feature.getProperties()) {
|
||||
if (p.getValue() instanceof Geometry)
|
||||
fb.setPropertyValue(p.getName(), buffer);
|
||||
else
|
||||
fb.setPropertyValue(p.getName(), p.getValue());
|
||||
}
|
||||
final Feature bufferFeature = fb.buildFeature("buffer");
|
||||
final FeatureMapLayer bufferLayer = MapBuilder.createFeatureLayer(FeatureStoreUtilities.collection(bufferFeature));
|
||||
bufferLayer.setName("Buffer");
|
||||
context.layers().add(1, bufferLayer);
|
||||
|
||||
final FilterFactory2 ff = GO2Utilities.FILTER_FACTORY;
|
||||
final PropertyName geomProp = ff.property("geom");
|
||||
|
||||
MapItem zoneHydro = geoRef.getWFSLayers(Optional.of(ff.intersects(geomProp, ff.literal(buffer))), GeoReferentials.TYPE_ZONE_HYDRO);
|
||||
zoneHydro = zoneHydro.items().get(0);
|
||||
MapItem wfsLayers = geoRef.getWFSLayers(Optional.of(ff.bbox(null, ((MapLayer)zoneHydro).getBounds())), GeoReferentials.TYPE_TACHE_ARTIF, GeoReferentials.TYPE_TACHE_URBAINE, GeoReferentials.TYPE_RPG);
|
||||
wfsLayers.setName("BBox filtered");
|
||||
context.items().add(0, wfsLayers);
|
||||
|
||||
final List<Geometry> geomCollection = ((FeatureMapLayer)zoneHydro).getCollection().stream()
|
||||
.map(feat -> feat.getDefaultGeometryProperty().getValue())
|
||||
.filter(geom -> geom instanceof Geometry)
|
||||
.map(geom -> (Geometry) geom)
|
||||
.collect(Collectors.toList());
|
||||
Geometry filterGeom = GO2Utilities.JTS_FACTORY.createGeometryCollection(geomCollection.toArray(new Geometry[geomCollection.size()])).union();
|
||||
wfsLayers = geoRef.getWFSLayers(Optional.of(ff.intersects(geomProp, ff.literal(filterGeom))), GeoReferentials.TYPE_TACHE_ARTIF, GeoReferentials.TYPE_TACHE_URBAINE, GeoReferentials.TYPE_RPG);
|
||||
wfsLayers.setName("Polygon filtered");
|
||||
context.items().add(1, wfsLayers);
|
||||
|
||||
context.items().add(0, zoneHydro);
|
||||
|
||||
FXMapFrame.show(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p08;
|
||||
|
||||
import com.vividsolutions.jts.geom.Geometry;
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.core.util.ExportUtils;
|
||||
import fr.cenra.rhomeo.core.util.GeometryUtils;
|
||||
import fr.cenra.rhomeo.protocol.p08.I12;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import fr.cenra.rhomeo.api.process.Process;
|
||||
import fr.cenra.rhomeo.fx.p09.FXP09ProcessPane;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferential;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
|
||||
import fr.cenra.rhomeo.protocol.p08.I12Process;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.BitSet;
|
||||
import java.util.Optional;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.geotoolkit.internal.GeotkFX;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXP08ProcessPane extends VBox {
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private GeoReferentials geoRef;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(I12.NAME)
|
||||
private Indicator indicator;
|
||||
|
||||
@FXML
|
||||
private Button next;
|
||||
|
||||
@FXML
|
||||
private Button uiExport;
|
||||
|
||||
@FXML
|
||||
private HBox yearBoxes;
|
||||
|
||||
private final List<CheckBox> checkBoxes = new ArrayList<>();
|
||||
|
||||
private final SimpleObjectProperty<Task<Set<Index>>> globalTask = new SimpleObjectProperty<>();
|
||||
|
||||
private BitSet activatedYears;
|
||||
|
||||
private final Preferences prefs;
|
||||
|
||||
public FXP08ProcessPane(){
|
||||
Rhomeo.loadFXML(this);
|
||||
prefs = Preferences.userNodeForPackage(FXP08ProcessPane.class);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
//liste available referential state for this site
|
||||
final ObservableList<GeoReferential> refs;
|
||||
try {
|
||||
refs = geoRef.getLocalReferentials(session.getDataContext().getSite())
|
||||
.filtered(ref -> ref.hasLocalTypes(
|
||||
GeoReferentials.TYPE_ZONE_HYDRO,
|
||||
GeoReferentials.TYPE_TACHE_ARTIF,
|
||||
GeoReferentials.TYPE_TACHE_URBAINE)
|
||||
)
|
||||
.sorted();
|
||||
} catch (IllegalArgumentException | IOException ex) {
|
||||
// If we cannot load georeferentials, it's no use to go further.
|
||||
GeotkFX.newExceptionDialog("Impossible de déterminer la liste des réferentiels disponibles. La lecture sur le système a échouée.", ex).show();
|
||||
session.requestWorkflowStep(WorkflowStep.DATASET);
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer yearsAsBytes = ByteBuffer.wrap(new byte[refs.size() * (Integer.SIZE / Byte.SIZE)]);
|
||||
final IntBuffer years = yearsAsBytes.asIntBuffer();
|
||||
for (final GeoReferential ref : refs) {
|
||||
years.put(ref.getYear());
|
||||
}
|
||||
years.position(0);
|
||||
|
||||
/* Restore selection state from serialized bitset if possible. To
|
||||
* determine if restoration is possible, we ensure there's data stored
|
||||
* in preferences, and also that they match current edited site / years.
|
||||
* Otherwise, we set a new bitset, with default selection on types 17
|
||||
* and 18.
|
||||
* We do the same with years, except that by default, they're all selected.
|
||||
*/
|
||||
final String siteName = session.getDataContext().getSite().getName();
|
||||
final boolean restore = prefs.get(FXP09ProcessPane.SITE_NAME, "").equals(siteName)
|
||||
&& ByteBuffer.wrap(prefs.getByteArray(FXP09ProcessPane.AVAILABLE_YEARS, new byte[0])).asIntBuffer().equals(years);
|
||||
|
||||
final byte[] yearSelection;
|
||||
if (restore && (yearSelection = prefs.getByteArray(FXP09ProcessPane.YEAR_SELECTION, null)) != null) {
|
||||
activatedYears = BitSet.valueOf(yearSelection);
|
||||
} else {
|
||||
activatedYears = new BitSet(refs.size());
|
||||
activatedYears.set(0, activatedYears.length());
|
||||
}
|
||||
|
||||
// A new session is starting. We initialize preferences for it.
|
||||
if (!restore) {
|
||||
prefs.put(FXP09ProcessPane.SITE_NAME, siteName);
|
||||
prefs.putByteArray(FXP09ProcessPane.AVAILABLE_YEARS, yearsAsBytes.array());
|
||||
}
|
||||
|
||||
for (GeoReferential ref : refs) {
|
||||
final CheckBox check = new CheckBox(String.valueOf(ref.getYear()));
|
||||
final int index = checkBoxes.size();
|
||||
check.setSelected(activatedYears.get(index));
|
||||
check.selectedProperty().addListener((obs, oldValue, newValue) -> {
|
||||
yearSelectionChanged(index, newValue == null? false : newValue);
|
||||
});
|
||||
yearBoxes.getChildren().add(check);
|
||||
checkBoxes.add(check);
|
||||
}
|
||||
}
|
||||
|
||||
private void yearSelectionChanged(final int index, final boolean value) {
|
||||
activatedYears.set(index, value);
|
||||
prefs.putByteArray(FXP09ProcessPane.YEAR_SELECTION, activatedYears.toByteArray());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void exportGeometries(ActionEvent event) {
|
||||
final Site site = session.getDataContext().getSite();
|
||||
final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
|
||||
final File outputFile = Rhomeo.askOutputFilePopup(site.getName()+".zip", this, zipFilter);
|
||||
|
||||
if (outputFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tmpSelectedYear = Integer.MIN_VALUE;
|
||||
for(final CheckBox c : checkBoxes) {
|
||||
if (c.isSelected()) {
|
||||
tmpSelectedYear = Math.max(tmpSelectedYear, Integer.parseInt(c.getText()));
|
||||
}
|
||||
}
|
||||
|
||||
final int selectedYear = tmpSelectedYear;
|
||||
|
||||
final ChoiceDialog<ExportUtils.ExportType> dialog = new ChoiceDialog<>(ExportUtils.ExportType.GJSON, ExportUtils.ExportType.values());
|
||||
dialog.setTitle("Format d'export");
|
||||
dialog.setHeaderText("Choisissez un format de sortie");
|
||||
dialog.setResizable(true);
|
||||
dialog.showAndWait().ifPresent(type -> {
|
||||
Task<Boolean> t = TaskManager.INSTANCE.submit("Export", () -> {
|
||||
final Geometry buffer = GeometryUtils.computeBuffer(site.getGeometry());
|
||||
final Optional<Geometry> territory;
|
||||
if (selectedYear > Integer.MIN_VALUE) {
|
||||
territory = geoRef.getLocalReferential(site, selectedYear).unionIntersecting(GeoReferentials.TYPE_ZONE_HYDRO, site.getGeometry());
|
||||
} else {
|
||||
territory = Optional.empty();
|
||||
}
|
||||
ExportUtils.exportSiteAndBuffer(outputFile, site, buffer, territory, type);
|
||||
return true;
|
||||
});
|
||||
uiExport.disableProperty().unbind();
|
||||
uiExport.disableProperty().bind(t.runningProperty());
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
void nextStep(ActionEvent event) {
|
||||
|
||||
//check there is at least one year of data available
|
||||
int nbAvailable = 0;
|
||||
for(CheckBox c : checkBoxes){
|
||||
if(c.isSelected()) nbAvailable++;
|
||||
}
|
||||
|
||||
if (nbAvailable==0) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : veuillez sélectionner une année.");
|
||||
return;
|
||||
}
|
||||
|
||||
final Computer computer = new Computer();
|
||||
globalTask.set(computer);
|
||||
|
||||
computer.stateProperty().addListener(obs -> {
|
||||
if (computer.isDone()) {
|
||||
setDisable(false);
|
||||
switch (computer.getState()) {
|
||||
case FAILED:
|
||||
Rhomeo.showAlert(Alert.AlertType.ERROR, "process-failed");
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
session.setResults(computer.getValue());
|
||||
globalTask.set(null);
|
||||
session.requestWorkflowStep(WorkflowStep.RESULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TaskManager.INSTANCE.submit(computer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class Computer extends Task<Set<Index>> {
|
||||
|
||||
private final List<Process> processes = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected Set<Index> call() throws Exception {
|
||||
processes.addAll(indicator.createProcesses(session.getProcessContext()));
|
||||
final I12Process process = (I12Process) processes.get(0);
|
||||
|
||||
//configure selected years
|
||||
for(CheckBox c : checkBoxes){
|
||||
if(c.isSelected())process.getYears().add(Integer.valueOf(c.getText()));
|
||||
}
|
||||
|
||||
updateProgress(-1, -1);
|
||||
updateTitle("I12");
|
||||
|
||||
Platform.runLater(() -> {
|
||||
process.titleProperty().addListener((obs) -> updateTitle(process.getTitle()));
|
||||
process.messageProperty().addListener((obs) -> updateTitle(process.getMessage()));
|
||||
process.workDoneProperty().addListener((obs) -> updateProgress(process.getWorkDone(), process.getTotalWork()));
|
||||
});
|
||||
|
||||
process.run();
|
||||
return (Set) process.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
for (final Process p : processes) {
|
||||
p.cancel(true);
|
||||
}
|
||||
return super.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p08;
|
||||
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.protocol.p08.P08;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import javafx.scene.Node;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Protocol 08 editor spi.
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class P08ProtocolEditorSpi implements ProtocolEditorSpi{
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(P08.NAME)
|
||||
private Protocol protocol;
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getEditor(WorkflowStep step) {
|
||||
switch(step){
|
||||
case DATASET : return session.getBean(FXP08DatasetPane.class);
|
||||
case PROCESS : return session.getBean(FXP08ProcessPane.class);
|
||||
default : return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<WorkflowStep> getStepsToIgnore() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p09;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.process.ProcessContext;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.fx.p08.FXGeoReferentialTable;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferential;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
|
||||
import fr.cenra.rhomeo.protocol.p09.I13;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXP09DatasetPane extends VBox {
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
@Autowired
|
||||
private GeoReferentials geoRef;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(I13.NAME)
|
||||
private Indicator indicator;
|
||||
|
||||
@FXML
|
||||
private Button next;
|
||||
@FXML
|
||||
private BorderPane tableSpace;
|
||||
|
||||
private FXGeoReferentialTable table;
|
||||
|
||||
public FXP09DatasetPane() {
|
||||
Rhomeo.loadFXML(this);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct(){
|
||||
table = new FXGeoReferentialTable(geoRef, session.getDataContext().getSite(),
|
||||
GeoReferentials.TYPE_ZONE_HYDRO,
|
||||
GeoReferentials.TYPE_RPG);
|
||||
tableSpace.setCenter(table);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void nextStep(ActionEvent event) {
|
||||
|
||||
//check there is at least one year of data available
|
||||
int nbAvailable = 0;
|
||||
final ObservableList<GeoReferential> items = table.getItems();
|
||||
for(GeoReferential ref : items){
|
||||
//we need zone hydro,tache artif and tache_urbaine
|
||||
if(ref.zoneHydroLocal().get()
|
||||
&& ref.rpgLocal().get()) nbAvailable++;
|
||||
}
|
||||
|
||||
if (nbAvailable==0) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : la saisie n'est pas valide.");
|
||||
return;
|
||||
}
|
||||
|
||||
final ProcessContext ctx = new ProcessContext(session.getDataset(), session.getDataContext());
|
||||
ctx.getIndicators().add(indicator);
|
||||
session.setProcessContext(ctx);
|
||||
|
||||
if (!session.requestWorkflowStep(WorkflowStep.PROCESS)) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante. Veuillez vérifier votre lot de données.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p09;
|
||||
|
||||
import com.vividsolutions.jts.geom.Geometry;
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Site;
|
||||
import fr.cenra.rhomeo.api.process.Indicator;
|
||||
import fr.cenra.rhomeo.api.result.Index;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferential;
|
||||
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
|
||||
import fr.cenra.rhomeo.core.util.GeometryUtils;
|
||||
import fr.cenra.rhomeo.protocol.p09.I13;
|
||||
import fr.cenra.rhomeo.protocol.p09.I13Process;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javax.annotation.PostConstruct;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import fr.cenra.rhomeo.api.process.Process;
|
||||
import fr.cenra.rhomeo.core.util.ExportUtils;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.BitSet;
|
||||
import java.util.Optional;
|
||||
import java.util.prefs.Preferences;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.geotoolkit.internal.GeotkFX;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXP09ProcessPane extends VBox {
|
||||
|
||||
public static final String SITE_NAME = "site_name";
|
||||
public static final String AVAILABLE_YEARS = "available_years";
|
||||
private static final String TYPE_SELECTION = "type_selection";
|
||||
public static final String YEAR_SELECTION = "year_selection";
|
||||
|
||||
private static final String[] TYPES = new String[]{
|
||||
null,
|
||||
"01 - Blé tendre",
|
||||
"02 - Maïs grain et ensilage",
|
||||
"03 - Orge",
|
||||
"04 - Autres céréales",
|
||||
"05 - Colza",
|
||||
"06 - Tournesol",
|
||||
"07 - Autres oleagineux",
|
||||
"08 - Protéagineux",
|
||||
"09 - Plantes à fibres",
|
||||
"10 - Semences",
|
||||
"11 - Gel (surfaces gelées sans production)",
|
||||
"12 - Gel industriel",
|
||||
"13 - Autres gels",
|
||||
"14 - Riz",
|
||||
"15 - Légumineuses à grains",
|
||||
"16 - Fourrage",
|
||||
"17 - Estives landes",
|
||||
"18 - Prairies permanentes",
|
||||
"19 - Prairies temporaires",
|
||||
"20 - Vergers",
|
||||
"21 - Vignes",
|
||||
"22 - Fruits à coque",
|
||||
"23 - Oliviers",
|
||||
"24 - Autres cultures industrielles",
|
||||
"25 - Légumes-fleurs",
|
||||
"26 - Canne à sucre",
|
||||
"27 - Arboriculture",
|
||||
"28 - Divers"
|
||||
};
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
@Autowired
|
||||
private GeoReferentials geoRef;
|
||||
@Autowired
|
||||
@Qualifier(I13.NAME)
|
||||
private Indicator indicator;
|
||||
|
||||
@FXML
|
||||
private Button next;
|
||||
|
||||
@FXML
|
||||
private Button uiExport;
|
||||
|
||||
@FXML
|
||||
private HBox yearBoxes;
|
||||
@FXML
|
||||
private GridPane typeBoxes;
|
||||
|
||||
private final List<CheckBox> yearCheckBoxes = new ArrayList<>();
|
||||
private final List<CheckBox> typeCheckBoxes = new ArrayList<>();
|
||||
|
||||
private final SimpleObjectProperty<Task<Set<Index>>> globalTask = new SimpleObjectProperty<>();
|
||||
|
||||
private BitSet activatedYears;
|
||||
private BitSet activatedTypes;
|
||||
|
||||
private final Preferences prefs;
|
||||
|
||||
public FXP09ProcessPane() {
|
||||
Rhomeo.loadFXML(this);
|
||||
prefs = Preferences.userNodeForPackage(FXP09ProcessPane.class);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
|
||||
//liste available referential state for this site
|
||||
final ObservableList<GeoReferential> refs;
|
||||
try {
|
||||
refs = geoRef.getLocalReferentials(session.getDataContext().getSite())
|
||||
.filtered(ref -> ref.hasLocalTypes(GeoReferentials.TYPE_ZONE_HYDRO, GeoReferentials.TYPE_RPG))
|
||||
.sorted();
|
||||
} catch (IllegalArgumentException | IOException ex) {
|
||||
// If we cannot load georeferentials, it's no use to go further.
|
||||
GeotkFX.newExceptionDialog("Impossible de déterminer la liste des réferentiels disponibles. La lecture sur le système a échouée.", ex).show();
|
||||
session.requestWorkflowStep(WorkflowStep.DATASET);
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer yearsAsBytes = ByteBuffer.wrap(new byte[refs.size() * (Integer.SIZE / Byte.SIZE)]);
|
||||
final IntBuffer years = yearsAsBytes.asIntBuffer();
|
||||
for (final GeoReferential ref : refs) {
|
||||
years.put(ref.getYear());
|
||||
}
|
||||
years.position(0);
|
||||
|
||||
/* Restore selection state from serialized bitset if possible. To
|
||||
* determine if restoration is possible, we ensure there's data stored
|
||||
* in preferences, and also that they match current edited site / years.
|
||||
* Otherwise, we set a new bitset, with default selection on types 17
|
||||
* and 18.
|
||||
* We do the same with years, except that by default, they're all selected.
|
||||
*/
|
||||
final String siteName = session.getDataContext().getSite().getName();
|
||||
final boolean restore = prefs.get(SITE_NAME, "").equals(siteName)
|
||||
&& ByteBuffer.wrap(prefs.getByteArray(AVAILABLE_YEARS, new byte[0])).asIntBuffer().equals(years);
|
||||
|
||||
final byte[] yearSelection;
|
||||
if (restore && (yearSelection = prefs.getByteArray(YEAR_SELECTION, null)) != null) {
|
||||
activatedYears = BitSet.valueOf(yearSelection);
|
||||
} else {
|
||||
activatedYears = new BitSet(refs.size());
|
||||
activatedYears.set(0, activatedYears.length());
|
||||
}
|
||||
|
||||
final byte[] typeSelection;
|
||||
if (restore && (typeSelection = prefs.getByteArray(TYPE_SELECTION, null)) != null) {
|
||||
activatedTypes = BitSet.valueOf(typeSelection);
|
||||
} else {
|
||||
activatedTypes = new BitSet(TYPES.length);
|
||||
activatedTypes.set(17, true);
|
||||
activatedTypes.set(18, true);
|
||||
activatedTypes.set(19, true);
|
||||
}
|
||||
|
||||
// A new session is starting. We initialize preferences for it.
|
||||
if (!restore) {
|
||||
prefs.put(SITE_NAME, siteName);
|
||||
prefs.putByteArray(AVAILABLE_YEARS, yearsAsBytes.array());
|
||||
}
|
||||
|
||||
// Init UI. For checkboxes, each time selection change, we update preferences.
|
||||
for (GeoReferential ref : refs) {
|
||||
final CheckBox check = new CheckBox(String.valueOf(ref.getYear()));
|
||||
final int index = yearCheckBoxes.size();
|
||||
check.setSelected(activatedYears.get(index));
|
||||
check.selectedProperty().addListener((obs, oldValue, newValue) -> {
|
||||
yearSelectionChanged(index, newValue == null? false : newValue);
|
||||
});
|
||||
yearBoxes.getChildren().add(check);
|
||||
yearCheckBoxes.add(check);
|
||||
}
|
||||
|
||||
double best = 0;
|
||||
typeCheckBoxes.add(null);
|
||||
for(int i=1;i<29;i++){
|
||||
final CheckBox box = new CheckBox(TYPES[i]);
|
||||
box.setSelected(activatedTypes.get(i));
|
||||
typeCheckBoxes.add(box);
|
||||
GridPane.setColumnIndex(box, (i-1)%4);
|
||||
GridPane.setRowIndex(box, ((i-1)-((i-1)%4)));
|
||||
typeBoxes.getChildren().add(box);
|
||||
best = Math.max(best, box.getWidth());
|
||||
final int index = i;
|
||||
box.selectedProperty().addListener((obs, oldValue, newValue) -> {
|
||||
boxSelectionChanged(index, newValue == null? false : newValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void yearSelectionChanged(final int index, final boolean value) {
|
||||
activatedYears.set(index, value);
|
||||
prefs.putByteArray(YEAR_SELECTION, activatedYears.toByteArray());
|
||||
}
|
||||
|
||||
private void boxSelectionChanged(final int index, final boolean value) {
|
||||
activatedTypes.set(index, value);
|
||||
prefs.putByteArray(TYPE_SELECTION, activatedTypes.toByteArray());
|
||||
}
|
||||
|
||||
@FXML
|
||||
void exportGeometries(ActionEvent event) {
|
||||
final Site site = session.getDataContext().getSite();
|
||||
final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
|
||||
final File outputFile = Rhomeo.askOutputFilePopup(site.getName()+".zip", this, zipFilter);
|
||||
|
||||
if (outputFile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tmpSelectedYear = Integer.MIN_VALUE;
|
||||
for(final CheckBox c : yearCheckBoxes) {
|
||||
if (c.isSelected()) {
|
||||
tmpSelectedYear = Math.max(tmpSelectedYear, Integer.parseInt(c.getText()));
|
||||
}
|
||||
}
|
||||
|
||||
final int selectedYear = tmpSelectedYear;
|
||||
|
||||
final ChoiceDialog<ExportUtils.ExportType> dialog = new ChoiceDialog<>(ExportUtils.ExportType.GJSON, ExportUtils.ExportType.values());
|
||||
dialog.setTitle("Format d'export");
|
||||
dialog.setHeaderText("Choisissez un format de sortie");
|
||||
dialog.setResizable(true);
|
||||
dialog.showAndWait().ifPresent(type -> {
|
||||
Task<Boolean> t = TaskManager.INSTANCE.submit("Export", () -> {
|
||||
final Geometry buffer = GeometryUtils.computeBuffer(site.getGeometry());
|
||||
final Optional<Geometry> territory;
|
||||
if (selectedYear > Integer.MIN_VALUE) {
|
||||
territory = geoRef.getLocalReferential(site, selectedYear).unionIntersecting(GeoReferentials.TYPE_ZONE_HYDRO, site.getGeometry());
|
||||
} else {
|
||||
territory = Optional.empty();
|
||||
}
|
||||
ExportUtils.exportSiteAndBuffer(outputFile, site, buffer, territory, type);
|
||||
return true;
|
||||
});
|
||||
uiExport.disableProperty().unbind();
|
||||
uiExport.disableProperty().bind(t.runningProperty());
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
void nextStep(ActionEvent event) {
|
||||
|
||||
//check there is at least one year of data available
|
||||
int nbAvailable = 0;
|
||||
for(CheckBox c : yearCheckBoxes){
|
||||
if(c.isSelected()) nbAvailable++;
|
||||
}
|
||||
|
||||
if (nbAvailable==0) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : veuillez sélectionner une année.");
|
||||
return;
|
||||
}
|
||||
|
||||
final Computer computer = new Computer();
|
||||
globalTask.set(computer);
|
||||
|
||||
computer.stateProperty().addListener(obs -> {
|
||||
if (computer.isDone()) {
|
||||
setDisable(false);
|
||||
switch (computer.getState()) {
|
||||
case FAILED:
|
||||
Rhomeo.showAlert(Alert.AlertType.ERROR, "process-failed");
|
||||
break;
|
||||
case SUCCEEDED:
|
||||
session.setResults(computer.getValue());
|
||||
globalTask.set(null);
|
||||
session.requestWorkflowStep(WorkflowStep.RESULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TaskManager.INSTANCE.submit(computer);
|
||||
}
|
||||
|
||||
|
||||
private class Computer extends Task<Set<Index>> {
|
||||
|
||||
private final List<Process> processes = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected Set<Index> call() throws Exception {
|
||||
processes.addAll(indicator.createProcesses(session.getProcessContext()));
|
||||
final I13Process process = (I13Process) processes.get(0);
|
||||
|
||||
//configure selected years
|
||||
for(CheckBox c : yearCheckBoxes){
|
||||
if(c.isSelected())process.getYears().add(Integer.valueOf(c.getText()));
|
||||
}
|
||||
//configure excluded types
|
||||
for(int i=1;i<29;i++){
|
||||
if(typeCheckBoxes.get(i).isSelected()){
|
||||
process.getNoCount().add(i);
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(-1, -1);
|
||||
updateTitle("I13");
|
||||
|
||||
Platform.runLater(() -> {
|
||||
process.titleProperty().addListener((obs) -> updateTitle(process.getTitle()));
|
||||
process.messageProperty().addListener((obs) -> updateTitle(process.getMessage()));
|
||||
process.workDoneProperty().addListener((obs) -> updateProgress(process.getWorkDone(), process.getTotalWork()));
|
||||
});
|
||||
|
||||
process.run();
|
||||
return (Set) process.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
for (final Process p : processes) {
|
||||
p.cancel(true);
|
||||
}
|
||||
return super.cancel(mayInterruptIfRunning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.p09;
|
||||
|
||||
import fr.cenra.rhomeo.api.data.Protocol;
|
||||
import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import fr.cenra.rhomeo.core.WorkflowStep;
|
||||
import fr.cenra.rhomeo.protocol.p09.P09;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import javafx.scene.Node;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Protocol 09 editor spi.
|
||||
*
|
||||
* @author Johann Sorel (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class P09ProtocolEditorSpi implements ProtocolEditorSpi{
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(P09.NAME)
|
||||
private Protocol protocol;
|
||||
|
||||
@Override
|
||||
public Protocol getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getEditor(WorkflowStep step) {
|
||||
switch(step){
|
||||
case DATASET : return session.getBean(FXP09DatasetPane.class);
|
||||
case PROCESS : return session.getBean(FXP09ProcessPane.class);
|
||||
default : return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<WorkflowStep> getStepsToIgnore() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.validation;
|
||||
|
||||
|
||||
import fr.cenra.rhomeo.api.InternationalResource;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Level;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Path;
|
||||
|
||||
|
||||
/**
|
||||
* A simple cell which render a constraint violation message in cell text.
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class ConstraintViolationListCell extends ListCell<ConstraintViolation> {
|
||||
|
||||
@Override
|
||||
protected void updateItem(ConstraintViolation item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
final StringBuilder msgBuilder = new StringBuilder();
|
||||
if (item.getLeafBean() instanceof InternationalResource) {
|
||||
final Iterator<Path.Node> it = item.getPropertyPath().iterator();
|
||||
String pName = null;
|
||||
while (it.hasNext())
|
||||
pName = it.next().getName();
|
||||
if (pName != null) {
|
||||
try {
|
||||
msgBuilder.append(InternationalResource.getResourceString((Class)item.getLeafBean().getClass(), pName, "label"))
|
||||
.append(" : ");
|
||||
} catch (Exception e) {
|
||||
RhomeoCore.LOGGER.log(Level.FINE, "Cannot find resource ".concat(pName), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setText(msgBuilder.append(item.getMessage()).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.fx.validation;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.data.Dataset;
|
||||
import fr.cenra.rhomeo.api.data.Warning;
|
||||
import fr.cenra.rhomeo.core.Session;
|
||||
import java.util.HashSet;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Callback;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validator;
|
||||
import org.geotoolkit.font.FontAwesomeIcons;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXValidationPane extends StackPane {
|
||||
|
||||
@Autowired
|
||||
private Session session;
|
||||
|
||||
@Autowired
|
||||
private Validator validator;
|
||||
|
||||
@FXML
|
||||
private Label uiStateIcon;
|
||||
|
||||
@FXML
|
||||
private Label uiStateLabel;
|
||||
|
||||
@FXML
|
||||
private Button uiValidate;
|
||||
|
||||
@FXML
|
||||
private Hyperlink uiErrorContainer;
|
||||
|
||||
@FXML
|
||||
private Hyperlink uiWarningContainer;
|
||||
|
||||
@FXML
|
||||
private ProgressIndicator uiProgress;
|
||||
|
||||
private final ListView<ConstraintViolation> uiErrorList;
|
||||
private final ListView<ConstraintViolation> uiWarningList;
|
||||
|
||||
private final ObservableList errors;
|
||||
private final ObservableList warnings;
|
||||
|
||||
private final BooleanBinding noError;
|
||||
|
||||
private Stage errorStage;
|
||||
private Stage warningStage;
|
||||
|
||||
public FXValidationPane() {
|
||||
super();
|
||||
Rhomeo.loadFXML(this, FXValidationPane.class);
|
||||
errors = FXCollections.observableArrayList();
|
||||
warnings = FXCollections.observableArrayList();
|
||||
uiErrorList = new ListView<>(errors);
|
||||
uiErrorList.setMaxHeight(200);
|
||||
final Insets insets = new Insets(4);
|
||||
uiErrorList.setPadding(insets);
|
||||
uiWarningList = new ListView<>(warnings);
|
||||
uiWarningList.setPadding(insets);
|
||||
uiWarningList.setMaxHeight(200);
|
||||
|
||||
final Callback factory = (list) -> new ConstraintViolationListCell();
|
||||
uiErrorList.setCellFactory(factory);
|
||||
uiWarningList.setCellFactory(factory);
|
||||
|
||||
// Update display on error detection
|
||||
noError = Bindings.isEmpty(errors);
|
||||
uiErrorContainer.visibleProperty().bind(noError.not());
|
||||
uiErrorContainer.managedProperty().bind(uiErrorContainer.visibleProperty());
|
||||
final BooleanBinding noWarning = Bindings.isEmpty(warnings);
|
||||
uiWarningContainer.visibleProperty().bind(noWarning.not());
|
||||
uiWarningContainer.managedProperty().bind(uiWarningContainer.visibleProperty());
|
||||
noError.addListener((obs) -> {
|
||||
if (session.getDataset().getItems().isEmpty()) {
|
||||
uiStateIcon.setText(null);
|
||||
uiStateLabel.setText(null);
|
||||
} else if (noError.get()) {
|
||||
uiStateIcon.setText(FontAwesomeIcons.ICON_CHECK);
|
||||
uiStateIcon.setStyle("-fx-font-family: FontAwesome; -fx-font-size: 24; -fx-text-fill:#9CB35B");
|
||||
uiStateLabel.setText(ResourceBundle.getBundle(FXValidationPane.class.getCanonicalName()).getString("dataset.valid"));
|
||||
} else {
|
||||
uiStateIcon.setText(FontAwesomeIcons.ICON_TIMES);
|
||||
uiStateIcon.setStyle("-fx-font-family: FontAwesome; -fx-font-size: 24; -fx-text-fill:#D85F70");
|
||||
uiStateLabel.setText(ResourceBundle.getBundle(FXValidationPane.class.getCanonicalName()).getString("dataset.invalid"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
uiValidate.disableProperty().bind(Bindings.isEmpty(session.getDataset().getItems()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
void validate(ActionEvent event) {
|
||||
TaskManager.INSTANCE.submit("Validation du lot de données", () -> {
|
||||
Rhomeo.fxRun(false, () -> {
|
||||
setDisable(true);
|
||||
uiProgress.setVisible(true);
|
||||
});
|
||||
try {
|
||||
Set<ConstraintViolation<Dataset>> violations = validator.validate(session.getDataset());
|
||||
|
||||
final HashSet tmpErrors = new HashSet();
|
||||
final HashSet tmpWarnings = new HashSet();
|
||||
for (final ConstraintViolation violation : violations) {
|
||||
if (findWarning(violation)) {
|
||||
tmpWarnings.add(violation);
|
||||
} else {
|
||||
tmpErrors.add(violation);
|
||||
}
|
||||
}
|
||||
|
||||
Rhomeo.fxRun(false, () -> {
|
||||
errors.setAll(tmpErrors);
|
||||
warnings.setAll(tmpWarnings);
|
||||
uiStateLabel.setVisible(true);
|
||||
noError.invalidate();
|
||||
});
|
||||
} finally {
|
||||
Rhomeo.fxRun(false, () -> {
|
||||
setDisable(false);
|
||||
uiProgress.setVisible(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean findWarning(final ConstraintViolation violation) {
|
||||
for (final Object p : violation.getConstraintDescriptor().getPayload()) {
|
||||
if (p == Warning.class) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ListView<ConstraintViolation> getErrorList() {
|
||||
return uiErrorList;
|
||||
}
|
||||
|
||||
public ListView<ConstraintViolation> getWarningList() {
|
||||
return uiWarningList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty this panel content.
|
||||
*/
|
||||
public void reset() {
|
||||
warnings.clear();
|
||||
errors.clear();
|
||||
noError.invalidate();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showWarnings(final ActionEvent e) {
|
||||
if (warningStage == null) {
|
||||
warningStage = createErrorStage("Avertissements", uiWarningList);
|
||||
}
|
||||
|
||||
warningStage.show();
|
||||
warningStage.requestFocus();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void showErrors(final ActionEvent e) {
|
||||
if (errorStage == null) {
|
||||
errorStage = createErrorStage("Erreurs", uiErrorList);
|
||||
}
|
||||
|
||||
errorStage.show();
|
||||
errorStage.requestFocus();
|
||||
}
|
||||
|
||||
private Stage createErrorStage(final String title, final ListView<ConstraintViolation> errors) {
|
||||
final Stage s = new Stage(StageStyle.UNIFIED);
|
||||
if (title != null)
|
||||
s.setTitle(title);
|
||||
s.initModality(Modality.NONE);
|
||||
if (getScene() != null)
|
||||
s.initOwner(getScene().getWindow());
|
||||
|
||||
final Scene scene = new Scene(errors);
|
||||
s.setScene(scene);
|
||||
s.setAlwaysOnTop(false);
|
||||
s.sizeToScene();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.preferences;
|
||||
|
||||
import fr.cenra.rhomeo.Rhomeo;
|
||||
import fr.cenra.rhomeo.api.preferences.PreferenceGroup;
|
||||
import fr.cenra.rhomeo.api.ui.PreferencePane;
|
||||
import fr.cenra.rhomeo.core.RhomeoCore;
|
||||
import fr.cenra.rhomeo.core.preferences.ftp.FTPAccess;
|
||||
import fr.cenra.rhomeo.core.preferences.ftp.FTPPreferences;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.util.StringConverter;
|
||||
import org.geotoolkit.gui.javafx.util.TaskManager;
|
||||
import org.geotoolkit.internal.GeotkFX;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
@Scope("prototype")
|
||||
public class FXFTPConfiguration extends BorderPane implements PreferencePane {
|
||||
|
||||
@FXML
|
||||
private ComboBox<FTPPreferences.ACCESS_POINTS> uiAccess;
|
||||
|
||||
@Autowired
|
||||
FTPPreferences prefs;
|
||||
|
||||
@FXML
|
||||
private TextField uiURL;
|
||||
|
||||
@FXML
|
||||
private Spinner<Integer> uiPort;
|
||||
|
||||
@FXML
|
||||
private TextField uiDir;
|
||||
|
||||
@FXML
|
||||
private TextField uiLogin;
|
||||
|
||||
@FXML
|
||||
private PasswordField uiPassword;
|
||||
|
||||
@FXML
|
||||
private Button uiTest;
|
||||
|
||||
private FTPAccess access;
|
||||
|
||||
public FXFTPConfiguration() {
|
||||
Rhomeo.loadFXML(this);
|
||||
uiPort.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 65535, 21, 1));
|
||||
Rhomeo.initComboBox(uiAccess, FXCollections.observableArrayList(Arrays.asList(FTPPreferences.ACCESS_POINTS.values())));
|
||||
uiAccess.setEditable(false);
|
||||
uiAccess.setConverter(new StringConverter<FTPPreferences.ACCESS_POINTS>() {
|
||||
@Override
|
||||
public String toString(FTPPreferences.ACCESS_POINTS object) {
|
||||
if (object == null)
|
||||
return null;
|
||||
return object.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FTPPreferences.ACCESS_POINTS fromString(String string) {
|
||||
if (string == null)
|
||||
return null;
|
||||
for (final FTPPreferences.ACCESS_POINTS ap : FTPPreferences.ACCESS_POINTS.values()) {
|
||||
if (string.equals(ap))
|
||||
return ap;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
uiAccess.valueProperty().addListener(this::accessChanged);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void cancel(ActionEvent event) {
|
||||
if (access == null)
|
||||
clearForm();
|
||||
else
|
||||
fillFromAcccess();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void checkConnection(ActionEvent event) {
|
||||
final String adress = uiURL.getText();
|
||||
if (adress == null || adress.isEmpty()) {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de tester la connexion si le champs d'adresse est vide !");
|
||||
}
|
||||
|
||||
final int port = uiPort.getValue();
|
||||
final String workDir = uiDir.getText();
|
||||
final String login = uiLogin.getText();
|
||||
final String pw = uiPassword.getText();
|
||||
|
||||
final TaskManager.MockTask t = new TaskManager.MockTask(() -> FTPAccess.createClient(adress, port, workDir, login, pw));
|
||||
|
||||
t.setOnSucceeded((evt) -> Platform.runLater(() -> {
|
||||
if (t.getValue() != null) {
|
||||
Rhomeo.showAlert(Alert.AlertType.INFORMATION, "Connexion établie avec succès !");
|
||||
} else {
|
||||
Rhomeo.showAlert(Alert.AlertType.WARNING, "Aucune réponse du serveur !");
|
||||
}
|
||||
}));
|
||||
|
||||
t.setOnFailed(evt -> Platform.runLater(() -> GeotkFX.newExceptionDialog("Impossible de se connecter au service FTP", t.getException()).show()));
|
||||
|
||||
t.runningProperty().addListener((obs, oldValue, newValue) -> uiTest.setDisable(newValue));
|
||||
TaskManager.INSTANCE.submit("Test de connexion", t);
|
||||
}
|
||||
|
||||
private void accessChanged(final ObservableValue<? extends FTPPreferences.ACCESS_POINTS> obs, FTPPreferences.ACCESS_POINTS oldAccess, FTPPreferences.ACCESS_POINTS newAccess) {
|
||||
if (newAccess == null) {
|
||||
access = null;
|
||||
clearForm();
|
||||
|
||||
} else {
|
||||
access = prefs.getAccess(newAccess);
|
||||
fillFromAcccess();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearForm() {
|
||||
uiDir.clear();
|
||||
uiLogin.clear();
|
||||
uiPassword.clear();
|
||||
uiPort.getValueFactory().setValue(21);
|
||||
uiURL.clear();
|
||||
}
|
||||
|
||||
private void fillFromAcccess() {
|
||||
uiDir.setText(access.getWorkingDir());
|
||||
uiLogin.setText(access.getLogin());
|
||||
try {
|
||||
uiPassword.setText(access.getPassword());
|
||||
} catch (GeneralSecurityException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot read password for FTP access", ex);
|
||||
Rhomeo.showAlert(Alert.AlertType.ERROR, "Impossible de lire le mot de passe enregistré");
|
||||
}
|
||||
uiPort.getValueFactory().setValue(access.getPort());
|
||||
uiURL.setText(access.getAdress());
|
||||
}
|
||||
|
||||
private void fillAccess() {
|
||||
if (access == null)
|
||||
return;
|
||||
|
||||
access.setPort(uiPort.getValue());
|
||||
access.setAdress(uiURL.getText());
|
||||
access.setWorkingDir(uiDir.getText());
|
||||
access.setLogin(uiLogin.getText());
|
||||
try {
|
||||
access.setPassword(uiPassword.getText());
|
||||
} catch (GeneralSecurityException ex) {
|
||||
RhomeoCore.LOGGER.log(Level.WARNING, "Cannot write password for FTP access", ex);
|
||||
GeotkFX.newExceptionDialog("Impossible d'enregistrer le mot de passe", ex).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreferenceGroup getPreferenceGroup() {
|
||||
return prefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getEditor() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
fillAccess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.preferences.csv;
|
||||
|
||||
import fr.cenra.rhomeo.api.preferences.InternationalPreferenceKey;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class CSVKey implements InternationalPreferenceKey {
|
||||
|
||||
private final String keyId;
|
||||
private final String defaultValue;
|
||||
|
||||
public CSVKey(final String netKey) {
|
||||
this(netKey, null);
|
||||
}
|
||||
|
||||
public CSVKey(final String netKey, final String defaultValue) {
|
||||
ArgumentChecks.ensureNonNull("Key", netKey);
|
||||
this.keyId = netKey;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright © CENRA (2016)
|
||||
*
|
||||
* remi.clement@espaces-naturels.fr
|
||||
*
|
||||
* This software is a multi-platform and portable application based on
|
||||
* the scientifical toolbox to monitor wetlands. With this toolbox it’s
|
||||
* possible to calculate indicators «RhoMéO» to monitor quality,
|
||||
* fonctionalities and urban and agricultural pressures of wetlands.
|
||||
* Indicators are calculated using naturalist data entered and/or imported,
|
||||
* and geographic data downloaded from a geographic server.
|
||||
*
|
||||
* This software is governed by the CeCILL license under French law and
|
||||
* abiding by the rules of distribution of free software. You can use,
|
||||
* modify and/ or redistribute the software under the terms of the CeCILL
|
||||
* license as circulated by CEA, CNRS and INRIA at the following URL
|
||||
* "http://www.cecill.info".
|
||||
*
|
||||
* As a counterpart to the access to the source code and rights to copy,
|
||||
* modify and redistribute granted by the license, users are provided only
|
||||
* with a limited warranty and the software's author, the holder of the
|
||||
* economic rights, and the successive licensors have only limited
|
||||
* liability.
|
||||
*
|
||||
* In this respect, the user's attention is drawn to the risks associated
|
||||
* with loading, using, modifying and/or developing or reproducing the
|
||||
* software by the user in light of its specific status of free software,
|
||||
* that may mean that it is complicated to manipulate, and that also
|
||||
* therefore means that it is reserved for developers and experienced
|
||||
* professionals having in-depth computer knowledge. Users are therefore
|
||||
* encouraged to load and test the software's suitability as regards their
|
||||
* requirements in conditions enabling the security of their systems and/or
|
||||
* data to be ensured and, more generally, to use and operate it in the
|
||||
* same conditions as regards security.
|
||||
*
|
||||
* The fact that you are presently reading this means that you have had
|
||||
* knowledge of the CeCILL license and that you accept its terms.
|
||||
*/
|
||||
package fr.cenra.rhomeo.preferences.csv;
|
||||
|
||||
import fr.cenra.rhomeo.api.preferences.UserPackagePreferenceGroup;
|
||||
import fr.cenra.rhomeo.core.CSVMapper;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Display preferences of encoding and other CSV formatting parameters. This is
|
||||
* needed for dataset import/export.
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
@Component
|
||||
public class CSVPreferences extends UserPackagePreferenceGroup<CSVKey> {
|
||||
|
||||
public static final CSVKey CHARSET_KEY = new CSVKey("charset", StandardCharsets.UTF_8.name());
|
||||
public static final CSVKey SEPARATOR_KEY = new CSVKey("separator", String.valueOf(CSVMapper.DEFAULT_SEPARATOR));
|
||||
|
||||
private static final List<CSVKey> KEYS = Collections.unmodifiableList(Arrays.asList(new CSVKey[] {
|
||||
CHARSET_KEY, SEPARATOR_KEY
|
||||
}));
|
||||
|
||||
@Override
|
||||
public List getKeys() {
|
||||
return KEYS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Geotoolkit - An Open Source Java GIS Toolkit
|
||||
* http://www.geotoolkit.org
|
||||
*
|
||||
* (C) 2015, Geomatys
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*/
|
||||
package org.geotoolkit.gui.javafx.parameter;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import org.apache.sis.util.UnconvertibleObjectException;
|
||||
|
||||
/**
|
||||
* COPIED FROM GEOTOOLKIT 4.0.0-M0082. Aim is to simplify edition.
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class FXDateEditor extends FXValueEditor {
|
||||
|
||||
static final Class[] SUPPORTED_CLASSES = new Class[]{Date.class, LocalDate.class, LocalDateTime.class};
|
||||
|
||||
private final SimpleObjectProperty dateProperty = new SimpleObjectProperty();
|
||||
private final DatePicker editor = new DatePicker();
|
||||
|
||||
public FXDateEditor(final Spi spi) {
|
||||
super(spi);
|
||||
|
||||
dateProperty.addListener((ObservableValue observable, Object oldValue, Object newValue) -> {
|
||||
if (editor.valueProperty().isBound())
|
||||
return; // Do not throw exception, because user has set editor property as a target for one of his own property.
|
||||
|
||||
if (newValue == null)
|
||||
editor.valueProperty().set(null);
|
||||
else if (newValue instanceof LocalDateTime)
|
||||
editor.valueProperty().set(((LocalDateTime)newValue).toLocalDate());
|
||||
else if (newValue instanceof LocalDate)
|
||||
editor.valueProperty().set((LocalDate)newValue);
|
||||
else if (newValue instanceof Date) {
|
||||
editor.valueProperty().set(new Timestamp(((Date)newValue).getTime()).toLocalDateTime().toLocalDate());
|
||||
} else throw new UnconvertibleObjectException("Cannot convert from "+newValue.getClass() + " to "+LocalDate.class);
|
||||
});
|
||||
|
||||
editor.valueProperty().addListener((ObservableValue<? extends LocalDate> observable, LocalDate oldValue, LocalDate newValue) -> {
|
||||
if (dateProperty.isBound())
|
||||
return; // Do not throw exception, because user has set exposed property as a target for one of his own property.
|
||||
|
||||
if (newValue == null) {
|
||||
dateProperty.set(null);
|
||||
} else {
|
||||
Class valueClass = getValueClass();
|
||||
if (valueClass == null)
|
||||
dateProperty.set(newValue); // TODO : throw an exception ? editor has not been configured, but user already started to work with it.
|
||||
else if (LocalDateTime.class.isAssignableFrom(valueClass))
|
||||
dateProperty.set(newValue.atStartOfDay());
|
||||
else if (LocalDate.class.isAssignableFrom(valueClass))
|
||||
dateProperty.set(newValue);
|
||||
else if (Date.class.isAssignableFrom(valueClass))
|
||||
dateProperty.set(Timestamp.valueOf(newValue.atStartOfDay()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property valueProperty() {
|
||||
return dateProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getComponent() {
|
||||
return editor;
|
||||
}
|
||||
|
||||
/**
|
||||
* SPI
|
||||
*/
|
||||
public static final class Spi extends FXValueEditorSpi {
|
||||
|
||||
@Override
|
||||
public boolean canHandle(Class binding) {
|
||||
for (final Class c : SUPPORTED_CLASSES) {
|
||||
if (c.isAssignableFrom(binding))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FXValueEditor createEditor() {
|
||||
return new FXDateEditor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
package org.geotoolkit.gui.javafx.util;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CustomMenuItem;
|
||||
import javafx.scene.control.Dialog;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ProgressBar;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.Background;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.text.Font;
|
||||
import org.apache.sis.util.ArgumentChecks;
|
||||
import org.geotoolkit.font.FontAwesomeIcons;
|
||||
import org.geotoolkit.internal.GeotkFX;
|
||||
|
||||
/**
|
||||
* A JavaFX component which display progress and encountered errors for all tasks
|
||||
* submitted on a specific task manager.
|
||||
*
|
||||
* The last submitted task is displayed in an Hbox. To see other running tasks
|
||||
* and tasks in error, there's two {@link MenuButton}. Each display custom menu
|
||||
* items containing information about a task.
|
||||
*
|
||||
* The {@link ProgressMonitor} is skinnable using a css stylesheet and the specific
|
||||
* following css classes:
|
||||
* {@link ProgressMonitor.CURRENT_TASK_CSS_CLASS}
|
||||
* {@link ProgressMonitor.CURRENT_TASK_GRAPHIC_CSS_CLASS}
|
||||
* {@link ProgressMonitor.ERROR_TASK_CSS_CLASS}
|
||||
* {@link ProgressMonitor.ERROR_TASK_GRAPHIC_CSS_CLASS}
|
||||
* {@link ProgressMonitor.PROGRESS_MONITOR_CSS_CLASS}
|
||||
* {@link ProgressMonitor.CANCEL_BUTTON_CSS_CLASS}
|
||||
* {@link ProgressMonitor.TASK_PROGRESS_CSS_CLASS}
|
||||
* {@link ProgressMonitor.TASK_PROGRESS_GRAPHIC_CSS_CLASS}
|
||||
* {@link ProgressMonitor.MENU_ITEM_CSS_CLASS}
|
||||
* {@link ProgressMonitor.CLEAR_MENU_ITEM_CSS_CLASS}
|
||||
*
|
||||
*
|
||||
* {@link ProgressMonitor} labels can be parametrized using {@link ResourceBundle}
|
||||
* properties defined by {@link ProgressMonitor.ResourceKey}.
|
||||
*
|
||||
* @author Alexis Manin (Geomatys)
|
||||
*/
|
||||
public class ProgressMonitor extends HBox {
|
||||
|
||||
private static String ICON_LABEL_FONT_FAMILY = "-fx-font-family: FontAwesome Regular;";
|
||||
|
||||
/**
|
||||
* The css classes associated to the {@link ProgressMonitor} nodes.
|
||||
*/
|
||||
public static final String CURRENT_TASK_CSS_CLASS="geotk-progressMonitor-runningTasks";
|
||||
public static final String CURRENT_TASK_GRAPHIC_CSS_CLASS="geotk-progressMonitor-runningTasks-graphic";
|
||||
public static final String ERROR_TASK_CSS_CLASS="geotk-progressMonitor-tasksInError";
|
||||
public static final String ERROR_TASK_GRAPHIC_CSS_CLASS="geotk-progressMonitor-tasksInError-graphic";
|
||||
public static final String PROGRESS_MONITOR_CSS_CLASS="geotk-progressMonitor";
|
||||
public static final String CANCEL_BUTTON_CSS_CLASS="geotk-progressMonitor-cancelButton";
|
||||
public static final String TASK_PROGRESS_CSS_CLASS="geotk-progressMonitor-taskProgress";
|
||||
public static final String TASK_PROGRESS_GRAPHIC_CSS_CLASS="geotk-progressMonitor-taskProgress-graphic";
|
||||
public static final String MENU_ITEM_CSS_CLASS="geotk-progressMonitor-menuItem";
|
||||
public static final String CLEAR_MENU_ITEM_CSS_CLASS="geotk-progressMonitor-clearMenuItem";
|
||||
|
||||
private TaskProgress lastTask;
|
||||
|
||||
private final MenuButton runningTasks;
|
||||
private final MenuButton tasksInError;
|
||||
|
||||
private final TaskManager taskRegistry;
|
||||
|
||||
static {
|
||||
// Load Font Awesome.
|
||||
final Font font = FXUtilities.FONTAWESOME;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base constructor of progress monitors.
|
||||
*
|
||||
* @param registry The {@link TaskManager} followed by this progress monitor.
|
||||
*/
|
||||
public ProgressMonitor(final TaskManager registry) {
|
||||
ArgumentChecks.ensureNonNull("Input task registry", registry);
|
||||
|
||||
taskRegistry = registry;
|
||||
|
||||
final Label runningIcon = new Label(FontAwesomeIcons.ICON_GEARS_ALIAS);
|
||||
runningIcon.setStyle(ICON_LABEL_FONT_FAMILY);
|
||||
runningIcon.getStyleClass().add(CURRENT_TASK_GRAPHIC_CSS_CLASS);
|
||||
runningTasks = new MenuButton(GeotkFX.getString(ProgressMonitor.class, "currentTasksLabel"), runningIcon);
|
||||
final Label errorIcon = new Label(FontAwesomeIcons.ICON_EXCLAMATION_CIRCLE);
|
||||
errorIcon.setStyle(ICON_LABEL_FONT_FAMILY);
|
||||
errorIcon.getStyleClass().add(ERROR_TASK_GRAPHIC_CSS_CLASS);
|
||||
tasksInError = new MenuButton(GeotkFX.getString(ProgressMonitor.class, "errorTasksLabel"), errorIcon);
|
||||
|
||||
final Tooltip runninTasksTooltip = new Tooltip(GeotkFX.getString(ProgressMonitor.class, "currentTasksTooltip"));
|
||||
runningTasks.setTooltip(runninTasksTooltip);
|
||||
final Tooltip tasksInErrorTooltip = new Tooltip(GeotkFX.getString(ProgressMonitor.class, "errorTasksTooltip"));
|
||||
tasksInError.setTooltip(tasksInErrorTooltip);
|
||||
|
||||
final SimpleListProperty runningTasksProp = new SimpleListProperty(taskRegistry.getSubmittedTasks());
|
||||
final SimpleListProperty failedTasksProp = new SimpleListProperty(taskRegistry.getTasksInError());
|
||||
|
||||
// Hide list of tasks if there's no information available.
|
||||
runningTasks.visibleProperty().bind(runningTasksProp.sizeProperty().greaterThan(1));
|
||||
tasksInError.visibleProperty().bind(failedTasksProp.emptyProperty().not());
|
||||
|
||||
// Display number of tasks on menu button.
|
||||
runningTasks.textProperty().bind(runningTasksProp.sizeProperty().asString());
|
||||
tasksInError.textProperty().bind(failedTasksProp.sizeProperty().asString());
|
||||
|
||||
// Set default visible task the last one submitted.
|
||||
lastTask = new TaskProgress();
|
||||
lastTask.taskProperty().bind(runningTasksProp.valueAt(runningTasksProp.sizeProperty().subtract(1)));
|
||||
|
||||
// Do not reserve size for hidden components.
|
||||
runningTasks.managedProperty().bind(runningTasks.visibleProperty());
|
||||
tasksInError.managedProperty().bind(tasksInError.visibleProperty());
|
||||
lastTask.managedProperty().bind(lastTask.visibleProperty());
|
||||
|
||||
initTasks();
|
||||
|
||||
getChildren().addAll(lastTask, runningTasks, tasksInError);
|
||||
minWidthProperty().bind(prefWidthProperty());
|
||||
prefWidthProperty().set(USE_COMPUTED_SIZE);
|
||||
|
||||
getStyleClass().add(PROGRESS_MONITOR_CSS_CLASS);
|
||||
runningTasks.getStyleClass().add(CURRENT_TASK_CSS_CLASS);
|
||||
tasksInError.getStyleClass().add(ERROR_TASK_CSS_CLASS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill panel with currently submitted tasks. Add listeners on
|
||||
* {@link TaskManager} to be aware of new events.
|
||||
*/
|
||||
private void initTasks() {
|
||||
|
||||
final MenuItem clearErrorItem = new MenuItem(GeotkFX.getString(ProgressMonitor.class, "cleanErrorList"));
|
||||
clearErrorItem.setOnAction(evt -> taskRegistry.getTasksInError().clear());
|
||||
|
||||
final Label icon = new Label(FontAwesomeIcons.ICON_TRASH_O);
|
||||
icon.setStyle(ICON_LABEL_FONT_FAMILY);
|
||||
clearErrorItem.setGraphic(icon);
|
||||
|
||||
clearErrorItem.getStyleClass().add(CLEAR_MENU_ITEM_CSS_CLASS);
|
||||
|
||||
tasksInError.getItems().add(clearErrorItem);
|
||||
tasksInError.getItems().add(new SeparatorMenuItem());
|
||||
|
||||
// Listen on current running tasks
|
||||
final ObservableList<Task> tmpSubmittedTasks = taskRegistry.getSubmittedTasks();
|
||||
tmpSubmittedTasks.addListener((ListChangeListener.Change<? extends Task> c) -> {
|
||||
|
||||
final Set<Task> toAdd = new HashSet<>();
|
||||
final Set<Task> toRemove = new HashSet<>();
|
||||
storeChanges(c, toAdd, toRemove);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
for (final Task task : toAdd) {
|
||||
final CustomMenuItem item = new CustomMenuItem(new TaskProgress(task));
|
||||
item.setHideOnClick(false);
|
||||
runningTasks.getItems().add(item);
|
||||
}
|
||||
// remove Ended tasks
|
||||
runningTasks.getItems().removeIf(new GetItemsForTask(toRemove));
|
||||
});
|
||||
});
|
||||
|
||||
// Check failed tasks.
|
||||
final ObservableList<Task> tmpTasksInError = taskRegistry.getTasksInError();
|
||||
tmpTasksInError.addListener((ListChangeListener.Change<? extends Task> c) -> {
|
||||
final Set<Task> toAdd = new HashSet<>();
|
||||
final Set<Task> toRemove = new HashSet<>();
|
||||
storeChanges(c, toAdd, toRemove);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
for (final Task task : toAdd) {
|
||||
tasksInError.getItems().add(new ErrorMenuItem(task));
|
||||
}
|
||||
// remove Ended tasks
|
||||
tasksInError.getItems().removeIf(new GetItemsForTask(toRemove));
|
||||
});
|
||||
});
|
||||
|
||||
final Runnable initPanel = () -> {
|
||||
final int nbSubmitted = tmpSubmittedTasks.size();
|
||||
// do not add last task to our menu, it will be used on main display.
|
||||
for (int i = 0; i < nbSubmitted; i++) {
|
||||
final CustomMenuItem item = new CustomMenuItem(new TaskProgress(tmpSubmittedTasks.get(i)));
|
||||
item.setHideOnClick(false);
|
||||
runningTasks.getItems().add(item);
|
||||
}
|
||||
|
||||
for (final Task t : tmpTasksInError) {
|
||||
tasksInError.getItems().add(new ErrorMenuItem(t));
|
||||
}
|
||||
};
|
||||
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
initPanel.run();
|
||||
} else {
|
||||
Platform.runLater(initPanel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store all objects depicted by a {@link ListChangeListener} into given
|
||||
* collections.
|
||||
*
|
||||
* @param c The {@link ListChangeListener.Change} containing list content delta.
|
||||
* @param added The collection to store new added objects into.
|
||||
* @param removed Add removed objects in it.
|
||||
*/
|
||||
private static void storeChanges(final ListChangeListener.Change c, final Collection added, final Collection removed) {
|
||||
while (c.next()) {
|
||||
final List<? extends Task> addedSubList = c.getAddedSubList();
|
||||
final List<? extends Task> removeSubList = c.getRemoved();
|
||||
|
||||
if (addedSubList != null && !addedSubList.isEmpty()) {
|
||||
added.addAll(addedSubList);
|
||||
}
|
||||
|
||||
final Iterator<? extends Task> it = removeSubList.iterator();
|
||||
while (it.hasNext()) {
|
||||
final Task current = it.next();
|
||||
if (!added.remove(current)) {
|
||||
removed.add(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The node giving information about a specific task. Allow to see title,
|
||||
* description and current progress, as to cancel the task.
|
||||
*/
|
||||
private static class TaskProgress extends HBox {
|
||||
|
||||
private final Label title = new Label();
|
||||
private final Tooltip description = new Tooltip();
|
||||
private final ProgressBar progress = new ProgressBar();
|
||||
private final Button cancelButton;
|
||||
|
||||
private final ObjectProperty<Task> taskProperty = new SimpleObjectProperty<>();
|
||||
|
||||
TaskProgress() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
TaskProgress(final Task t) {
|
||||
final Label icon = new Label(FontAwesomeIcons.ICON_BAN);
|
||||
icon.setStyle(ICON_LABEL_FONT_FAMILY);
|
||||
icon.getStyleClass().add(TASK_PROGRESS_GRAPHIC_CSS_CLASS);
|
||||
cancelButton = new Button("", icon);
|
||||
|
||||
taskProperty.addListener((ObservableValue<? extends Task> observable, Task oldValue, Task newValue) -> {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
taskUpdated();
|
||||
} else {
|
||||
Platform.runLater(()->taskUpdated());
|
||||
}
|
||||
});
|
||||
|
||||
taskProperty.set(t);
|
||||
|
||||
getChildren().addAll(title, progress, cancelButton);
|
||||
|
||||
getStyleClass().add(TASK_PROGRESS_CSS_CLASS);
|
||||
cancelButton.getStyleClass().add(CANCEL_BUTTON_CSS_CLASS);
|
||||
}
|
||||
|
||||
public ObjectProperty<Task> taskProperty() {
|
||||
return taskProperty;
|
||||
}
|
||||
|
||||
public Task getTask() {
|
||||
return taskProperty.get();
|
||||
}
|
||||
|
||||
public synchronized void taskUpdated() {
|
||||
title.textProperty().unbind();
|
||||
progress.progressProperty().unbind();
|
||||
description.textProperty().unbind();
|
||||
|
||||
cancelButton.setOnAction(null);
|
||||
|
||||
final Task t =taskProperty.get();
|
||||
if (t != null) {
|
||||
title.textProperty().bind(t.titleProperty());
|
||||
description.textProperty().bind(t.messageProperty());
|
||||
progress.progressProperty().bind(t.progressProperty());
|
||||
cancelButton.setOnAction((ActionEvent e) -> t.cancel());
|
||||
setVisible(true);
|
||||
} else {
|
||||
setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple menu items for failed tasks. Display an exception Dialog when clicked.
|
||||
*/
|
||||
private class ErrorMenuItem extends MenuItem {
|
||||
|
||||
final Task failedTask;
|
||||
|
||||
ErrorMenuItem(final Task failedTask) {
|
||||
ArgumentChecks.ensureNonNull("task in error", failedTask);
|
||||
|
||||
this.failedTask = failedTask;
|
||||
// No need for binding here. Task failed, its state should not change anymore.
|
||||
String title = failedTask.getTitle();
|
||||
if (title == null || title.isEmpty())
|
||||
title = GeotkFX.getString(ProgressMonitor.class, "anonymOperation");
|
||||
setText(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))+" - "+title);
|
||||
|
||||
final Label icon = new Label(FontAwesomeIcons.ICON_TRASH_O);
|
||||
icon.setStyle(ICON_LABEL_FONT_FAMILY);
|
||||
|
||||
final Button deleteButton = new Button("", icon);
|
||||
deleteButton.setBorder(Border.EMPTY);
|
||||
deleteButton.setPadding(Insets.EMPTY);
|
||||
deleteButton.setBackground(Background.EMPTY);
|
||||
deleteButton.setOnAction(e -> {
|
||||
taskRegistry.getTasksInError().remove(failedTask);
|
||||
e.consume();
|
||||
});
|
||||
setGraphic(deleteButton);
|
||||
|
||||
final Dialog d = GeotkFX.newExceptionDialog(failedTask.getMessage(), failedTask.getException());
|
||||
d.setResizable(true);
|
||||
|
||||
setOnAction((ActionEvent ae) -> d.show());
|
||||
|
||||
getStyleClass().add(MENU_ITEM_CSS_CLASS);
|
||||
}
|
||||
|
||||
public Task getTask() {
|
||||
return failedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple {@link Predicate} which return current monitor progress bars
|
||||
* which are focused on one of the given tasks.
|
||||
*/
|
||||
private static class GetItemsForTask implements Predicate<MenuItem> {
|
||||
|
||||
private final Collection<? extends Task> tasks;
|
||||
|
||||
GetItemsForTask(final Collection<? extends Task> taskFilter) {
|
||||
ArgumentChecks.ensureNonNull("Input filter tasks", taskFilter);
|
||||
tasks = taskFilter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(MenuItem item) {
|
||||
if (item instanceof CustomMenuItem) {
|
||||
final Node content = ((CustomMenuItem) item).getContent();
|
||||
return (content instanceof TaskProgress
|
||||
&& tasks.contains(((TaskProgress) content).getTask()));
|
||||
} else if (item instanceof ErrorMenuItem) {
|
||||
return tasks.contains(((ErrorMenuItem) item).getTask());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user