version 1.1

This commit is contained in:
2024-10-09 15:06:56 +02:00
parent d81fb678e3
commit 836a412a6d
536 changed files with 63922 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.cenra.rhomeo</groupId>
<artifactId>rhomeo</artifactId>
<version>1.2-SNAPSHOT</version>
</parent>
<groupId>fr.cenra.rhomeo</groupId>
<artifactId>desktop</artifactId>
<packaging>jar</packaging>
<name>Rhomeo desktop</name>
<description>Rhomeo client application</description>
<dependencies>
<dependency>
<groupId>fr.cenra.rhomeo</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>fr.cenra.rhomeo</groupId>
<artifactId>protocol</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.geotoolkit</groupId>
<artifactId>geotk-widgets-javafx</artifactId>
<exclusions>
<exclusion>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</exclusion>
<exclusion>
<groupId>org.fxmisc.richtext</groupId>
<artifactId>richtextfx</artifactId>
</exclusion>
<exclusion>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.geotoolkit</groupId>
<artifactId>geotk-client-wfs</artifactId>
</dependency>
<dependency>
<groupId>org.geotoolkit</groupId>
<artifactId>geotk-jaxp-xsd</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- Override final name for jfx:native command to create a friendly-named package -->
<finalName>Rhomeo</finalName>
<plugins>
<plugin>
<groupId>com.zenjava</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<configuration>
<nativeReleaseVersion>${project.version}</nativeReleaseVersion>
<mainClass>fr.cenra.rhomeo.Launcher</mainClass>
<needMenu>true</needMenu>
<jvmArgs>
<!-- Force GC. G1 can factorize string instances to save
some memory, which can be useful in a restrained environment as this one. -->
<jvmArg>-XX:+UseG1GC</jvmArg>
<!-- Memory management. -->
<jvmArg>-Xms256m</jvmArg>
<jvmArg>-Xmx1G</jvmArg>
<!-- We delegate proxy detection to Java -->
<jvmArg>-Djava.net.useSystemProxies=true</jvmArg>
<!-- Force base encoding to avoid problems when exchanging data with central server. -->
<jvmArg>-Dfile.encoding=UTF-8</jvmArg>
</jvmArgs>
<additionalAppResources>
src/main/deploy/additional
</additionalAppResources>
<!-- License agreement. It does not work well for .deb packages.-->
<bundleArguments>
<copyright>Copyright (C) 2016, CENRA</copyright>
<licenseFile>CLUF</licenseFile>
</bundleArguments>
</configuration>
</plugin>
</plugins>
</build>
</project>
+550
View File
@@ -0,0 +1,550 @@
CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL
Version 2.1 du 2013-06-21
Avertissement
Ce contrat est une licence de logiciel libre issue d'une concertation
entre ses auteurs afin que le respect de deux grands principes préside à
sa rédaction:
* d'une part, le respect des principes de diffusion des logiciels
libres: accès au code source, droits étendus conférés aux utilisateurs,
* d'autre part, la désignation d'un droit applicable, le droit
français, auquel elle est conforme, tant au regard du droit de la
responsabilité civile que du droit de la propriété intellectuelle et
de la protection qu'il offre aux auteurs et titulaires des droits
patrimoniaux sur un logiciel.
Les auteurs de la licence CeCILL (Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
sont:
Commissariat à l'énergie atomique et aux énergies alternatives - CEA,
établissement public de recherche à caractère scientifique, technique et
industriel, dont le siège est situé 25 rue Leblanc, immeuble Le Ponant
D, 75015 Paris.
Centre National de la Recherche Scientifique - CNRS, établissement
public à caractère scientifique et technologique, dont le siège est
situé 3 rue Michel-Ange, 75794 Paris cedex 16.
Institut National de Recherche en Informatique et en Automatique -
Inria, établissement public à caractère scientifique et technologique,
dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153
Le Chesnay cedex.
Préambule
Ce contrat est une licence de logiciel libre dont l'objectif est de
conférer aux utilisateurs la liberté de modification et de
redistribution du logiciel régi par cette licence dans le cadre d'un
modèle de diffusion en logiciel libre.
L'exercice de ces libertés est assorti de certains devoirs à la charge
des utilisateurs afin de préserver ce statut au cours des
redistributions ultérieures.
L'accessibilité au code source et les droits de copie, de modification
et de redistribution qui en découlent ont pour contrepartie de n'offrir
aux utilisateurs qu'une garantie limitée et de ne faire peser sur
l'auteur du logiciel, le titulaire des droits patrimoniaux et les
concédants successifs qu'une responsabilité restreinte.
A cet égard l'attention de l'utilisateur est attirée sur les risques
associés au chargement, à l'utilisation, à la modification et/ou au
développement et à la reproduction du logiciel par l'utilisateur étant
donné sa spécificité de logiciel libre, qui peut le rendre complexe à
manipuler et qui le réserve donc à des développeurs ou des
professionnels avertis possédant des connaissances informatiques
approfondies. Les utilisateurs sont donc invités à charger et tester
l'adéquation du logiciel à leurs besoins dans des conditions permettant
d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus
généralement, à l'utiliser et l'exploiter dans les mêmes conditions de
sécurité. Ce contrat peut être reproduit et diffusé librement, sous
réserve de le conserver en l'état, sans ajout ni suppression de clauses.
Ce contrat est susceptible de s'appliquer à tout logiciel dont le
titulaire des droits patrimoniaux décide de soumettre l'exploitation aux
dispositions qu'il contient.
Une liste de questions fréquemment posées se trouve sur le site web
officiel de la famille des licences CeCILL
(http://www.cecill.info/index.fr.html) pour toute clarification qui
serait nécessaire.
Article 1 - DEFINITIONS
Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une
lettre capitale, auront la signification suivante:
Contrat: désigne le présent contrat de licence, ses éventuelles versions
postérieures et annexes.
Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code
Source et le cas échéant sa documentation, dans leur état au moment de
l'acceptation du Contrat par le Licencié.
Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et
éventuellement de Code Objet et le cas échéant sa documentation, dans
leur état au moment de leur première diffusion sous les termes du Contrat.
Logiciel Modifié: désigne le Logiciel modifié par au moins une
Contribution.
Code Source: désigne l'ensemble des instructions et des lignes de
programme du Logiciel et auquel l'accès est nécessaire en vue de
modifier le Logiciel.
Code Objet: désigne les fichiers binaires issus de la compilation du
Code Source.
Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur
sur le Logiciel Initial.
Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le
Contrat.
Contributeur: désigne le Licencié auteur d'au moins une Contribution.
Concédant: désigne le Titulaire ou toute personne physique ou morale
distribuant le Logiciel sous le Contrat.
Contribution: désigne l'ensemble des modifications, corrections,
traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans
le Logiciel par tout Contributeur, ainsi que tout Module Interne.
Module: désigne un ensemble de fichiers sources y compris leur
documentation qui permet de réaliser des fonctionnalités ou services
supplémentaires à ceux fournis par le Logiciel.
Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce
Module et le Logiciel s'exécutent dans des espaces d'adressage
différents, l'un appelant l'autre au moment de leur exécution.
Module Interne: désigne tout Module lié au Logiciel de telle sorte
qu'ils s'exécutent dans le même espace d'adressage.
GNU GPL: désigne la GNU General Public License dans sa version 2 ou
toute version ultérieure, telle que publiée par Free Software Foundation
Inc.
GNU Affero GPL: désigne la GNU Affero General Public License dans sa
version 3 ou toute version ultérieure, telle que publiée par Free
Software Foundation Inc.
EUPL: désigne la Licence Publique de l'Union européenne dans sa version
1.1 ou toute version ultérieure, telle que publiée par la Commission
Européenne.
Parties: désigne collectivement le Licencié et le Concédant.
Ces termes s'entendent au singulier comme au pluriel.
Article 2 - OBJET
Le Contrat a pour objet la concession par le Concédant au Licencié d'une
licence non exclusive, cessible et mondiale du Logiciel telle que
définie ci-après à l'article 5 <#etendue> pour toute la durée de
protection des droits portant sur ce Logiciel.
Article 3 - ACCEPTATION
3.1 L'acceptation par le Licencié des termes du Contrat est réputée
acquise du fait du premier des faits suivants:
* (i) le chargement du Logiciel par tout moyen notamment par
téléchargement à partir d'un serveur distant ou par chargement à
partir d'un support physique;
* (ii) le premier exercice par le Licencié de l'un quelconque des
droits concédés par le Contrat.
3.2 Un exemplaire du Contrat, contenant notamment un avertissement
relatif aux spécificités du Logiciel, à la restriction de garantie et à
la limitation à un usage par des utilisateurs expérimentés a été mis à
disposition du Licencié préalablement à son acceptation telle que
définie à l'article 3.1 <#acceptation-acquise> ci dessus et le Licencié
reconnaît en avoir pris connaissance.
Article 4 - ENTREE EN VIGUEUR ET DUREE
4.1 ENTREE EN VIGUEUR
Le Contrat entre en vigueur à la date de son acceptation par le Licencié
telle que définie en 3.1 <#acceptation-acquise>.
4.2 DUREE
Le Contrat produira ses effets pendant toute la durée légale de
protection des droits patrimoniaux portant sur le Logiciel.
Article 5 - ETENDUE DES DROITS CONCEDES
Le Concédant concède au Licencié, qui accepte, les droits suivants sur
le Logiciel pour toutes destinations et pour la durée du Contrat dans
les conditions ci-après détaillées.
Par ailleurs, si le Concédant détient ou venait à détenir un ou
plusieurs brevets d'invention protégeant tout ou partie des
fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas
opposer les éventuels droits conférés par ces brevets aux Licenciés
successifs qui utiliseraient, exploiteraient ou modifieraient le
Logiciel. En cas de cession de ces brevets, le Concédant s'engage à
faire reprendre les obligations du présent alinéa aux cessionnaires.
5.1 DROIT D'UTILISATION
Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant
aux domaines d'application, étant ci-après précisé que cela comporte:
1.
la reproduction permanente ou provisoire du Logiciel en tout ou
partie par tout moyen et sous toute forme.
2.
le chargement, l'affichage, l'exécution, ou le stockage du Logiciel
sur tout support.
3.
la possibilité d'en observer, d'en étudier, ou d'en tester le
fonctionnement afin de déterminer les idées et principes qui sont à
la base de n'importe quel élément de ce Logiciel; et ceci, lorsque
le Licencié effectue toute opération de chargement, d'affichage,
d'exécution, de transmission ou de stockage du Logiciel qu'il est en
droit d'effectuer en vertu du Contrat.
5.2 DROIT D'APPORTER DES CONTRIBUTIONS
Le droit d'apporter des Contributions comporte le droit de traduire,
d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel
et le droit de reproduire le logiciel en résultant.
Le Licencié est autorisé à apporter toute Contribution au Logiciel sous
réserve de mentionner, de façon explicite, son nom en tant qu'auteur de
cette Contribution et la date de création de celle-ci.
5.3 DROIT DE DISTRIBUTION
Le droit de distribution comporte notamment le droit de diffuser, de
transmettre et de communiquer le Logiciel au public sur tout support et
par tout moyen ainsi que le droit de mettre sur le marché à titre
onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé.
Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou
non, à des tiers dans les conditions ci-après détaillées.
5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION
Le Licencié est autorisé à distribuer des copies conformes du Logiciel,
sous forme de Code Source ou de Code Objet, à condition que cette
distribution respecte les dispositions du Contrat dans leur totalité et
soit accompagnée:
1.
d'un exemplaire du Contrat,
2.
d'un avertissement relatif à la restriction de garantie et de
responsabilité du Concédant telle que prévue aux articles 8
<#responsabilite> et 9 <#garantie>,
et que, dans le cas où seul le Code Objet du Logiciel est redistribué,
le Licencié permette un accès effectif au Code Source complet du
Logiciel pour une durée d'au moins 3 ans à compter de la distribution du
logiciel, étant entendu que le coût additionnel d'acquisition du Code
Source ne devra pas excéder le simple coût de transfert des données.
5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE
Lorsque le Licencié apporte une Contribution au Logiciel, les conditions
de distribution du Logiciel Modifié en résultant sont alors soumises à
l'intégralité des dispositions du Contrat.
Le Licencié est autorisé à distribuer le Logiciel Modifié, sous forme de
code source ou de code objet, à condition que cette distribution
respecte les dispositions du Contrat dans leur totalité et soit
accompagnée:
1.
d'un exemplaire du Contrat,
2.
d'un avertissement relatif à la restriction de garantie et de
responsabilité du Concédant telle que prévue aux articles 8
<#responsabilite> et 9 <#garantie>,
et, dans le cas où seul le code objet du Logiciel Modifié est redistribué,
3.
d'une note précisant les conditions d'accès effectif au code source
complet du Logiciel Modifié, pendant une période d'au moins 3 ans à
compter de la distribution du Logiciel Modifié, étant entendu que le
coût additionnel d'acquisition du code source ne devra pas excéder
le simple coût de transfert des données.
5.3.3 DISTRIBUTION DES MODULES EXTERNES
Lorsque le Licencié a développé un Module Externe les conditions du
Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué
sous un contrat de licence différent.
5.3.4 COMPATIBILITE AVEC D'AUTRES LICENCES
Le Licencié peut inclure un code soumis aux dispositions d'une des
versions de la licence GNU GPL, GNU Affero GPL et/ou EUPL dans le
Logiciel modifié ou non et distribuer l'ensemble sous les conditions de
la même version de la licence GNU GPL, GNU Affero GPL et/ou EUPL.
Le Licencié peut inclure le Logiciel modifié ou non dans un code soumis
aux dispositions d'une des versions de la licence GNU GPL, GNU Affero
GPL et/ou EUPL et distribuer l'ensemble sous les conditions de la même
version de la licence GNU GPL, GNU Affero GPL et/ou EUPL.
Article 6 - PROPRIETE INTELLECTUELLE
6.1 SUR LE LOGICIEL INITIAL
Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel
Initial. Toute utilisation du Logiciel Initial est soumise au respect
des conditions dans lesquelles le Titulaire a choisi de diffuser son
oeuvre et nul autre n'a la faculté de modifier les conditions de
diffusion de ce Logiciel Initial.
Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi
par le Contrat et ce, pour la durée visée à l'article 4.2 <#duree>.
6.2 SUR LES CONTRIBUTIONS
Le Licencié qui a développé une Contribution est titulaire sur celle-ci
des droits de propriété intellectuelle dans les conditions définies par
la législation applicable.
6.3 SUR LES MODULES EXTERNES
Le Licencié qui a développé un Module Externe est titulaire sur celui-ci
des droits de propriété intellectuelle dans les conditions définies par
la législation applicable et reste libre du choix du contrat régissant
sa diffusion.
6.4 DISPOSITIONS COMMUNES
Le Licencié s'engage expressément:
1.
à ne pas supprimer ou modifier de quelque manière que ce soit les
mentions de propriété intellectuelle apposées sur le Logiciel;
2.
à reproduire à l'identique lesdites mentions de propriété
intellectuelle sur les copies du Logiciel modifié ou non.
Le Licencié s'engage à ne pas porter atteinte, directement ou
indirectement, aux droits de propriété intellectuelle du Titulaire et/ou
des Contributeurs sur le Logiciel et à prendre, le cas échéant, à
l'égard de son personnel toutes les mesures nécessaires pour assurer le
respect des dits droits de propriété intellectuelle du Titulaire et/ou
des Contributeurs.
Article 7 - SERVICES ASSOCIES
7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de
prestations d'assistance technique ou de maintenance du Logiciel.
Cependant le Concédant reste libre de proposer ce type de services. Les
termes et conditions d'une telle assistance technique et/ou d'une telle
maintenance seront alors déterminés dans un acte séparé. Ces actes de
maintenance et/ou assistance technique n'engageront que la seule
responsabilité du Concédant qui les propose.
7.2 De même, tout Concédant est libre de proposer, sous sa seule
responsabilité, à ses licenciés une garantie, qui n'engagera que lui,
lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce,
dans les conditions qu'il souhaite. Cette garantie et les modalités
financières de son application feront l'objet d'un acte séparé entre le
Concédant et le Licencié.
Article 8 - RESPONSABILITE
8.1 Sous réserve des dispositions de l'article 8.2
<#limite-responsabilite>, le Licencié a la faculté, sous réserve de
prouver la faute du Concédant concerné, de solliciter la réparation du
préjudice direct qu'il subirait du fait du Logiciel et dont il apportera
la preuve.
8.2 La responsabilité du Concédant est limitée aux engagements pris en
application du Contrat et ne saurait être engagée en raison notamment:
(i) des dommages dus à l'inexécution, totale ou partielle, de ses
obligations par le Licencié, (ii) des dommages directs ou indirects
découlant de l'utilisation ou des performances du Logiciel subis par le
Licencié et (iii) plus généralement d'un quelconque dommage indirect. En
particulier, les Parties conviennent expressément que tout préjudice
financier ou commercial (par exemple perte de données, perte de
bénéfices, perte d'exploitation, perte de clientèle ou de commandes,
manque à gagner, trouble commercial quelconque) ou toute action dirigée
contre le Licencié par un tiers, constitue un dommage indirect et
n'ouvre pas droit à réparation par le Concédant.
Article 9 - GARANTIE
9.1 Le Licencié reconnaît que l'état actuel des connaissances
scientifiques et techniques au moment de la mise en circulation du
Logiciel ne permet pas d'en tester et d'en vérifier toutes les
utilisations ni de détecter l'existence d'éventuels défauts. L'attention
du Licencié a été attirée sur ce point sur les risques associés au
chargement, à l'utilisation, la modification et/ou au développement et à
la reproduction du Logiciel qui sont réservés à des utilisateurs avertis.
Il relève de la responsabilité du Licencié de contrôler, par tous
moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et
de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens.
9.2 Le Concédant déclare de bonne foi être en droit de concéder
l'ensemble des droits attachés au Logiciel (comprenant notamment les
droits visés à l'article 5 <#etendue>).
9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le
Concédant sans autre garantie, expresse ou tacite, que celle prévue à
l'article 9.2 <#bonne-foi> et notamment sans aucune garantie sur sa
valeur commerciale, son caractère sécurisé, innovant ou pertinent.
En particulier, le Concédant ne garantit pas que le Logiciel est exempt
d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible
avec l'équipement du Licencié et sa configuration logicielle ni qu'il
remplira les besoins du Licencié.
9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le
Logiciel ne porte pas atteinte à un quelconque droit de propriété
intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout
autre droit de propriété. Ainsi, le Concédant exclut toute garantie au
profit du Licencié contre les actions en contrefaçon qui pourraient être
diligentées au titre de l'utilisation, de la modification, et de la
redistribution du Logiciel. Néanmoins, si de telles actions sont
exercées contre le Licencié, le Concédant lui apportera son expertise
technique et juridique pour sa défense. Cette expertise technique et
juridique est déterminée au cas par cas entre le Concédant concerné et
le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage
toute responsabilité quant à l'utilisation de la dénomination du
Logiciel par le Licencié. Aucune garantie n'est apportée quant à
l'existence de droits antérieurs sur le nom du Logiciel et sur
l'existence d'une marque.
Article 10 - RESILIATION
10.1 En cas de manquement par le Licencié aux obligations mises à sa
charge par le Contrat, le Concédant pourra résilier de plein droit le
Contrat trente (30) jours après notification adressée au Licencié et
restée sans effet.
10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à
utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les
licences qu'il aura concédées antérieurement à la résiliation du Contrat
resteront valides sous réserve qu'elles aient été effectuées en
conformité avec le Contrat.
Article 11 - DISPOSITIONS DIVERSES
11.1 CAUSE EXTERIEURE
Aucune des Parties ne sera responsable d'un retard ou d'une défaillance
d'exécution du Contrat qui serait dû à un cas de force majeure, un cas
fortuit ou une cause extérieure, telle que, notamment, le mauvais
fonctionnement ou les interruptions du réseau électrique ou de
télécommunication, la paralysie du réseau liée à une attaque
informatique, l'intervention des autorités gouvernementales, les
catastrophes naturelles, les dégâts des eaux, les tremblements de terre,
le feu, les explosions, les grèves et les conflits sociaux, l'état de
guerre...
11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou
plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du
Contrat, ne pourra en aucun cas impliquer renonciation par la Partie
intéressée à s'en prévaloir ultérieurement.
11.3 Le Contrat annule et remplace toute convention antérieure, écrite
ou orale, entre les Parties sur le même objet et constitue l'accord
entier entre les Parties sur cet objet. Aucune addition ou modification
aux termes du Contrat n'aura d'effet à l'égard des Parties à moins
d'être faite par écrit et signée par leurs représentants dûment habilités.
11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat
s'avèrerait contraire à une loi ou à un texte applicable, existants ou
futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les
amendements nécessaires pour se conformer à cette loi ou à ce texte.
Toutes les autres dispositions resteront en vigueur. De même, la
nullité, pour quelque raison que ce soit, d'une des dispositions du
Contrat ne saurait entraîner la nullité de l'ensemble du Contrat.
11.5 LANGUE
Le Contrat est rédigé en langue française et en langue anglaise, ces
deux versions faisant également foi.
Article 12 - NOUVELLES VERSIONS DU CONTRAT
12.1 Toute personne est autorisée à copier et distribuer des copies de
ce Contrat.
12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé
et ne peut être modifié que par les auteurs de la licence, lesquels se
réservent le droit de publier périodiquement des mises à jour ou de
nouvelles versions du Contrat, qui posséderont chacune un numéro
distinct. Ces versions ultérieures seront susceptibles de prendre en
compte de nouvelles problématiques rencontrées par les logiciels libres.
12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra
faire l'objet d'une diffusion ultérieure que sous la même version du
Contrat ou une version postérieure, sous réserve des dispositions de
l'article 5.3.4 <#compatibilite>.
Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE
13.1 Le Contrat est régi par la loi française. Les Parties conviennent
de tenter de régler à l'amiable les différends ou litiges qui
viendraient à se produire par suite ou à l'occasion du Contrat.
13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter
de leur survenance et sauf situation relevant d'une procédure d'urgence,
les différends ou litiges seront portés par la Partie la plus diligente
devant les Tribunaux compétents de Paris.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

@@ -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;
}
}
}
@@ -0,0 +1,58 @@
.geotk-progressMonitor-cancelButton {
-fx-background-color: transparent;
}
/*Override default css*/
.label {
-fx-text-fill: #ffffff;
}
.geotk-progressMonitor-tasksInError > .arrow-button, .geotk-progressMonitor-runningTasks > .arrow-button {
-fx-padding: 0;
}
.geotk-progressMonitor-tasksInError > .arrow-button > .arrow, .geotk-progressMonitor-runningTasks > .arrow-button > .arrow {
-fx-padding: 0;
}
.geotk-progressMonitor-tasksInError, .geotk-progressMonitor-runningTasks {
-fx-background-color: transparent;
-fx-border-style: none;
-fx-border-width: 2;
}
.geotk-progressMonitor-tasksInError {
-fx-text-fill: green;
}
.geotk-progressMonitor-clearMenuItem .label {
-fx-font-size: 14px;
}
.geotk-progressMonitor-menuItem .label {
-fx-font-size: 12px;
}
.geotk-progressMonitor {
-fx-spacing: 10;
-fx-alignment: center;
-fx-font-size: 15px;
-fx-background-color: transparent;
}
.geotk-progressMonitor-taskProgress{
-fx-spacing: 5;
-fx-alignment: center;
}
.geotk-progressMonitor-runningTasks-graphic{
-fx-text-fill: aquamarine;
}
.geotk-progressMonitor-tasksInError-graphic{
-fx-text-fill: #aa0000;
}
.geotk-progressMonitor-taskProgress-graphic{
-fx-text-fill: #aa0000;
}
@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.92" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox id="sitebar" fx:id="uiSiteBar" maxWidth="1.7976931348623157E308" prefHeight="40.0" prefWidth="1024.0" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<children>
<HBox fx:id="uiToCollapseButtonsSiteHbox" styleClass="cursor-hand">
<children>
<ImageView fx:id="uiConfigSiteImg" fitHeight="26.0" fitWidth="26.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<HBox.margin>
<Insets right="5.0" />
</HBox.margin>
</ImageView>
<Label maxHeight="1.7976931348623157E308" text="%site-protocol" HBox.hgrow="NEVER">
<HBox.margin>
<Insets left="5.0" right="5.0" />
</HBox.margin>
</Label>
<ImageView fx:id="uiCaretSiteImg" fitHeight="18.0" fitWidth="18.0" pickOnBounds="true" preserveRatio="true">
<HBox.margin>
<Insets left="5.0" top="5.0" />
</HBox.margin>
</ImageView>
</children>
</HBox>
<Pane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
<Label fx:id="uiSelectedSiteLbl" maxHeight="1.7976931348623157E308" text="Site">
<padding>
<Insets right="20.0" />
</padding>
<HBox.margin>
<Insets />
</HBox.margin>
</Label>
</children>
</HBox>
</top>
<center>
<BorderPane id="siteprotocol" fx:id="uiProtocolSitePane" BorderPane.alignment="CENTER">
<top>
<HBox id="selectedsitebar" fx:id="uiSiteProtocolHbox" maxHeight="-Infinity" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="42.0" prefWidth="1024.0" spacing="10.0" BorderPane.alignment="CENTER">
<children>
<Label maxHeight="1.7976931348623157E308" text="%site" />
<ComboBox fx:id="uiSitesList" maxWidth="-Infinity" prefWidth="300.0">
<HBox.margin>
<Insets top="1.0" />
</HBox.margin>
</ComboBox>
<ToggleButton id="viewinfobtn" fx:id="uiInfoSiteToggleBtn" mnemonicParsing="false" prefHeight="26.0" prefWidth="26.0" styleClass="no-border">
<HBox.margin>
<Insets top="1.0" />
</HBox.margin>
</ToggleButton>
<ToggleButton fx:id="uiNewSiteToggleBtn" maxWidth="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefWidth="145.0" text="%new-site">
<HBox.margin>
<Insets top="1.0" />
</HBox.margin>
</ToggleButton>
<Button maxWidth="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#importSite" prefWidth="145.0" text="%import-site">
<HBox.margin>
<Insets top="1.0" />
</HBox.margin>
</Button>
</children>
<padding>
<Insets bottom="5.0" left="20.0" right="20.0" top="7.0" />
</padding>
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</HBox>
</top>
<center>
<BorderPane fx:id="uiSiteInfoPane" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets />
</BorderPane.margin>
<bottom>
<HBox prefHeight="46.0" prefWidth="1024.0" BorderPane.alignment="CENTER">
<children>
<Pane prefHeight="0.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
<Button fx:id="uiCancelBtn" mnemonicParsing="false" onAction="#cancel" prefHeight="30.0" prefWidth="100.0" styleClass="no-border" text="%cancel">
<HBox.margin>
<Insets left="20.0" right="20.0" />
</HBox.margin>
</Button>
<Button fx:id="uiCreateSiteBtn" mnemonicParsing="false" onAction="#createOrUpdateSite" prefHeight="30.0" prefWidth="150.0" styleClass="create-button">
<HBox.margin>
<Insets left="20.0" right="20.0" />
</HBox.margin>
</Button>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</HBox>
</bottom>
<center>
<BorderPane id="siteinfo" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
</BorderPane.margin>
<center>
<GridPane fx:id="uiInfoSiteGrid" hgap="10.0" vgap="5.0">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="100.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="100.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" minWidth="70.0" prefWidth="70.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="NEVER" maxWidth="30.0" minWidth="30.0" prefWidth="30.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%sites-info-title" GridPane.columnSpan="2" />
<Label text="%info-site" GridPane.rowIndex="1">
<GridPane.margin>
<Insets left="30.0" />
</GridPane.margin>
</Label>
<Label text="%referent-site" GridPane.rowIndex="2">
<GridPane.margin>
<Insets left="30.0" />
</GridPane.margin>
</Label>
<Label text="%contour-site" GridPane.rowIndex="3">
<GridPane.margin>
<Insets left="30.0" />
</GridPane.margin>
</Label>
<Label text="%departement-site" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<Label text="%structure-site" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<Label text="%type-zh-site" GridPane.columnIndex="4" GridPane.rowIndex="1" />
<Label text="%zb-odo-site" GridPane.columnIndex="4" GridPane.rowIndex="2" />
<Label text="%zb-ortho-site" GridPane.columnIndex="4" GridPane.rowIndex="3" />
<TextField fx:id="uiNameSiteTxt" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<ComboBox fx:id="uiDepartmentSiteComboBox" maxWidth="1.7976931348623157E308" GridPane.columnIndex="3" GridPane.rowIndex="1" />
<ComboBox fx:id="uiTypeZhSiteComboBox" maxWidth="1.7976931348623157E308" GridPane.columnIndex="5" GridPane.rowIndex="1" />
<TextField fx:id="uiReferentSiteTxt" maxWidth="1.7976931348623157E308" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextField fx:id="uiStructureSiteTxt" maxWidth="1.7976931348623157E308" GridPane.columnIndex="3" GridPane.rowIndex="2" />
<ComboBox fx:id="uiZbOdoSiteComboBox" maxWidth="1.7976931348623157E308" GridPane.columnIndex="5" GridPane.rowIndex="2" />
<Button fx:id="uiImportContourBtn" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#importOrViewContour" text="%import-contour" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<ComboBox fx:id="uiZbOrthoSiteComboBox" maxWidth="1.7976931348623157E308" GridPane.columnIndex="5" GridPane.rowIndex="3" />
<HBox fx:id="uiInfoSelectedSiteBtnsBox" alignment="TOP_RIGHT" GridPane.columnIndex="5" GridPane.columnSpan="2">
<children>
<ToggleButton fx:id="uiEditSelectedSiteToggleBtn" mnemonicParsing="false" prefHeight="28.0" prefWidth="28.0" styleClass="no-border" />
<Button fx:id="uiDownloadSelectedSiteBtn" mnemonicParsing="false" onAction="#downloadSelectedSite" pickOnBounds="true" prefHeight="28.0" prefWidth="28.0" styleClass="no-border" />
<Button fx:id="uiDeleteSelectedSiteBtn" mnemonicParsing="false" onAction="#deleteSelectedSite" pickOnBounds="true" prefHeight="28.0" prefWidth="28.0" styleClass="no-border" />
</children>
<GridPane.margin>
<Insets />
</GridPane.margin>
</HBox>
</children>
</GridPane>
</center>
<padding>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
</padding>
</BorderPane>
</center>
</BorderPane>
</center>
<bottom>
<HBox id="protocolsbar" fx:id="protocolsBannerBox" prefHeight="40.0" prefWidth="1024.0" spacing="20.0" BorderPane.alignment="CENTER">
<children>
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="%protocoles" />
</children>
<padding>
<Insets bottom="5.0" left="20.0" right="20.0" top="5.0" />
</padding>
</HBox>
</bottom>
</BorderPane>
</center>
</fx:root>
@@ -0,0 +1,32 @@
site=SITE
new-site=Nouveau site
import-site=Importer un site
site-protocol=SITE ET PROTOCOLE
protocoles=PROTOCOLES
create-site=Cr\u00e9er le site
cancel=Annuler
save=Enregistrer
sites-info-title=INFORMATIONS SUR LE SITE
info-site=Site *
referent-site=R\u00e9f\u00e9rent
contour-site=Contour *
departement-site=D\u00e9partement *
structure-site=Structure
type-zh-site=Type ZH *
zb-odo-site=ZB odo
zb-ortho-site=ZB ortho
import-contour=Importer un contour de site
view-contour=Visualiser
cleanProgressMonitorList=Vider la liste
downloadShape=Format .shp
downloadKml=Format .kml
download-selected-site-tooltip=T\u00e9l\u00e9charger le site s\u00e9lectionn\u00e9
delete-selected-site-tooltip=Supprimer le site s\u00e9lectionn\u00e9
edit-selected-site-tooltip=Edition / Lecture simple pour le site s\u00e9lectionn\u00e9
view-info-site-tooltip=Afficher / Masquer les informations du site s\u00e9lectionn\u00e9
alert-site-deleted=La suppression du site \"{0}\" supprimera \u00e9galement ses donn\u00e9es associ\u00e9es. Etes-vous s\u00fbr ?
alert-site-download=L'extension du fichier de sortie doit \u00eatre .shp, .zip ou .kml.
alert-site-createOrUpdate=Un site est d\u00e9j\u00e0 pr\u00e9sent portant le nom \"{0}\". Veuillez saisir un nouveau nom.
unsaved-modification=Les modifications non sauvegard\u00e9es seront perdues. Etes-vous s\u00fbr de vouloir continuer ?
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<BorderPane id="dashboardbody" fx:id="uiDashboardBodyPane" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="5.0" />
</padding>
<bottom>
<HBox prefHeight="40.0" prefWidth="1024.0" spacing="10.0" BorderPane.alignment="CENTER">
<children>
<Pane HBox.hgrow="ALWAYS" />
<Button fx:id="uiPublish" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#publishData" text="%publish" />
<Button fx:id="uiExport" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#exportData" text="%export" />
</children>
<padding>
<Insets bottom="5.0" top="5.0" />
</padding>
<BorderPane.margin>
<Insets top="5.0" />
</BorderPane.margin>
</HBox>
</bottom>
<top>
<HBox prefHeight="40.0" prefWidth="1024.0" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" top="5.0" />
</padding>
<children>
<Label maxHeight="1.7976931348623157E308" text="%title-table" />
</children>
</HBox>
</top>
</BorderPane>
</center>
</fx:root>
@@ -0,0 +1,5 @@
title-table=TABLEAU DE BORD
publish=Publier les r\u00e9sultats
export=Exporter les r\u00e9sultats
year=Ann\u00e9e
publish-title=Publication des r\u00e9sultats sur le serveur
@@ -0,0 +1,5 @@
.pane {
-fx-padding: 15;
-fx-hgap: 10;
-fx-vgap: 10;
}
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root spacing="10.0" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label styleClass="text-bold" text="%indicatorLabel" />
<GridPane fx:id="uiIndicatorPane" hgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="-Infinity" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="-Infinity" minWidth="-Infinity" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="-Infinity" minWidth="-Infinity" />
<ColumnConstraints hgrow="SOMETIMES" />
</columnConstraints>
<rowConstraints>
<RowConstraints />
</rowConstraints>
<padding>
<Insets left="10.0" />
</padding>
</GridPane>
<Label styleClass="text-bold" text="%yearLabel">
<padding>
<Insets top="5.0" />
</padding></Label>
<FlowPane fx:id="uiYearPane" hgap="40.0" vgap="10.0">
<padding>
<Insets left="10.0" />
</padding></FlowPane>
<Label styleClass="text-bold" text="%trackingPointLabel">
<padding>
<Insets top="5.0" />
</padding></Label>
<BorderPane fx:id="uiTrackingPointPane" VBox.vgrow="ALWAYS" />
<VBox fx:id="uiProgressBox" VBox.vgrow="NEVER">
<children>
<Label fx:id="uiProgressTitle" text="Label">
<padding>
<Insets top="5.0" />
</padding></Label>
<HBox alignment="CENTER" spacing="5.0">
<children>
<ProgressBar fx:id="uiProgress" maxWidth="1.7976931348623157E308" progress="0.0" HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" onAction="#cancelProcess" text="%process.cancel" HBox.hgrow="NEVER" />
</children>
</HBox>
<Label fx:id="uiProgressMessage" text="Label">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
</Label>
</children>
</VBox>
<HBox fx:id="uiBtnBox" alignment="CENTER_RIGHT">
<children>
<Button mnemonicParsing="false" onAction="#nextStep" styleClass="contrast" text="%proceed" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</fx:root>
@@ -0,0 +1,12 @@
indicatorLabel=Indicateurs \u00e0 calculer
yearLabel=Ann\u00e9es souhait\u00e9es
trackingPointLabel=Points de suivi retenus
selectionColumn=S\u00e9lection
nameColumn=Point de suivi
dateColumn=Date
countColumn=Nb. de relev\u00e9s
proceed=Calculer
process-failed=Le calcul a \u00e9chou\u00e9
process.prepare=Pr\u00e9paration des calculs
process.cancel=Annuler
process.go=Calcul des indicateurs
@@ -0,0 +1,18 @@
.summary-box {
-fx-background-color:-white-translucent;
-fx-border-color:lightgrey;
-fx-border-width:1;
}
.button-gray {
-fx-background-color: #949494;
-fx-text-fill: #FFF;
-fx-border-width: 0;
-fx-background-radius: none;
-fx-border-radius: none;
}
.text-gray-italic {
-fx-text-fill: #949494;
-fx-font-style: italic;
}
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?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.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root fx:id="defaultResultPane" spacing="5.0" stylesheets="@FXDefaultResultPane.css" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<VBox spacing="5.0" styleClass="summary-box" VBox.vgrow="NEVER">
<children>
<Label styleClass="text-bold" text="%summaryLabel" />
<Label fx:id="uiYearsLbl" />
<Label fx:id="uiIndicatorLbl" />
<Label fx:id="uiDataStatusLbl" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
<GridPane fx:id="uiInvalidateGridPane" hgap="10.0" minHeight="50.0" vgap="5.0" VBox.vgrow="NEVER">
<columnConstraints>
<ColumnConstraints fillWidth="false" hgrow="NEVER" maxWidth="250.0" prefWidth="250.0" />
<ColumnConstraints fillWidth="false" hgrow="NEVER" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ToggleButton fx:id="uiDisplayValuesToggleBtn" maxWidth="250.0" mnemonicParsing="false" prefWidth="250.0" styleClass="button-gray" text="%display-values" textAlignment="CENTER" />
<Button fx:id="uiExportDataBtn" maxWidth="250.0" mnemonicParsing="false" onAction="#exportData" prefWidth="250.0" styleClass="button-gray" text="%export-data" textAlignment="CENTER" GridPane.rowIndex="1" />
<Label styleClass="text-gray-italic" text="%indicatif" GridPane.columnIndex="1" />
<Label styleClass="text-gray-italic" text="%indicatif" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
</GridPane>
<VBox fx:id="uiTableBox">
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
<children>
<Label fx:id="uiSiteLbl" styleClass="text-bold" text="%siteLabel" />
<TableView fx:id="uiSitePane" maxHeight="400.0" minHeight="70.0" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="uiSiteYearColumn" maxWidth="200.0" minWidth="100.0" prefWidth="-1.0" text="%yearSiteColumn" />
</columns>
</TableView>
<Label fx:id="uiAdditionalLabel" styleClass="text-bold" text="%additional.title">
<VBox.margin>
<Insets top="20.0" />
</VBox.margin>
</Label>
<ScrollPane fx:id="uiAdditionalValuesScrollPane" fitToWidth="true" minHeight="50.0" VBox.vgrow="SOMETIMES">
<content>
<VBox fx:id="uiAdditionalValuesBox" spacing="10.0" />
</content>
</ScrollPane>
</children>
</VBox>
</children>
<padding>
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</padding>
</fx:root>
@@ -0,0 +1,8 @@
summaryLabel=R\u00e9sum\u00e9
siteLabel=Site
yearSiteColumn=Ann\u00e9e
proceed=Finaliser
display-values=Afficher les valeurs calcul\u00e9es
export-data=Exporter les donn\u00e9es
indicatif=A titre indicatif
additional.title=Valeurs compl\u00e9mentaires
@@ -0,0 +1,22 @@
.final-blue-button {
-fx-background-color: -menu-color;
-fx-border-width: 0;
-fx-text-fill: #FFF;
-fx-background-radius: 0;
}
.final-blue-button:disabled {
-fx-background-color: -light-gray-bg;
-fx-text-fill: -light-text-color;
-fx-border-width: 1px;
}
.final-blue-button.green-bg-button {
-fx-background-color: -protocol-color;
}
.final-label-right{
-fx-background-color: #FFF;
-fx-border-width: 1px;
-fx-border-color: -light-gray-bg;
}
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root stylesheets="@FXFinalizationPane.css" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GridPane hgap="10.0" vgap="15.0" BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" maxWidth="-Infinity" prefWidth="250.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button alignment="CENTER" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#exportResults" styleClass="final-blue-button" text="%export-results" />
<Button fx:id="addToDashboardBtn" alignment="CENTER" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#addToDashboard" styleClass="final-blue-button" text="%add-to-dashboard" GridPane.rowIndex="1" />
<Button fx:id="publishBtn" alignment="CENTER" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#publishResults" styleClass="final-blue-button" text="%publish-results" GridPane.rowIndex="2" />
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" styleClass="final-label-right" text="%text-export-results" GridPane.columnIndex="1">
<padding>
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0" />
</padding>
</Label>
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" styleClass="final-label-right" text="%text-add-to-dashboard" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="1">
<padding>
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0" />
</padding>
</Label>
<Label maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" styleClass="final-label-right" text="%text-publish-results" GridPane.columnIndex="1" GridPane.rowIndex="2">
<padding>
<Insets bottom="5.0" left="15.0" right="15.0" top="5.0" />
</padding>
</Label>
</children>
</GridPane>
</children>
<padding>
<Insets bottom="30.0" left="20.0" right="20.0" top="30.0" />
</padding>
</fx:root>
@@ -0,0 +1,7 @@
export-results=Exporter les r\u00e9sultats
add-to-dashboard=Ajouter au tableau de bord
publish-results=Publier les r\u00e9sultats
text-export-results=Exporter les r\u00e9sultats au format .csv.
text-add-to-dashboard=Ajouter les r\u00e9sultats au tableau de bord.\nAttention : si des r\u00e9sultats sont d\u00e9j\u00e0 pr\u00e9sents dans le tableau de bord pour les donn\u00e9es s\u00e9lectionn\u00e9es, ils seront \u00e9cras\u00e9s.
alert-add-to-dashboard=Si des valeurs \u00e9taient d\u00e9j\u00e0 pr\u00e9sentes dans le tableau de bord pour un indicateur \u00e0 une ann\u00e9e donn\u00e9e, celles-ci seront \u00e9cras\u00e9es.\n Voulez-vous continuer ?
text-publish-results=Publier les r\u00e9sultats sur le serveur.
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?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.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root id="initPopup" prefWidth="480.0" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<VBox spacing="20.0" BorderPane.alignment="CENTER">
<children>
<Label focusTraversable="false" text="%first-line" wrapText="true">
<VBox.margin>
<Insets />
</VBox.margin>
</Label>
<Label focusTraversable="false" text="%second-line" wrapText="true">
<VBox.margin>
<Insets />
</VBox.margin>
</Label>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<VBox.margin>
<Insets />
</VBox.margin>
<children>
<ImageView fx:id="uiWarning" fitHeight="24.0" fitWidth="24.0" pickOnBounds="true" preserveRatio="true" />
<Label focusTraversable="false" text="%third-line" wrapText="true" />
</children>
</HBox>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label style="-fx-font-style: italic;" text="Contact :" />
<Label style="-fx-font-style: italic;" text="Plus d'informations :" GridPane.rowIndex="1" />
<Hyperlink fx:id="uiEMail" text="%contact-email" GridPane.columnIndex="1" />
<Hyperlink fx:id="uiHref" text="%info-href" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
</GridPane>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
<BorderPane.margin>
<Insets top="20.0" />
</BorderPane.margin>
</VBox>
</center>
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
<bottom>
<VBox BorderPane.alignment="CENTER">
<children>
<CheckBox fx:id="displayOnStartup" mnemonicParsing="false" text="%displayOnStartup" VBox.vgrow="NEVER" />
<HBox alignment="CENTER" VBox.vgrow="NEVER">
<children>
<Button fx:id="uiClose" mnemonicParsing="false" onAction="#accept" text="Accepter et continuer" />
</children>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</HBox>
</children>
<BorderPane.margin>
<Insets top="10.0" />
</BorderPane.margin>
</VBox>
</bottom>
<top>
<Label fx:id="uiVersion" style="-fx-font-style: italic;" BorderPane.alignment="CENTER_RIGHT" />
</top>
</fx:root>
@@ -0,0 +1,6 @@
first-line=Le Conservatoire d'Espaces Naturels Rh\u00f4ne-Alpes avec le soutien de la R\u00e9gion Auvergne-Rh\u00f4ne-Alpes et de l'Agence de l'eau Rh\u00f4ne M\u00e9diterran\u00e9e Corse fournit ce logiciel.
second-line=Il permet de calculer les indicateurs de la boite \u00e0 outils de suivi des zones humides du bassin Rh\u00f4ne-M\u00e9diterran\u00e9e (RhoM\u00e9O) \u00e0 partir des donn\u00e9es de chaque utilisateur.
third-line=Attention, la pertinence des r\u00e9sultats d\u00e9pend du respect strict des diff\u00e9rents protocoles de suivi et de leurs conditions de validit\u00e9.
displayOnStartup=Ne plus afficher cette fen\u00eatre au d\u00e9marrage
contact-email=calculette.rhomeo@mail.cenrhonealpes.org
info-href=http://rhomeo-bao.fr/?q=calculette
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.MenuButton?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<fx:root minHeight="480.0" minWidth="640.0" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox id="menubar" alignment="CENTER_LEFT" BorderPane.alignment="CENTER_LEFT">
<children>
<ImageView fitHeight="28.0" fitWidth="28.0" pickOnBounds="true" preserveRatio="true" HBox.hgrow="NEVER">
<image>
<Image url="@../images/logo.png" />
</image>
<HBox.margin>
<Insets left="5.0" />
</HBox.margin>
</ImageView>
<Label alignment="TOP_CENTER" contentDisplay="CENTER" styleClass="title" text="%title">
<padding>
<Insets left="10.0" />
</padding>
</Label>
<Pane maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS" />
<MenuButton fx:id="uiMenuButton" alignment="TOP_LEFT" mnemonicParsing="false" nodeOrientation="LEFT_TO_RIGHT">
<items>
<MenuItem mnemonicParsing="false" onAction="#showPreferences" text="%preferences" />
<MenuItem mnemonicParsing="false" onAction="#showRefDocuments" text="%ref-docs" />
<MenuItem mnemonicParsing="false" onAction="#showAboutDialog" text="%about" />
<MenuItem mnemonicParsing="false" onAction="#quit" text="%quit" />
</items>
<styleClass>
<String fx:value="cursor-hand" />
<String fx:value="rhom-menu-button" />
</styleClass>
<HBox.margin>
<Insets />
</HBox.margin>
</MenuButton>
</children>
<padding>
<Insets bottom="2.0" left="5.0" right="5.0" top="2.0" />
</padding>
</HBox>
</top>
</fx:root>
@@ -0,0 +1,5 @@
title=CALCULETTE RHOMEO
preferences=Configuration
ref-docs=R\u00e9f\u00e9rences
about=A propos
quit=Quitter
@@ -0,0 +1,44 @@
.install-button {
-fx-padding: 4 13 4 13;
-fx-border-width:1;
-fx-border-color:lightgrey;
-fx-border-radius:3;
-fx-background-radius:3;
-fx-background-color:transparent;
-fx-min-height: 23px;
-fx-max-height: 23px;
}
.install-button:hover {
-fx-background-color: rgba(212, 212, 212, 0.5);
}
.install-button:armed {
-fx-border-color:lightskyblue;
}
.white-button {
-fx-padding: 4 13 4 13;
-fx-border-width:1;
-fx-border-color:lightgrey;
-fx-border-radius:4;
-fx-background-radius:4;
-fx-background-color:white;
}
.white-button:hover {
-fx-background-color: aliceblue;
}
.white-button:armed {
-fx-border-color:lightgrey;
}
.white-box {
-fx-background-color: white;
-fx-border-color:lightgrey;
-fx-border-width:1;
-fx-padding:10;
-fx-border-radius: 4;
-fx-background-radius: 4;
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<fx:root minHeight="-Infinity" minWidth="-Infinity" prefHeight="100.0" prefWidth="600.0" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.92" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox maxHeight="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="40.0" prefWidth="600.0" BorderPane.alignment="CENTER">
<children>
<Label fx:id="uiRefNameLbl" alignment="CENTER" contentDisplay="CENTER" maxWidth="1.7976931348623157E308" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</HBox>
</top>
</fx:root>
@@ -0,0 +1,5 @@
.hBox {
-fx-padding: 15;
-fx-spacing: 10;
-fx-alignment: center-right;
}
@@ -0,0 +1,5 @@
# To change this license header, choose License Headers in Project Properties.
# To change this template file, choose Tools | Templates
# and open the template in the editor.
cancel=Annuler
validate=Valider
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.92" xmlns:fx="http://javafx.com/fxml/1">
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<center>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="20.0" BorderPane.alignment="CENTER">
<children>
<Label text="%models-files" />
<GridPane fx:id="uiModelsBtnGrid" vgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
<Separator prefWidth="200.0" />
<Label text="%protocols-files" />
<GridPane fx:id="uiProtocolsBtnGrid" vgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
</GridPane>
</children>
</VBox>
</center>
<bottom>
<HBox BorderPane.alignment="CENTER">
<children>
<Pane HBox.hgrow="ALWAYS" />
<Button mnemonicParsing="false" onAction="#close" text="%close" />
</children>
</HBox>
</bottom>
</fx:root>
@@ -0,0 +1,3 @@
protocols-files=Fiches de protocole
models-files=Mod\u00e8les de fichiers
close=Fermer
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<VBox fx:id="uiHeader" VBox.vgrow="NEVER">
<children>
<HBox spacing="10.0" styleClass="banner-grey-bg" VBox.vgrow="NEVER">
<children>
<HBox id="protocolChosenGreen" fx:id="uiProtocolChosenBox" spacing="10.0">
<HBox.margin>
<Insets />
</HBox.margin>
<children>
<Label fx:id="uiSelectedProtocolLbl" maxHeight="1.7976931348623157E308" />
</children>
<padding>
<Insets bottom="5.0" left="20.0" right="20.0" top="5.0" />
</padding>
</HBox>
<Label maxHeight="1.7976931348623157E308" styleClass="text-bold" text="%site" />
<Label fx:id="uiSelectedSiteLbl" maxHeight="1.7976931348623157E308" />
<Pane HBox.hgrow="ALWAYS" />
<Button fx:id="uiRefUsedBtn" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#showUsedRef" text="%refUsed">
<HBox.margin>
<Insets bottom="5.0" top="5.0" />
</HBox.margin>
</Button>
<Button fx:id="uiStopProcessBtn" maxHeight="1.7976931348623157E308" mnemonicParsing="false" onAction="#stopProcess" text="%stopProcess">
<HBox.margin>
<Insets bottom="5.0" right="10.0" top="5.0" />
</HBox.margin>
</Button>
</children>
</HBox>
<GridPane id="protocolSteps" fx:id="uiProtocolStepsGrid" VBox.vgrow="NEVER">
<columnConstraints>
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="50.0" />
<ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="180.0" minWidth="10.0" />
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="0.0" />
<ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="180.0" minWidth="10.0" />
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="0.0" />
<ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="180.0" minWidth="10.0" />
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="0.0" />
<ColumnConstraints halignment="CENTER" hgrow="NEVER" maxWidth="180.0" minWidth="10.0" />
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="50.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER" />
</rowConstraints>
<padding>
<Insets bottom="2.0" left="30.0" right="30.0" top="2.0" />
</padding>
<children>
<Button fx:id="uiStep1Btn" mnemonicParsing="false" styleClass="no-border" text="%step1" GridPane.columnIndex="1">
<graphic>
<Label text="1" />
</graphic>
</Button>
<Button fx:id="uiStep2Btn" mnemonicParsing="false" styleClass="no-border" text="%step2" GridPane.columnIndex="3">
<graphic>
<Label text="2" />
</graphic>
</Button>
<Button fx:id="uiStep3Btn" mnemonicParsing="false" styleClass="no-border" text="%step3" GridPane.columnIndex="5">
<graphic>
<Label text="3" />
</graphic>
</Button>
<Button fx:id="uiStep4Btn" mnemonicParsing="false" styleClass="no-border" text="%step4" GridPane.columnIndex="7">
<graphic>
<Label text="4" />
</graphic>
</Button>
</children>
</GridPane>
</children>
</VBox>
<BorderPane fx:id="uiBody" minHeight="30.0" minWidth="30.0" />
</children>
</fx:root>
@@ -0,0 +1,7 @@
site=Site :
refUsed=R\u00e9f\u00e9rentiels utilis\u00e9s
stopProcess=Arr\u00eater le processus
step1=Lot de donn\u00e9es
step2=Indicateurs
step3=R\u00e9sultats
step4=Finalisation
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.paint.*?>
<?import java.lang.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.image.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" styleClass="background" stylesheets="@../splashscreen.css" xmlns="http://javafx.com/javafx/8.0.66" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fr.cenra.rhomeo.fx.FXSplashScreen">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" maxWidth="358.0" minWidth="10.0" prefWidth="132.0" />
<ColumnConstraints hgrow="NEVER" maxWidth="378.0" minWidth="10.0" prefWidth="378.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="145.0" minHeight="121.0" prefHeight="123.0" vgrow="ALWAYS" />
<RowConstraints maxHeight="121.0" minHeight="100.0" prefHeight="119.0" vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
</rowConstraints>
<children>
<GridPane fx:id="uiLoadingPane" hgap="10.0" vgap="9.0" GridPane.columnSpan="2" GridPane.rowIndex="1" GridPane.valignment="BOTTOM">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" />
<ColumnConstraints hgrow="SOMETIMES" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER" />
<RowConstraints vgrow="NEVER" />
<RowConstraints vgrow="NEVER" />
</rowConstraints>
<children>
<ProgressBar fx:id="uiProgressBar" maxWidth="1.7976931348623157E308" progress="0.0" GridPane.columnSpan="2147483647" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.valignment="CENTER" />
<Label fx:id="uiProgressLabel" maxWidth="1.7976931348623157E308" styleClass="label-advanced" text="Label" textFill="#b57171" GridPane.columnSpan="2" GridPane.rowIndex="1">
<GridPane.margin>
<Insets />
</GridPane.margin>
<font>
<Font name="Candara" size="12.0" />
</font>
</Label>
<Button fx:id="uiCancel" mnemonicParsing="false" onAction="#closeApp" text="Annuler" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="2" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets />
</GridPane.margin>
<font>
<Font name="Candara" size="13.0" />
</font>
</Button>
</children>
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</GridPane.margin>
</GridPane>
</children>
</GridPane>
@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<fx:root spacing="5.0" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Jeu de caractères :">
<font>
<Font name="System Bold Italic" size="13.0" />
</font>
</Label>
<ComboBox fx:id="uiEncoding" />
<Separator prefWidth="200.0">
<VBox.margin>
<Insets bottom="5.0" top="5.0" />
</VBox.margin>
</Separator>
<Label text="Séparateur de colonne :">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
<font>
<Font name="System Bold Italic" size="13.0" />
</font>
</Label>
<HBox alignment="CENTER_LEFT" spacing="10.0">
<children>
<TextField fx:id="uiText" maxWidth="30.0" />
<Label text="ou" />
<CheckBox fx:id="uiTab" mnemonicParsing="false" text="Tabulation" />
</children>
</HBox>
<Separator prefWidth="200.0">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
</Separator>
<Label fx:id="uiInfo" wrapText="true">
<font>
<Font name="System Italic" size="13.0" />
</font>
</Label>
<HBox alignment="CENTER_LEFT" spacing="5.0">
<VBox.margin>
<Insets top="5.0" />
</VBox.margin>
<children>
<Separator visible="false" HBox.hgrow="ALWAYS" />
<Button fx:id="uiCancel" mnemonicParsing="false" text="Annuler" visible="false" HBox.hgrow="NEVER" />
<Button fx:id="uiApply" mnemonicParsing="false" text="OK" visible="false" HBox.hgrow="NEVER" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</fx:root>
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Spinner?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root minHeight="30.0" minWidth="30.0" spacing="5.0" stylesheets="@FXDatasetPane.css" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GridPane hgap="15.0" maxWidth="1.7976931348623157E308" vgap="5.0" VBox.vgrow="NEVER">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" />
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%point.name" />
<Label text="%point.year" GridPane.rowIndex="1" />
<Label text="%point.remarks" GridPane.columnIndex="2" />
<TextField fx:id="uiName" GridPane.columnIndex="1" />
<TextArea fx:id="uiRemarks" minHeight="30.0" minWidth="30.0" GridPane.columnIndex="3" GridPane.rowSpan="2147483647" />
<Spinner fx:id="uiYear" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
</GridPane>
<Separator prefWidth="200.0">
<padding>
<Insets bottom="5.0" top="5.0" />
</padding></Separator>
<BorderPane fx:id="uiCenterPane" maxHeight="1.7976931348623157E308" minHeight="30.0" minWidth="30.0">
<bottom>
<HBox spacing="10.0" BorderPane.alignment="BOTTOM_LEFT">
<children>
<Button fx:id="uiAdd" disable="true" mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="%statement.add" />
<Button fx:id="uiDelete" disable="true" mnemonicParsing="false" onAction="#deleteStatements" styleClass="transparent-button" text="%statement.delete" />
</children>
<padding>
<Insets top="5.0" />
</padding>
</HBox>
</bottom>
<top>
<Label text="%statement.list.title" BorderPane.alignment="BOTTOM_LEFT">
<BorderPane.margin>
<Insets />
</BorderPane.margin>
<padding>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
</padding></Label>
</top>
</BorderPane>
<Separator prefWidth="200.0">
<padding>
<Insets bottom="5.0" top="5.0" />
</padding></Separator>
<HBox alignment="CENTER_RIGHT" VBox.vgrow="NEVER">
<children>
<Button fx:id="uiSave" alignment="CENTER" disable="true" mnemonicParsing="false" onAction="#savePoint" styleClass="contrast" text="%point.save" />
</children>
</HBox>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</fx:root>
@@ -0,0 +1,9 @@
point.name=Nom
point.remarks=Remarques
point.year=Ann\u00e9e
title=Descriptif du point de suivi
statement.list.title=Relev\u00e9s
statement.add=Ajouter un relev\u00e9
point.save=Enregistrer le point de suivi
statement.delete=Supprimer la s\u00e9lection
@@ -0,0 +1,55 @@
/*
Created on : 19-May-2016, 14:13:28
Author : Alexis Manin (Geomatys)
*/
.title-text {
-fx-font-size:14pt;
}
.transparent-button {
-fx-padding: 4 13 4 13;
-fx-border-width:1;
-fx-border-color:lightgrey;
-fx-border-radius:1;
-fx-background-radius:1;
-fx-background-color:transparent;
-fx-min-height: 34px;
-fx-max-height: 34px;
}
.transparent-button:hover {
-fx-blend-mode:color-burn;
}
.transparent-button:armed {
-fx-border-color:lightskyblue;
}
.point-right {
-fx-background-radius:1, 12, 12, 1;
-fx-border-radius:1, 7, 7, 1;
}
.white-box {
-fx-background-color:-white-translucent;
-fx-border-color:lightgrey;
-fx-border-width:1;
-fx-padding:10;
}
/***************************
* TITLED PANELS
***************************/
.titled-pane .title {
-fx-background-color:transparent;
}
.titled-pane > .title > .text {
-fx-font-weight: bold;
}
.titled-pane, .titled-pane .content {
-fx-border-width:0;
}
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root alignment="CENTER" maxWidth="1.7976931348623157E308" minHeight="30.0" minWidth="30.0" spacing="5.0" stylesheets="@FXDatasetPane.css" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<children>
<GridPane hgap="10.0" maxWidth="1.7976931348623157E308" vgap="5.0">
<columnConstraints>
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" maxWidth="1.7976931348623157E308" minWidth="10.0" />
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints hgrow="NEVER" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER" />
<RowConstraints vgrow="NEVER" />
</rowConstraints>
<children>
<Button fx:id="uiDeleteDataset" disable="true" maxWidth="1.7976931348623157E308" minWidth="50.0" mnemonicParsing="false" onAction="#deleteDataset" styleClass="transparent-button" text="%dataset.delete" GridPane.columnIndex="2" />
<Button fx:id="uiImportDataset" maxWidth="1.7976931348623157E308" minWidth="50.0" mnemonicParsing="false" onAction="#importDataset" styleClass="contrast" text="%dataset.import" GridPane.columnIndex="2" GridPane.rowIndex="1" />
<Button fx:id="uiDisplayDocumentation" mnemonicParsing="false" onAction="#displayDocumentation" styleClass="transparent-button" text="%protocol.doc" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label styleClass="title-text" text="%title" GridPane.columnSpan="2" />
<Label styleClass="text-bold" text="%point.list" GridPane.rowIndex="1" />
</children>
<padding>
<Insets left="5.0" right="5.0" />
</padding>
</GridPane>
<HBox spacing="10.0">
<children>
<BorderPane fx:id="uiPointListContainer" maxHeight="250.0" minHeight="90.0" HBox.hgrow="ALWAYS" />
<VBox alignment="CENTER" spacing="5.0" HBox.hgrow="NEVER">
<children>
<Button fx:id="uiAddPoint" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#addPoint" styleClass="contrast" text="%point.add" />
<Button fx:id="uiDeletePoints" disable="true" maxWidth="1.7976931348623157E308" mnemonicParsing="false" onAction="#deletePoints" styleClass="transparent-button" text="%point.delete" />
</children>
</VBox>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</HBox>
<TitledPane text="%point.title" VBox.vgrow="SOMETIMES">
<content>
<BorderPane fx:id="uiPointEditorContainer" minHeight="100.0" styleClass="white-box">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</BorderPane>
</content>
</TitledPane>
<TitledPane text="%dataset.validity" VBox.vgrow="SOMETIMES">
<content>
<BorderPane fx:id="uiValidationContainer" styleClass="white-box">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</BorderPane>
</content>
</TitledPane>
<HBox alignment="CENTER_RIGHT" spacing="10.0" VBox.vgrow="NEVER">
<children>
<Button fx:id="uiExportDataset" disable="true" mnemonicParsing="false" onAction="#exportDataset" styleClass="transparent-button" text="%dataset.export" />
<Button fx:id="uiProceed" disable="true" mnemonicParsing="false" onAction="#nextStep" styleClass="contrast" text="%proceed" />
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</HBox>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</fx:root>
@@ -0,0 +1,23 @@
title=Cr\u00e9ation du lot de donn\u00e9es
point.list=Liste des points de suivi
protocol.doc=Format du protocole
dataset.delete=Supprimer le lot de donn\u00e9es
dataset.import=Importer un lot de donn\u00e9es
point.add=Ajouter un point de suivi
point.delete=Supprimer la s\u00e9lection
dataset.export=Exporter le lot de donn\u00e9es
proceed=Param\u00e9trer le calcul
save.before.import=Voulez-vous sauvegarder le lot de donn\u00e9ees actuel avant d'ajouter de nouvelles donn\u00e9es ?
dataset.import.choice=Fichier \u00e0 importer
dataset.export.task=Export d'un lot de donn\u00e9es
dataset.export.error=Une erreur a empech\u00e9 l'export de se terminer correctement.
dataset.import.task=Import d'un fichier CSV
dataset.import.result=%d entr\u00e9es ont \u00e9t\u00e9 int\u00e9gr\u00e9es, et %d ont \u00e9t\u00e9 ignor\u00e9es car elles ont \u00e9t\u00e9 consid\u00e9r\u00e9es comme doublon.
dataset.import.error=Une erreur est survenue lors de l'import. Un partie des donn\u00e9es pourrait ne pas avoir \u00e9t\u00e9 int\u00e9gr\u00e9e
dataset.delete.confirm=Vous allez supprimer l'ensemble des donn\u00e9es pr\u00e9sentes (tous points de suivi confondus). Voulez-vous continuer ?
point.title=Descriptif du point de suivi
dataset.validity=Conformit\u00e9 des donn\u00e9es
point.delete.confirm=Voulez-vous vraiment supprimer les points de suivis s\u00e9l\u00e9ctionn\u00e9s ?
save.before.next=Voulez-vous sauvegarder le lot de donn\u00e9ees actuel avant de passer \u00e0 l'\u00e9tape suivante ?
dataset.import.result.no-doublon=%d entr\u00e9es ont \u00e9t\u00e9 int\u00e9gr\u00e9es.
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<fx:root styleClass="transparent" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<SplitPane dividerPositions="0.5" styleClass="transparent" BorderPane.alignment="CENTER">
<items>
<GridPane hgap="5.0" vgap="5.0">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%point.name" />
<Label text="%statement.date" GridPane.rowIndex="1" />
<Label text="%statement.physionomy" GridPane.rowIndex="2" />
<Label text="%statement.remarks" GridPane.rowIndex="3" />
<TextField fx:id="uiName" minWidth="50.0" GridPane.columnIndex="1" />
<DatePicker fx:id="uiDate" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<ComboBox fx:id="uiPhysio" maxWidth="1.7976931348623157E308" minWidth="50.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextArea fx:id="uiRemarks" maxWidth="1.7976931348623157E308" minHeight="50.0" minWidth="50.0" prefWidth="250.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</GridPane>
<BorderPane fx:id="uiObsContainer">
<top>
<Label text="%statement.list" BorderPane.alignment="TOP_LEFT" />
</top>
<bottom>
<HBox spacing="5.0" BorderPane.alignment="TOP_LEFT">
<children>
<Button mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="%statement.add" />
<Button fx:id="uiDelete" mnemonicParsing="false" onAction="#deleteStatements" styleClass="contrast" text="%statement.delete" />
</children>
</HBox>
</bottom>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</BorderPane>
</items>
</SplitPane>
</center>
</fx:root>
@@ -0,0 +1,7 @@
point.name=Nom
statement.date=Date
statement.physionomy=Physionomie
statement.remarks=Remarques
statement.list=Liste des observations
statement.add=Ajouter une observation
statement.delete=Supprimer la s\u00e9lection
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<fx:root styleClass="transparent" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<SplitPane dividerPositions="0.5" styleClass="transparent" BorderPane.alignment="CENTER">
<items>
<GridPane hgap="5.0" vgap="5.0">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%point.name" />
<Label text="%statement.date" GridPane.rowIndex="1" />
<Label text="%statement.remarks" GridPane.rowIndex="2" />
<TextField fx:id="uiName" minWidth="50.0" GridPane.columnIndex="1" />
<DatePicker fx:id="uiDate" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<TextArea fx:id="uiRemarks" maxWidth="1.7976931348623157E308" minHeight="50.0" minWidth="50.0" prefWidth="250.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</GridPane>
<BorderPane fx:id="uiObsContainer">
<top>
<Label text="%statement.list" BorderPane.alignment="TOP_LEFT" />
</top>
<bottom>
<HBox spacing="5.0" BorderPane.alignment="TOP_LEFT">
<children>
<Button mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="%statement.add" />
<Button fx:id="uiDelete" mnemonicParsing="false" onAction="#deleteStatements" styleClass="contrast" text="%statement.delete" />
</children>
</HBox>
</bottom>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</BorderPane>
</items>
</SplitPane>
</center>
</fx:root>
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<fx:root styleClass="transparent" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<SplitPane dividerPositions="0.5" styleClass="transparent" BorderPane.alignment="CENTER">
<items>
<GridPane hgap="5.0" vgap="5.0">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%point.name" />
<Label text="%statement.date" GridPane.rowIndex="1" />
<Label text="%statement.remarks" GridPane.rowIndex="2" />
<TextField fx:id="uiName" minWidth="50.0" GridPane.columnIndex="1" />
<DatePicker fx:id="uiDate" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<TextArea fx:id="uiRemarks" maxWidth="1.7976931348623157E308" minHeight="50.0" minWidth="50.0" prefWidth="250.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</GridPane>
<BorderPane fx:id="uiObsContainer">
<top>
<Label text="%statement.list" BorderPane.alignment="TOP_LEFT" />
</top>
<bottom>
<HBox spacing="5.0" BorderPane.alignment="TOP_LEFT">
<children>
<Button mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="%statement.add" />
<Button fx:id="uiDelete" mnemonicParsing="false" onAction="#deleteStatements" styleClass="contrast" text="%statement.delete" />
</children>
</HBox>
</bottom>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</BorderPane>
</items>
</SplitPane>
</center>
</fx:root>
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<VBox minHeight="30.0" minWidth="30.0" spacing="5.0" stylesheets="@FXDatasetPane.css">
<children>
<GridPane hgap="15.0" maxWidth="1.7976931348623157E308" vgap="5.0" VBox.vgrow="NEVER">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" />
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%point.name" />
<Label text="%point.date" GridPane.rowIndex="1" />
<Label text="%point.remarks" GridPane.columnIndex="2" />
<TextField fx:id="uiName" GridPane.columnIndex="1" />
<TextArea fx:id="uiRemarks" minHeight="30.0" minWidth="30.0" GridPane.columnIndex="3" GridPane.rowSpan="2147483647" />
<DatePicker fx:id="uiDate" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
</GridPane>
<Separator prefWidth="200.0">
<padding>
<Insets bottom="5.0" top="5.0" />
</padding>
</Separator>
<BorderPane fx:id="uiObsContainer" minHeight="30.0" minWidth="30.0" VBox.vgrow="SOMETIMES">
<top>
<HBox spacing="10.0" BorderPane.alignment="CENTER">
<children>
<Label styleClass="text-bold" text="%statement.list.title">
<padding>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
<Pane maxWidth="1.7976931348623157E308" minHeight="0.0" minWidth="0.0" prefHeight="0.0" HBox.hgrow="ALWAYS" />
<Button fx:id="uiAdd" mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="%statement.add" />
<Button fx:id="uiDelete" disable="true" mnemonicParsing="false" onAction="#deleteStatements" styleClass="transparent-button" text="%statement.delete" />
</children>
<padding>
<Insets top="5.0" />
</padding>
</HBox>
</top>
</BorderPane>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</VBox>
</center>
</fx:root>
@@ -0,0 +1,9 @@
point.name=Nom
point.remarks=Remarques
point.date=Date
statement.list.title=Horizons
statement.add=Ajouter un horizon
point.save=Enregistrer le point de suivi
statement.delete=Supprimer la s\u00e9lection
statement.delete.confirm=\u00cates-vous s\u00fbr de vouloir supprimer les horizons s\u00e9l\u00e9ctionn\u00e9s ?
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<fx:root type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<top>
<Label styleClass="text-bold" text="Liste des valeurs physiochimiques" BorderPane.alignment="BOTTOM_LEFT">
<padding>
<Insets bottom="10.0" />
</padding></Label>
</top>
<bottom>
<HBox alignment="CENTER_RIGHT" spacing="5.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="uiFinalize" mnemonicParsing="false" onAction="#validate" styleClass="contrast" text="Finaliser" />
</children>
<padding>
<Insets top="20.0" />
</padding>
</HBox>
</bottom>
<center>
<VBox spacing="10.0" BorderPane.alignment="CENTER">
<children>
<BorderPane fx:id="uiTableContainer" />
<HBox spacing="5.0">
<children>
<Button mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="Ajouter une entrée" />
<Button fx:id="uiDelete" mnemonicParsing="false" onAction="#deleteSelection" styleClass="contrast" text="Supprimer la sélection" />
</children>
</HBox>
</children>
</VBox>
</center>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</fx:root>
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<fx:root styleClass="transparent" type="BorderPane" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<center>
<SplitPane dividerPositions="0.5" styleClass="transparent" BorderPane.alignment="CENTER">
<items>
<GridPane hgap="5.0" vgap="5.0">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints minHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="%point.name" />
<Label text="%statement.date" GridPane.rowIndex="1" />
<Label text="%statement.habitat" GridPane.rowIndex="2" />
<Label text="%statement.remarks" GridPane.rowIndex="3" />
<TextField fx:id="uiName" minWidth="50.0" GridPane.columnIndex="1" />
<DatePicker fx:id="uiDate" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<ComboBox fx:id="uiHabitat" maxWidth="1.7976931348623157E308" minWidth="50.0" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<TextArea fx:id="uiRemarks" maxWidth="1.7976931348623157E308" minHeight="50.0" minWidth="50.0" prefWidth="250.0" GridPane.columnIndex="1" GridPane.rowIndex="3" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</GridPane>
<BorderPane fx:id="uiObsContainer">
<top>
<Label text="%statement.list" BorderPane.alignment="TOP_LEFT" />
</top>
<bottom>
<HBox spacing="5.0" BorderPane.alignment="TOP_LEFT">
<children>
<Button mnemonicParsing="false" onAction="#addStatement" styleClass="contrast" text="%statement.add" />
<Button fx:id="uiDelete" mnemonicParsing="false" onAction="#deleteStatements" styleClass="contrast" text="%statement.delete" />
</children>
</HBox>
</bottom>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</BorderPane>
</items>
</SplitPane>
</center>
</fx:root>
@@ -0,0 +1,7 @@
point.name=Nom
statement.date=Date
statement.habitat=Habitat
statement.remarks=Remarques
statement.list=Liste des observations
statement.add=Ajouter une observation
statement.delete=Supprimer la s\u00e9lection
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<fx:root maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="30.0" minWidth="30.0" spacing="10.0" type="VBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="20.0" />
</padding>
<children>
<Label styleClass="text-bold" text="Téléchargement des référentiels : tâche &quot;artificialisée&quot; et tâche &quot;bâti urbain&quot; - Pression de l'artificialisation">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding></Label>
<BorderPane fx:id="tableSpace" prefHeight="200.0" prefWidth="200.0" />
<HBox alignment="CENTER_RIGHT">
<children>
<Button mnemonicParsing="false" onAction="#showMap" text="carte" visible="false" />
<Button fx:id="next" mnemonicParsing="false" onAction="#nextStep" styleClass="contrast" text="Paramétrer le calcul" />
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</HBox>
</children>
</fx:root>

Some files were not shown because too many files have changed in this diff Show More