03/19: ajout de l'onglet d'édition des zone (refs #7929)
This is an automated email from the git hooks/post-receive script. New commit to branch feature/7929_editeur_de_zone in repository tutti. See http://git.codelutin.com/tutti.git commit c175e1d3a00958464221bc56bea4fafb9db07348 Author: Kevin Morin <morin@codelutin.com> Date: Mon Jan 25 11:23:25 2016 +0100 ajout de l'onglet d'édition des zone (refs #7929) --- .../ui/swing/content/protocol/EditProtocolUI.jaxx | 5 + .../content/protocol/EditProtocolUIModel.java | 56 ++++++++ .../swing/content/protocol/zones/ZoneEditorUI.jaxx | 50 +++++++ .../swing/content/protocol/zones/ZoneEditorUI.jcss | 17 +++ .../protocol/zones/ZoneEditorUIHandler.java | 155 +++++++++++++++++++++ .../content/protocol/zones/ZoneEditorUIModel.java | 129 +++++++++++++++++ .../protocol/zones/actions/AddStratasAction.java | 48 +++++++ .../zones/actions/RemoveStratasAction.java | 22 +++ .../resources/i18n/tutti-ui-swing_en_GB.properties | 3 + .../resources/i18n/tutti-ui-swing_fr_FR.properties | 3 + 10 files changed, 488 insertions(+) diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUI.jaxx b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUI.jaxx index 5dd2e0d..a8687d4 100644 --- a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUI.jaxx +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUI.jaxx @@ -28,6 +28,7 @@ fr.ifremer.tutti.ui.swing.TuttiHelpBroker fr.ifremer.tutti.ui.swing.content.protocol.rtp.RtpEditorUI + fr.ifremer.tutti.ui.swing.content.protocol.zones.ZoneEditorUI fr.ifremer.tutti.ui.swing.util.TuttiUI fr.ifremer.tutti.ui.swing.util.TuttiUIUtil @@ -237,6 +238,10 @@ </Table> </JPanel> </tab> + <tab title='tutti.editProtocol.tab.zone'> + <ZoneEditorUI id="zoneEditor" + constructorParams='this'/> + </tab> </JTabbedPane> <JPanel id='saveWarningContainer' layout='{new BorderLayout(10, 10)}' diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUIModel.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUIModel.java index 84b1f81..73391ad 100644 --- a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUIModel.java +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/EditProtocolUIModel.java @@ -33,6 +33,7 @@ import fr.ifremer.tutti.persistence.entities.protocol.OperationFieldMappingRow; import fr.ifremer.tutti.persistence.entities.protocol.SpeciesProtocol; import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocol; import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocols; +import fr.ifremer.tutti.persistence.entities.protocol.Zone; import fr.ifremer.tutti.persistence.entities.referential.Caracteristic; import fr.ifremer.tutti.persistence.entities.referential.Species; import fr.ifremer.tutti.ui.swing.util.AbstractTuttiBeanUIModel; @@ -779,6 +780,61 @@ public class EditProtocolUIModel extends AbstractTuttiBeanUIModel<TuttiProtocol, } @Override + public Collection<Zone> getZones() { + return editObject.getZones(); + } + + @Override + public void setZones(Collection<Zone> zones) { + editObject.setZones(zones); + } + + @Override + public Zone getZones(int index) { + return editObject.getZones(index); + } + + @Override + public boolean isZonesEmpty() { + return editObject.isZonesEmpty(); + } + + @Override + public int sizeZones() { + return editObject.sizeZones(); + } + + @Override + public void addZones(Zone zone) { + editObject.addZones(zone); + } + + @Override + public void addAllZones(Collection<Zone> zones) { + editObject.addAllZones(zones); + } + + @Override + public boolean removeZones(Zone zone) { + return editObject.removeZones(zone); + } + + @Override + public boolean removeAllZones(Collection<Zone> zones) { + return editObject.removeAllZones(zones); + } + + @Override + public boolean containsZones(Zone zone) { + return editObject.containsZones(zone); + } + + @Override + public boolean containsAllZones(Collection<Zone> zones) { + return editObject.containsAllZones(zones); + } + + @Override public Collection<OperationFieldMappingRow> getOperationFieldMapping() { return editObject.getOperationFieldMapping(); } diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUI.jaxx b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUI.jaxx new file mode 100644 index 0000000..ae21d09 --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUI.jaxx @@ -0,0 +1,50 @@ + +<Table id='editProtocolTopPanel' + implements='fr.ifremer.tutti.ui.swing.util.TuttiUI<ZoneEditorUIModel, ZoneEditorUIHandler>'> + + <import> + fr.ifremer.tutti.ui.swing.util.TuttiUI + fr.ifremer.tutti.ui.swing.util.TuttiUIUtil + </import> + + <script><![CDATA[ + + public ZoneEditorUI(TuttiUI parentUI) { + TuttiUIUtil.setParentUI(this, parentUI); + } + ]]> + </script> + + <!-- model --> + <ZoneEditorUIModel id='model' javaBean='new ZoneEditorUIModel()'/> + + <row> + <cell weightx='0.5' weighty='1' fill='both'> + <JScrollPane onFocusGained='zoneTree.requestFocus()'> + <!-- List of the zones --> + <JTree id='zoneTree'/> + <!--onFocusGained='handler.selectFirstRowIfNoSelection(event)'--> + <!--onMouseClicked='handler.onUniverseListClicked(event)'--> + <!--onKeyPressed='handler.onKeyPressedOnUniverseList(event)'--> + </JScrollPane> + </cell> + + <cell anchor='north'> + <JPanel layout='{new GridLayout(0,1)}'> + <JButton id='addButton' /> + <JButton id='removeButton' /> + </JPanel> + </cell> + + <cell weightx='0.5' weighty='1' fill='both'> + <JScrollPane onFocusGained='availableStratas.requestFocus()'> + <!-- List of the available stratas and substratas --> + <JTree id='availableStratas' onValueChanged="availableStratas.expandPath(event.getNewLeadSelectionPath())"/> + <!--onFocusGained='handler.selectFirstRowIfNoSelection(event)'--> + <!--onMouseClicked='handler.onSelectedListClicked(event)'--> + <!--onKeyPressed='handler.onKeyPressedOnSelectedList(event)'/>--> + </JScrollPane> + </cell> + </row> + +</Table> \ No newline at end of file diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUI.jcss b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUI.jcss new file mode 100644 index 0000000..f6b30e7 --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUI.jcss @@ -0,0 +1,17 @@ +JTree { + autoscrolls: true; + expandsSelectedPaths: true; + scrollsOnExpand: true; + rootVisible: false; + showsRootHandles: false; +} + +#addButton { + text: "<<"; + _simpleAction: {fr.ifremer.tutti.ui.swing.content.protocol.zones.actions.AddStratasAction.class}; +} + +#removeButton { + text: ">>"; + _simpleAction: {fr.ifremer.tutti.ui.swing.content.protocol.zones.actions.RemoveStratasAction.class}; +} \ No newline at end of file diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUIHandler.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUIHandler.java new file mode 100644 index 0000000..f46342a --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUIHandler.java @@ -0,0 +1,155 @@ +package fr.ifremer.tutti.ui.swing.content.protocol.zones; + +import com.google.common.collect.Multimap; +import fr.ifremer.tutti.persistence.entities.referential.TuttiLocation; +import fr.ifremer.tutti.service.PersistenceService; +import fr.ifremer.tutti.ui.swing.util.AbstractTuttiUIHandler; +import jaxx.runtime.validator.swing.SwingValidator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.swing.JComponent; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeCellRenderer; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; +import java.awt.Component; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; + +/** + * @author Kevin Morin (Code Lutin) + * @since 4.5 + */ +public class ZoneEditorUIHandler extends AbstractTuttiUIHandler<ZoneEditorUIModel, ZoneEditorUI> { + + /** Logger. */ + private static final Log log = LogFactory.getLog(ZoneEditorUIHandler.class); + + @Override + public void afterInit(ZoneEditorUI zoneEditorUI) { + + initUI(zoneEditorUI); + + getModel().addPropertyChangeListener(ZoneEditorUIModel.PROPERTY_AVAILABLE_STRATAS, + evt -> { + + Multimap<TuttiLocation, TuttiLocation> oldAvailableStratas = + (Multimap<TuttiLocation, TuttiLocation>) evt.getOldValue(); + + Multimap<TuttiLocation, TuttiLocation> newAvailableStratas = + (Multimap<TuttiLocation, TuttiLocation>) evt.getNewValue(); + + Collection<TuttiLocation> stratasToAdd = new ArrayList<>(newAvailableStratas.keySet()); + stratasToAdd.removeAll(oldAvailableStratas.keySet()); + + Collection<TuttiLocation> stratasToRemove = new ArrayList<>(oldAvailableStratas.keySet()); + stratasToRemove.removeAll(newAvailableStratas.keySet()); + + updateTreeModel(newAvailableStratas, oldAvailableStratas, stratasToAdd, stratasToRemove); + }); + + DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + + TreeModel availableStratasTreeModel = new DefaultTreeModel(root); + + JTree availableStratasTree = ui.getAvailableStratas(); + availableStratasTree.setCellRenderer(new DefaultTreeCellRenderer() { + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { + super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + String text = "test"; + if (value instanceof DefaultMutableTreeNode) { + Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); + if (userObject instanceof TuttiLocation) { + text = ((TuttiLocation) userObject).getLabel(); + } + } + setText(text); + return this; + } + }); + availableStratasTree.setModel(availableStratasTreeModel); + + PersistenceService persistenceService = getPersistenceService(); + + TuttiLocation programZone = getDataContext().getProgram().getZone(); + + Multimap<TuttiLocation, TuttiLocation> programStratasAndSubstratas = + persistenceService.getAllFishingOperationStratasAndSubstratas(programZone.getId()); + + getModel().setAvailableStratas(programStratasAndSubstratas); + } + + protected void updateTreeModel(Multimap<TuttiLocation, TuttiLocation> newAvailableStratas, + Multimap<TuttiLocation, TuttiLocation> oldAvailableStratas, + Collection<TuttiLocation> stratasToAdd, + Collection<TuttiLocation> stratasToRemove) { + + if (log.isInfoEnabled()) { + log.info("updateTrremodle"); + } + + JTree availableStratas = getUI().getAvailableStratas(); + DefaultTreeModel availableStratasTreeModel = (DefaultTreeModel) availableStratas.getModel(); + DefaultMutableTreeNode root = (DefaultMutableTreeNode) availableStratasTreeModel.getRoot(); + + Enumeration rootChildren = root.children(); + Collection<DefaultMutableTreeNode> nodesToRemove = new HashSet<>(); + while (rootChildren.hasMoreElements()) { + DefaultMutableTreeNode strataNode = (DefaultMutableTreeNode) rootChildren.nextElement(); + if (log.isInfoEnabled()) { + log.info("root child " + ((TuttiLocation) strataNode.getUserObject()).getLabel()); + } + if (stratasToRemove.contains(strataNode.getUserObject())) { + if (log.isInfoEnabled()) { + log.info("remove " + ((TuttiLocation) strataNode.getUserObject()).getLabel()); + } + nodesToRemove.add(strataNode); + } + } + nodesToRemove.forEach(strataNode -> root.remove(strataNode)); + + stratasToAdd.forEach(strata -> { + + DefaultMutableTreeNode strataNode = new DefaultMutableTreeNode(strata, true); + root.add(strataNode); + + if (log.isInfoEnabled()) { + log.info("add strata node " + strata.getLabel()); + } + + newAvailableStratas.get(strata).forEach(substrata -> { + + if (substrata != null) { + DefaultMutableTreeNode subStrataNode = new DefaultMutableTreeNode(substrata, false); + strataNode.add(subStrataNode); + log.info("add substrata node " + substrata.getLabel()); + } + + }); + + }); + + availableStratasTreeModel.reload(); + } + + @Override + public SwingValidator<ZoneEditorUIModel> getValidator() { + return null; + } + + @Override + protected JComponent getComponentToFocus() { + return ui.getZoneTree(); + } + + @Override + public void onCloseUI() { + + } +} diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUIModel.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUIModel.java new file mode 100644 index 0000000..9645011 --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/ZoneEditorUIModel.java @@ -0,0 +1,129 @@ +package fr.ifremer.tutti.ui.swing.content.protocol.zones; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import fr.ifremer.tutti.persistence.entities.protocol.Zone; +import fr.ifremer.tutti.persistence.entities.referential.TuttiLocation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jdesktop.beans.AbstractSerializableBean; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Kevin Morin (Code Lutin) + * @since 4.5 + */ +public class ZoneEditorUIModel extends AbstractSerializableBean { + + /** Logger. */ + private static final Log log = LogFactory.getLog(ZoneEditorUIModel.class); + + public static final String PROPERTY_AVAILABLE_STRATAS = "availableStratas"; + + public static final String PROPERTY_ZONES = "zones"; + + protected Map<TuttiLocation, TuttiLocation> strataBySubstrata; + + protected final Multimap<TuttiLocation, TuttiLocation> availableStratas = HashMultimap.create(); + + protected final List<Zone> zones = new ArrayList<>(); + + public Multimap<TuttiLocation, TuttiLocation> getAvailableStratas() { + return availableStratas; + } + + public void setAvailableStratas(Multimap<TuttiLocation, TuttiLocation> availableStratas) { + this.availableStratas.clear(); + addAllAvailableStratas(availableStratas); + + strataBySubstrata = new HashMap<>(); + for (TuttiLocation stratas : availableStratas.keySet()) { + for (TuttiLocation substratas : availableStratas.get(stratas)) { + strataBySubstrata.put(substratas, stratas); + } + } + } + + public void addAllAvailableStratas(Multimap<TuttiLocation, TuttiLocation> availableStratas) { + Object oldValue = copyAvailableStratas(); + this.availableStratas.putAll(availableStratas); + firePropertyChange(PROPERTY_AVAILABLE_STRATAS, oldValue, availableStratas); + } + + public void addAvailableStratas(Collection<TuttiLocation> stratasAndSubStratas) { + + Object oldValue = copyAvailableStratas(); + + Collection<TuttiLocation> substratas = stratasAndSubStratas.stream() + .filter(location -> strataBySubstrata.containsKey(location)) + .collect(Collectors.toSet()); + + Collection<TuttiLocation> stratas = new HashSet<>(stratasAndSubStratas); + stratas.removeAll(substratas); + + stratas.forEach(strata -> { + + Collection<TuttiLocation> strataSubstratas = + strataBySubstrata.entrySet() + .stream() + .filter(entry->entry.getValue().equals(strata)) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + + substratas.removeAll(strataSubstratas); + availableStratas.putAll(strata, strataSubstratas); + + }); + + substratas.forEach(substrata -> { + availableStratas.put(strataBySubstrata.get(substrata), substrata); + }); + + firePropertyChange(PROPERTY_AVAILABLE_STRATAS, oldValue, availableStratas); + } + + public void removeAvailableStratas(Collection<TuttiLocation> stratasAndSubStratas) { + + Object oldValue = copyAvailableStratas(); + + Collection<TuttiLocation> substratas = stratasAndSubStratas.stream() + .filter(location -> strataBySubstrata.containsKey(location)) + .collect(Collectors.toSet()); + + Collection<TuttiLocation> stratas = new HashSet<>(stratasAndSubStratas); + stratas.removeAll(substratas); + + stratas.forEach(strata -> { + availableStratas.removeAll(strata); + }); + + substratas.forEach(substrata -> { + availableStratas.remove(strataBySubstrata.get(substrata), substrata); + }); + + firePropertyChange(PROPERTY_AVAILABLE_STRATAS, oldValue, availableStratas); + + } + + public List<Zone> getZones() { + return zones; + } + + public void setZones(List<Zone> zones) { + Object oldValue = getZones(); + this.zones.clear(); + this.zones.addAll(zones); + firePropertyChange(PROPERTY_ZONES, oldValue, zones); + } + + protected Multimap<TuttiLocation, TuttiLocation> copyAvailableStratas() { + return HashMultimap.create(availableStratas); + } +} diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/actions/AddStratasAction.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/actions/AddStratasAction.java new file mode 100644 index 0000000..75ddcb5 --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/actions/AddStratasAction.java @@ -0,0 +1,48 @@ +package fr.ifremer.tutti.ui.swing.content.protocol.zones.actions; + +import fr.ifremer.tutti.persistence.entities.referential.TuttiLocation; +import fr.ifremer.tutti.ui.swing.content.protocol.zones.ZoneEditorUI; +import fr.ifremer.tutti.ui.swing.util.actions.SimpleActionSupport; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; +import java.util.HashSet; +import java.util.Set; + +/** + * @author Kevin Morin (Code Lutin) + * @since 4.5 + */ +public class AddStratasAction extends SimpleActionSupport<ZoneEditorUI> { + + /** Logger. */ + private static final Log log = LogFactory.getLog(AddStratasAction.class); + + public AddStratasAction(ZoneEditorUI zoneEditorUI) { + super(zoneEditorUI); + } + + @Override + protected void onActionPerformed(ZoneEditorUI zoneEditorUI) { + + JTree availableStratasTree = zoneEditorUI.getAvailableStratas(); + TreePath[] selectionPaths = availableStratasTree.getSelectionPaths(); + + Set<TuttiLocation> locationsToAdd = new HashSet<>(); + + for (TreePath treePath : selectionPaths) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); + TuttiLocation location = (TuttiLocation) node.getUserObject(); + if (log.isInfoEnabled()) { + log.info("add location " + location.getLabel()); + } + locationsToAdd.add(location); + } + + zoneEditorUI.getModel().removeAvailableStratas(locationsToAdd); + + } +} diff --git a/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/actions/RemoveStratasAction.java b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/actions/RemoveStratasAction.java new file mode 100644 index 0000000..4c7390f --- /dev/null +++ b/tutti-ui-swing/src/main/java/fr/ifremer/tutti/ui/swing/content/protocol/zones/actions/RemoveStratasAction.java @@ -0,0 +1,22 @@ +package fr.ifremer.tutti.ui.swing.content.protocol.zones.actions; + +import fr.ifremer.tutti.ui.swing.content.protocol.zones.ZoneEditorUI; +import fr.ifremer.tutti.ui.swing.util.actions.SimpleActionSupport; + +import javax.swing.tree.DefaultTreeModel; + +/** + * @author Kevin Morin (Code Lutin) + * @since 4.5 + */ +public class RemoveStratasAction extends SimpleActionSupport<ZoneEditorUI> { + + public RemoveStratasAction(ZoneEditorUI zoneEditorUI) { + super(zoneEditorUI); + } + + @Override + protected void onActionPerformed(ZoneEditorUI zoneEditorUI) { + ((DefaultTreeModel) zoneEditorUI.getAvailableStratas().getModel()).reload(); + } +} diff --git a/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_en_GB.properties b/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_en_GB.properties index 336d396..73beba0 100644 --- a/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_en_GB.properties +++ b/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_en_GB.properties @@ -1,3 +1,5 @@ +<<= +>>= application.action.create.error= application.error.ui.business.warning= jaxx.application.action.create.error= @@ -1093,6 +1095,7 @@ tutti.editProtocol.tab.caracteristic.mapping= tutti.editProtocol.tab.caracteristic.vesselUseFeature= tutti.editProtocol.tab.info= tutti.editProtocol.tab.species= +tutti.editProtocol.tab.zone= tutti.editProtocol.table.header.calcifySample= tutti.editProtocol.table.header.calcifySample.tip= tutti.editProtocol.table.header.caracteristics.importFileColumn= diff --git a/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_fr_FR.properties b/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_fr_FR.properties index 8ce0e99..c6500ff 100644 --- a/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_fr_FR.properties +++ b/tutti-ui-swing/src/main/resources/i18n/tutti-ui-swing_fr_FR.properties @@ -1,3 +1,5 @@ +<<= +>>= application.action.create.error= application.error.ui.business.warning= jaxx.application.action.create.error= @@ -1049,6 +1051,7 @@ tutti.editProtocol.tab.caracteristic.lengthClasses=Classes de taille tutti.editProtocol.tab.caracteristic.mapping=Caractéristiques du trait tutti.editProtocol.tab.info=Informations générales tutti.editProtocol.tab.species=Espèces +tutti.editProtocol.tab.zone=Zones tutti.editProtocol.table.header.calcifySample=Prélèvement de pièces calcifiées tutti.editProtocol.table.header.calcifySample.tip=Prélèvement de pièces calcifiées (pour les données individuelles) tutti.editProtocol.table.header.caracteristics.importFileColumn=Colonne du fichier d'import -- To stop receiving notification emails like this one, please contact codelutin.com SCM administrator <admin+scm@codelutin.com>.
participants (1)
-
codelutin.com scm