r940 - in trunk: coser-business/src/main/java/fr/ifremer/coser/bean coser-business/src/main/java/fr/ifremer/coser/command coser-business/src/main/java/fr/ifremer/coser/services coser-business/src/main/java/fr/ifremer/coser/storage coser-business/src/main/resources/i18n coser-business/src/test/java/fr/ifremer/coser/services coser-ui/src/main/java/fr/ifremer/coser/ui coser-ui/src/main/java/fr/ifremer/coser/ui/control coser-ui/src/main/java/fr/ifremer/coser/ui/selection/replay coser-ui/src/main
Author: echatellier Date: 2012-01-04 11:14:42 +0100 (Wed, 04 Jan 2012) New Revision: 940 Url: http://forge.codelutin.com/repositories/revision/coser/940 Log: #303 : Gerer les actions undo/redo Added: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerRedoMenu.java trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerUndoMenu.java trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenu.java Removed: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenuItem.java Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/bean/AbstractDataContainer.java trunk/coser-business/src/main/java/fr/ifremer/coser/command/MergeSpeciesCommand.java trunk/coser-business/src/main/java/fr/ifremer/coser/command/ModifyFieldCommand.java trunk/coser-business/src/main/java/fr/ifremer/coser/services/CommandService.java trunk/coser-business/src/main/java/fr/ifremer/coser/services/ProjectService.java trunk/coser-business/src/main/java/fr/ifremer/coser/services/PublicationService.java trunk/coser-business/src/main/java/fr/ifremer/coser/storage/MemoryDataStorage.java trunk/coser-business/src/main/resources/i18n/coser-business_fr_FR.properties trunk/coser-business/src/test/java/fr/ifremer/coser/services/CommandServiceTest.java trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrame.jaxx trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrameHandler.java trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlDuplicatedLineTableModel.java trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlHandler.java trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/selection/replay/SelectionReplayHandler.java trunk/coser-ui/src/main/resources/i18n/coser-ui_en_GB.properties trunk/coser-ui/src/main/resources/i18n/coser-ui_fr_FR.properties Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/bean/AbstractDataContainer.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/bean/AbstractDataContainer.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/bean/AbstractDataContainer.java 2012-01-04 10:14:42 UTC (rev 940) @@ -44,6 +44,8 @@ /** serialVersionUID. */ private static final long serialVersionUID = 3963187480579783560L; + public static final String PROPERTY_HISTORY_COMMANDS= "historyCommands"; + protected DataStorage dataCatch; /** Deleted catch (can be null). */ @@ -65,7 +67,7 @@ protected DataStorage deletedDataLength; /** L'historique des commandes do/undo .*/ - protected List<Command> historyCommand; + protected List<Command> historyCommands; /** * Clear all data to force free memory. @@ -79,7 +81,7 @@ deletedDataHaul = null; dataLength = null; deletedDataLength = null; - historyCommand = null; + historyCommands = null; } public DataStorage getCatch() { @@ -146,14 +148,30 @@ this.deletedDataLength = deletedDataLength; } - public List<Command> getHistoryCommand() { - return historyCommand; + public List<Command> getHistoryCommands() { + return historyCommands; } - public void setHistoryCommand(List<Command> historyCommand) { - this.historyCommand = historyCommand; + public void setHistoryCommands(List<Command> historyCommand) { + this.historyCommands = historyCommand; + getPropertyChangeSupport().firePropertyChange("historyCommands", null, historyCommands); } + public void addHistoryCommand(Command historyCommand) { + historyCommands.add(historyCommand); + getPropertyChangeSupport().firePropertyChange("historyCommands", null, historyCommands); + } + + public void removeHistoryCommand(Command historyCommand) { + historyCommands.remove(historyCommand); + getPropertyChangeSupport().firePropertyChange("historyCommands", null, historyCommands); + } + + public void clearHistoryCommands() { + historyCommands.clear(); + getPropertyChangeSupport().firePropertyChange("historyCommands", null, historyCommands); + } + /** * Return {@code true} if data are loaded. * Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/command/MergeSpeciesCommand.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/command/MergeSpeciesCommand.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/command/MergeSpeciesCommand.java 2012-01-04 10:14:42 UTC (rev 940) @@ -5,7 +5,7 @@ * $Id$ * $HeadURL$ * %% - * Copyright (C) 2010 Codelutin, Chatellier Eric + * Copyright (C) 2010 - 2012 Codelutin, Chatellier Eric * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -94,8 +94,8 @@ */ @Override public void undoCommand(Project project, AbstractDataContainer container) throws CoserBusinessException { - throw new RuntimeException("Merge operation can't be undone"); - + throw new UnsupportedOperationException("Merge operation can't be undone"); + // can't undo merge } /** Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/command/ModifyFieldCommand.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/command/ModifyFieldCommand.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/command/ModifyFieldCommand.java 2012-01-04 10:14:42 UTC (rev 940) @@ -166,7 +166,7 @@ String stringFieldProperty = beanFieldName + "AsString"; String dataValue = (String)PropertyUtils.getProperty(beanData, stringFieldProperty); if (dataValue.equals(newValue)) { - PropertyUtils.setProperty(data, stringFieldProperty, currentValue); + PropertyUtils.setProperty(beanData, stringFieldProperty, currentValue); dataStorage.set(lineIndex, beanData.getData()); } Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/services/CommandService.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/services/CommandService.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/services/CommandService.java 2012-01-04 10:14:42 UTC (rev 940) @@ -5,7 +5,7 @@ * $Id$ * $HeadURL$ * %% - * Copyright (C) 2010 Codelutin, Chatellier Eric + * Copyright (C) 2010 - 2012 Ifremer, Codelutin, Chatellier Eric * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -84,19 +84,22 @@ } command.doCommand(project, container); - container.getHistoryCommand().add(command); + container.addHistoryCommand(command); } /** - * Undo last command on project. + * Undo last command on container. + * Undo all operation with same uuid of first action to undo. * + * Warning, this method doesn't fire historyCommands events. + * * @param project project * @param container container * @throws CoserBusinessException */ public void undoAction(Project project, AbstractDataContainer container) throws CoserBusinessException { - ListIterator<Command> itCommand = container.getHistoryCommand().listIterator(container.getHistoryCommand().size()); + ListIterator<Command> itCommand = container.getHistoryCommands().listIterator(container.getHistoryCommands().size()); String lastUUID = null; while (itCommand.hasPrevious()) { @@ -112,4 +115,20 @@ } } } + + /** + * Undo specified command count on container. + * + * @param project project + * @param container container + * @param commandsCount commands count to undo + * @throws CoserBusinessException + */ + public void undoAction(Project project, AbstractDataContainer container, int commandsCount) throws CoserBusinessException { + for (int i = 0; i < commandsCount ; i++) { + Command command = container.getHistoryCommands().get(container.getHistoryCommands().size() - 1); + command.undoCommand(project, container); + container.removeHistoryCommand(command); + } + } } Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/services/ProjectService.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/services/ProjectService.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/services/ProjectService.java 2012-01-04 10:14:42 UTC (rev 940) @@ -254,7 +254,7 @@ // init additional structures (load empty control) project.setSelections(new HashMap<String, Selection>()); - control.setHistoryCommand(new ArrayList<Command>()); + control.setHistoryCommands(new ArrayList<Command>()); project.setControl(control); return project; @@ -694,7 +694,7 @@ inputStream.close(); List<Command> commands = getHistoryCommandsFromProperties(props, "control.commands"); - control.setHistoryCommand(commands); + control.setHistoryCommands(commands); if (log.isDebugEnabled()) { log.debug("Read control properties file : " + controlPropertiesFile); @@ -708,7 +708,7 @@ } else { // FIX future NPE - control.setHistoryCommand(new ArrayList<Command>()); + control.setHistoryCommands(new ArrayList<Command>()); } return project; @@ -773,7 +773,7 @@ inputStream.close(); List<Command> commands = getHistoryCommandsFromProperties(props, "selection.commands"); - selection.setHistoryCommand(commands); + selection.setHistoryCommands(commands); if (log.isDebugEnabled()) { log.debug("Read selection properties file : " + selectionPropertiesFile); @@ -832,7 +832,7 @@ // sauvegarde des informations du control (properties) File propertiesFile = new File(controlDirectory, "control.properties"); Properties props = control.toProperties(); - addHistoryCommandsToProperties(props, project.getControl().getHistoryCommand(), "control.commands"); + addHistoryCommandsToProperties(props, project.getControl().getHistoryCommands(), "control.commands"); OutputStream outputStream = null; try { outputStream = new FileOutputStream(propertiesFile); @@ -920,8 +920,8 @@ // filter years filterDataYearsAndGetStrata(project, selection, selection.getSelectedYears()); // applying merges commands - List<Command> commands = new ArrayList<Command>(selection.getHistoryCommand()); - selection.getHistoryCommand().clear(); + List<Command> commands = new ArrayList<Command>(selection.getHistoryCommands()); + selection.clearHistoryCommands(); for (Command command : commands) { commandService.doAction(command, project, selection); } @@ -950,7 +950,7 @@ selection.setStrata(dataStrata); // data reloaded from control, so modification are empty - selection.setHistoryCommand(new ArrayList<Command>()); + selection.setHistoryCommands(new ArrayList<Command>()); return selection; } @@ -1023,7 +1023,7 @@ // sauvegarde des informations de la selection (properties) File propertiesFile = new File(selectionDirectory, selectionName + ".selection"); Properties props = selection.toProperties(); - addHistoryCommandsToProperties(props, selection.getHistoryCommand(), "selection.commands"); + addHistoryCommandsToProperties(props, selection.getHistoryCommands(), "selection.commands"); OutputStream outputStream = null; try { outputStream = new FileOutputStream(propertiesFile); @@ -3019,7 +3019,7 @@ selection.fromProperties(props); List<Command> commands = getHistoryCommandsFromProperties(props, "selection.commands"); - selection.setHistoryCommand(commands); + selection.setHistoryCommands(commands); if (log.isDebugEnabled()) { log.debug("Read selection properties file : " + selectionPropertiesFile); Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/services/PublicationService.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/services/PublicationService.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/services/PublicationService.java 2012-01-04 10:14:42 UTC (rev 940) @@ -5,7 +5,7 @@ * $Id$ * $HeadURL$ * %% - * Copyright (C) 2010 - 2011 Ifremer, Codelutin, Chatellier Eric + * Copyright (C) 2010 - 2012 Ifremer, Codelutin, Chatellier Eric * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -442,7 +442,7 @@ * @return extractedFile * @throws CoserBusinessException * - * @see AbstractDataContainer#getHistoryCommand() + * @see AbstractDataContainer#getHistoryCommands() */ public File extractControlLogAsHTML(Project project, Control control) throws CoserBusinessException { File exportHtmlFile = null; @@ -488,7 +488,7 @@ * @return extractedFile * @throws CoserBusinessException * - * @see AbstractDataContainer#getHistoryCommand() + * @see AbstractDataContainer#getHistoryCommands() */ public File extractSelectionLogAsHTML(Project project, Selection selection) throws CoserBusinessException { File exportHtmlFile = null; @@ -582,7 +582,7 @@ Set<String> haulLines = new HashSet<String>(); Set<String> strataLines = new HashSet<String>(); - for (Command command : container.getHistoryCommand()) { + for (Command command : container.getHistoryCommands()) { Category category = null; String line = null; if (command instanceof CategoryLineCommand) { @@ -616,7 +616,7 @@ // third, generate html report out.println("<h2>" + _("coser.business.publication.datamodification") + "</h2>"); out.println("<ol>"); - for (Command command : container.getHistoryCommand()) { + for (Command command : container.getHistoryCommands()) { Category category = null; String line = null; if (command instanceof CategoryLineCommand) { Modified: trunk/coser-business/src/main/java/fr/ifremer/coser/storage/MemoryDataStorage.java =================================================================== --- trunk/coser-business/src/main/java/fr/ifremer/coser/storage/MemoryDataStorage.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/java/fr/ifremer/coser/storage/MemoryDataStorage.java 2012-01-04 10:14:42 UTC (rev 940) @@ -155,6 +155,7 @@ String stringData = arrayToString(data); listStorage.add(index, stringData); lineIndexStorage.add(index, data[0]); + lineIndexStorageCache.clear(); // example (redo commands) } /* Modified: trunk/coser-business/src/main/resources/i18n/coser-business_fr_FR.properties =================================================================== --- trunk/coser-business/src/main/resources/i18n/coser-business_fr_FR.properties 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/main/resources/i18n/coser-business_fr_FR.properties 2012-01-04 10:14:42 UTC (rev 940) @@ -1,10 +1,10 @@ Can't\ create\ project=Impossible de créer le projet -Can't\ find\ line\ %s\ for\ deletion= -Can't\ find\ line\ %s\ for\ undeletion= +Can't\ find\ line\ %s\ for\ deletion=Impossible de trouver la ligne %s a supprimer +Can't\ find\ line\ %s\ for\ undeletion=Impossible de trouver la ligne %s a rétablir Can't\ read\ file\ %s\ for\ category\ %s=Impossible de lire le fichier %s pour la catégorie %s \! Can't\ read\ file\ '%s'.\ Check\ CSV\ file\ separator=Impossible de lire le fichier '%s'.\nMerci de vérifier le séparateur utilisé est bien un point-virgule ';' Can't\ replace\ data\ value.\ Expected\ %s\ but\ was\ %s= -Creating\ matrix\ \:\ %d*%d*%d*%d= +Creating\ matrix\ \:\ %d*%d*%d*%d=Impossible de créer la matrice \: %d*%d*%d*%d Missing\ file\ %s=Fichier manquant \: %s Missing\ maturity\ attribute=Attribut maturité manquant Missing\ sex\ attribute=Attribut sex manquant @@ -15,7 +15,7 @@ Project\ %s\ already\ exist=Le projet %s existe déjà \! Project\ %s\ doesn't\ exists\ \!=Le projet %s n'existe pas \! Selection\ %s\ already\ exists=La sélection %s existe déjà \! -Selection\ %s\ doesn't\ exists\ \!= +Selection\ %s\ doesn't\ exists\ \!=La sélection %s n'existe pas \! Species\ %s\ doesn't\ exist\ in\ referential=L'espèce %s n'existe pas dans le référentiel Wrong\ header\ detected\ in\ file\ %s.\ Found\ \:\ %s,\ expected\ %s=Mauvais entête de fichier détecté dans\n%s. Corrigez les entêtes et relancez la création du projet.\n\nTrouvé \:\n\t%s\nAttendu \:\n\t%s. Wrong\ header\ detected\ in\ file\ %s.\ Found\ \:\ %s,\ expected\ %s\ or\ %s=Mauvais entête de fichier détecté dans %s. Corrigez les entêtes et relancez la création du projet.\n\nTrouvé \:\n\t%s\nAttendu \:\n\t%s\nou \:\n\t%s. Modified: trunk/coser-business/src/test/java/fr/ifremer/coser/services/CommandServiceTest.java =================================================================== --- trunk/coser-business/src/test/java/fr/ifremer/coser/services/CommandServiceTest.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-business/src/test/java/fr/ifremer/coser/services/CommandServiceTest.java 2012-01-04 10:14:42 UTC (rev 940) @@ -127,7 +127,7 @@ commandService.doAction(command, project, project.getControl()); commandService.undoAction(project, project.getControl()); - project.getControl().getHistoryCommand().add(command); + project.getControl().addHistoryCommand(command); commandService.undoAction(project, project.getControl()); } @@ -150,8 +150,12 @@ commandService.doAction(command, project, project.getControl()); Assert.assertEquals("392.98", project.getControl().getCatch().get(4)[Catch.INDEX_NUMBER]); + + // also test undo + commandService.undoAction(project, project.getControl()); + Assert.assertEquals("251.86", project.getControl().getCatch().get(4)[Catch.INDEX_NUMBER]); } - + /** * Test la commande de modification de valeur des champs alors que la valeur * attendu avant de remplacer est fausse. Added: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerRedoMenu.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerRedoMenu.java (rev 0) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerRedoMenu.java 2012-01-04 10:14:42 UTC (rev 940) @@ -0,0 +1,162 @@ +/* + * #%L + * + * + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2012 Ifremer, Codelutin, Chatellier Eric + * %% + * This program 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, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +package fr.ifremer.coser.ui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import fr.ifremer.coser.bean.AbstractDataContainer; +import fr.ifremer.coser.bean.Control; +import fr.ifremer.coser.command.Command; +import fr.ifremer.coser.ui.control.ControlView; + +/** + * Redo menu action. + * + * Currently working only for control view because in selection merge commands + * are not undoable. + * + * Listen for action undone by {@link ContainerUndoMenu} to redo them. When + * new history commands are done on container, local modification list is + * cleared (can't be undone). + * + * @author chatellier + * @version $Revision$ + * @since 1.2 + * + * Last update : $Date$ + * By : $Author$ + */ +public class ContainerRedoMenu extends JMenu implements ActionListener, PropertyChangeListener { + + /** serialVersionUID */ + private static final long serialVersionUID = 2104672856535432709L; + + private static final Log log = LogFactory.getLog(ContainerRedoMenu.class); + + protected ControlView controlView; + + /** Ordered from older to newer (must be undo from last to first). */ + protected List<Command> redoableCommands = new ArrayList<Command>(); + + public ContainerRedoMenu() { + setEnabled(false); + } + + public void setControlView(ControlView controlView) { + if (this.controlView != null && this.controlView.getControl() != null) { + this.controlView.getControl().removePropertyChangeListener(AbstractDataContainer.PROPERTY_HISTORY_COMMANDS, this); + } + this.controlView = controlView; + if (this.controlView != null && this.controlView.getControl() != null) { + this.controlView.getControl().addPropertyChangeListener(AbstractDataContainer.PROPERTY_HISTORY_COMMANDS, this); + } + updateSubMenuItems(); + } + + public List<Command> getCommands() { + return redoableCommands; + } + + public void setCommands(List<Command> redoableCommands) { + this.redoableCommands = redoableCommands; + updateSubMenuItems(); + } + + /** + * Update submenu items. + */ + protected void updateSubMenuItems() { + + if (log.isDebugEnabled()) { + log.debug("Refresh redo menu items"); + } + + removeAll(); + boolean menuEnabled = false; + + if (controlView != null && controlView.getControl() != null) { + Control control = controlView.getControl(); + menuEnabled = !redoableCommands.isEmpty(); + + // command in reverse order (only 10 last) + for (int i = redoableCommands.size() - 1 ; i >= 0 && i > redoableCommands.size() - 10 ; i--) { + Command command = redoableCommands.get(i); + JMenuItem commandMenu = new JMenuItem(command.getDescription(control)); + commandMenu.setActionCommand(String.valueOf(i)); + commandMenu.addActionListener(this); + add(commandMenu); + } + } + + setEnabled(menuEnabled); + } + + /* + * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) + */ + @Override + public void propertyChange(PropertyChangeEvent evt) { + redoableCommands.clear(); + updateSubMenuItems(); + } + + /* + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @Override + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + + int commandIndex = Integer.parseInt(actionCommand); + if (log.isDebugEnabled()) { + log.debug("Redo command index " + commandIndex); + } + + List<Command> commands = new ArrayList<Command>(); + List<Command> newRedoCommands = new ArrayList<Command>(redoableCommands); + for (int i = redoableCommands.size() - 1 ; i >= commandIndex ; i--) { + Command command = redoableCommands.get(i); + commands.add(command); + newRedoCommands.remove(command); + } + controlView.getHandler().redoCommands(controlView, commands); + + // update redoable command list + // a gerer completement sinon, avec les event du control, on perd tout. + setCommands(newRedoCommands); + } +} Property changes on: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerRedoMenu.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Added: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerUndoMenu.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerUndoMenu.java (rev 0) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerUndoMenu.java 2012-01-04 10:14:42 UTC (rev 940) @@ -0,0 +1,156 @@ +/* + * #%L + * + * + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2012 Ifremer, Codelutin, Chatellier Eric + * %% + * This program 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, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ + +package fr.ifremer.coser.ui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import fr.ifremer.coser.bean.AbstractDataContainer; +import fr.ifremer.coser.bean.Control; +import fr.ifremer.coser.command.Command; +import fr.ifremer.coser.ui.control.ControlView; + +/** + * Undo menu action. + * + * Currently working only for control view because in selection merge commands + * are not undoable. + * + * @author chatellier + * @version $Revision$ + * @since 1.2 + * + * Last update : $Date$ + * By : $Author$ + */ +public class ContainerUndoMenu extends JMenu implements ActionListener, PropertyChangeListener { + + /** serialVersionUID */ + private static final long serialVersionUID = 2104672856535432709L; + + private static final Log log = LogFactory.getLog(ContainerUndoMenu.class); + + protected ControlView controlView; + + protected ContainerRedoMenu redoMenu; + + public ContainerUndoMenu() { + setEnabled(false); + } + + public void setControlView(ControlView controlView) { + if (this.controlView != null && this.controlView.getControl() != null) { + this.controlView.getControl().removePropertyChangeListener(AbstractDataContainer.PROPERTY_HISTORY_COMMANDS, this); + } + this.controlView = controlView; + if (this.controlView != null && this.controlView.getControl() != null) { + this.controlView.getControl().addPropertyChangeListener(AbstractDataContainer.PROPERTY_HISTORY_COMMANDS, this); + } + updateSubMenuItems(); + } + + public void setRedoMenu(ContainerRedoMenu redoMenu) { + this.redoMenu = redoMenu; + } + + /** + * Update submenu items. + */ + protected void updateSubMenuItems() { + + if (log.isDebugEnabled()) { + log.debug("Refresh undo menu items"); + } + + removeAll(); + boolean menuEnabled = false; + + if (controlView != null && controlView.getControl() != null) { + Control control = controlView.getControl(); + List<Command> commands = controlView.getControl().getHistoryCommands(); + menuEnabled = !commands.isEmpty(); + + // command in reverse order (only 10 last) + for (int i = commands.size() - 1 ; i >= 0 && i > commands.size() - 10 ; i--) { + Command command = commands.get(i); + JMenuItem commandMenu = new JMenuItem(command.getDescription(control)); + commandMenu.setActionCommand(String.valueOf(i)); + commandMenu.addActionListener(this); + add(commandMenu); + } + } + + setEnabled(menuEnabled); + } + + /* + * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) + */ + @Override + public void propertyChange(PropertyChangeEvent evt) { + updateSubMenuItems(); + } + + /* + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @Override + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + + Control control = controlView.getControl(); + int commandIndex = Integer.parseInt(actionCommand); + if (log.isDebugEnabled()) { + log.debug("Undo command index " + commandIndex); + } + + // le menu redo est géré directement par le menu undo + // c'est mal fait, mais tellement plus simple + // attention copie : la list va changer avec les fire + List<Command> redoCommands = new ArrayList<Command>(redoMenu.getCommands()); + List<Command> commands = controlView.getControl().getHistoryCommands(); + for (int i = commands.size() - 1 ; i >= commandIndex ; i--) { + redoCommands.add(commands.get(i)); + } + + // undo commands + int count = control.getHistoryCommands().size() - commandIndex; + controlView.getHandler().undoCommands(controlView, count); + + // update redo menu + redoMenu.setCommands(redoCommands); + } +} Property changes on: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/ContainerUndoMenu.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision HeadURL Modified: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrame.jaxx =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrame.jaxx 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrame.jaxx 2012-01-04 10:14:42 UTC (rev 940) @@ -5,7 +5,7 @@ $Id$ $HeadURL$ %% - Copyright (C) 2010 - 2011 Ifremer, Codelutin, Chatellier Eric + Copyright (C) 2010 - 2012 Ifremer, Codelutin, Chatellier Eric %% This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -47,8 +47,11 @@ <JMenu text="coser.ui.mainframe.menu.data" enabled="{getProject() != null}"> <JMenuItem text="coser.ui.mainframe.menu.data.summary" onActionPerformed="getHandler().showSummaryView()"/> <JMenuItem text="coser.ui.mainframe.menu.data.control" onActionPerformed="getHandler().showControlView()"/> - <SelectionsListMenuItem id="menuWindowSelectionMenuItem" project="{getProject()}" + <SelectionsListMenu id="menuWindowSelectionMenu" project="{getProject()}" text="coser.ui.mainframe.menu.data.selections" constructorParams="this" /> + <JSeparator/> + <ContainerUndoMenu id="menuWindowSelectionUndo" text="coser.ui.mainframe.menu.data.undo" redoMenu="{menuWindowSelectionRedo}" /> + <ContainerRedoMenu id="menuWindowSelectionRedo" text="coser.ui.mainframe.menu.data.redo" /> </JMenu> <JMenu text="coser.ui.mainframe.menu.admin"> Modified: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrameHandler.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrameHandler.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/CoserFrameHandler.java 2012-01-04 10:14:42 UTC (rev 940) @@ -157,6 +157,17 @@ if (component != null) { view.getMainViewContent().add(component, BorderLayout.CENTER); } + + // centralisation of undo/redo menu manipulation + if (component instanceof ControlView) { + ControlView controlView = (ControlView)component; + view.getMenuWindowSelectionUndo().setControlView(controlView); + view.getMenuWindowSelectionRedo().setControlView(controlView); + } else { + view.getMenuWindowSelectionUndo().setControlView(null); + view.getMenuWindowSelectionRedo().setControlView(null); + } + view.getMainViewContent().repaint(); view.getMainViewContent().validate(); } Copied: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenu.java (from rev 932, trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenuItem.java) =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenu.java (rev 0) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenu.java 2012-01-04 10:14:42 UTC (rev 940) @@ -0,0 +1,173 @@ +/* + * #%L + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2010 - 2012 Ifremer, Codelutin, Chatellier Eric + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +package fr.ifremer.coser.ui; + +import static org.nuiton.i18n.I18n._; + +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Map; + +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JSeparator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import fr.ifremer.coser.bean.Control; +import fr.ifremer.coser.bean.Project; +import fr.ifremer.coser.bean.Selection; + +/** + * Selection list menu item. Display dynamic entries for project selection. + * + * @author chatellier + * @version $Revision$ + * + * Last update : $Date$ + * By : $Author$ + */ +public class SelectionsListMenu extends JMenu implements ActionListener, PropertyChangeListener { + + /** serialVersionUID. */ + private static final long serialVersionUID = -3528302058982208907L; + + private static final Log log = LogFactory.getLog(SelectionsListMenu.class); + + protected CoserFrame view; + + protected Project project; + + public SelectionsListMenu(CoserFrame view) { + this.view = view; + updateMenuContent(); + } + + public Project getProject() { + return project; + } + + public void setProject(Project project) { + if (this.project != null) { + this.project.removePropertyChangeListener(Project.PROPERTY_SELECTIONS, this); + this.project.getControl().removePropertyChangeListener(Control.PROPERTY_VALIDATED, this); + } + this.project = project; + if (this.project != null) { + this.project.addPropertyChangeListener(Project.PROPERTY_SELECTIONS, this); + this.project.getControl().addPropertyChangeListener(Control.PROPERTY_VALIDATED, this); + } + updateMenuContent(); + } + + protected void updateMenuContent() { + removeAll(); + + if (log.isDebugEnabled()) { + log.debug("Refresh selection menu items"); + } + + if (project != null) { + + if (!project.getControl().isValidated()) { + JMenuItem menuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.noValidation")); + menuItem.setFont(menuItem.getFont().deriveFont(Font.ITALIC)); + menuItem.setEnabled(false); + add(menuItem); + } + else { + Map<String, Selection> selections = project.getSelections(); + + if (selections == null || selections.isEmpty()) { + JMenuItem menuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.noSelection")); + menuItem.setFont(menuItem.getFont().deriveFont(Font.ITALIC)); + menuItem.setEnabled(false); + add(menuItem); + } + else { + for (String selectionName : selections.keySet()) { + // new selection + JMenuItem menuItem = new JMenuItem(selectionName); + menuItem.setActionCommand(selectionName); + menuItem.addActionListener(this); + add(menuItem); + } + } + + // seperator + add(new JSeparator()); + + // new selection + JMenuItem newMenuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.newSelection")); + // les chaines ne doivent pas poser pb, on ne peut pas + // avoir de selection avec espace + newMenuItem.setActionCommand("$new selection$"); + newMenuItem.addActionListener(this); + add(newMenuItem); + + // replay selection + JMenuItem replayMenuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.replaySelection")); + // les chaines ne doivent pas poser pb, on ne peut pas + // avoir de selection avec espace + replayMenuItem.setActionCommand("$replay selection$"); + replayMenuItem.addActionListener(this); + add(replayMenuItem); + } + } + } + + /* + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + @Override + public void actionPerformed(ActionEvent event) { + String actionCommand = event.getActionCommand(); + + // new selection case + if ("$new selection$".equals(actionCommand)) { + view.getHandler().showSelectionView(); + } + else if ("$replay selection$".equals(actionCommand)) { + view.getHandler().replaySelection(); + } + else { + view.getHandler().showSelectionView(actionCommand); + } + } + + /* + * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) + */ + @Override + public void propertyChange(PropertyChangeEvent evt) { + + // quand la liste de selection change + // ou la validation du controle + updateMenuContent(); + } +} Deleted: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenuItem.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenuItem.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/SelectionsListMenuItem.java 2012-01-04 10:14:42 UTC (rev 940) @@ -1,173 +0,0 @@ -/* - * #%L - * $Id$ - * $HeadURL$ - * %% - * Copyright (C) 2010 - 2011 Ifremer, Codelutin, Chatellier Eric - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - -package fr.ifremer.coser.ui; - -import static org.nuiton.i18n.I18n._; - -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.Map; - -import javax.swing.JMenu; -import javax.swing.JMenuItem; -import javax.swing.JSeparator; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import fr.ifremer.coser.bean.Control; -import fr.ifremer.coser.bean.Project; -import fr.ifremer.coser.bean.Selection; - -/** - * Selection list menu item. Display dynamic entries for project selection. - * - * @author chatellier - * @version $Revision$ - * - * Last update : $Date$ - * By : $Author$ - */ -public class SelectionsListMenuItem extends JMenu implements ActionListener, PropertyChangeListener { - - /** serialVersionUID. */ - private static final long serialVersionUID = -3528302058982208907L; - - private static final Log log = LogFactory.getLog(SelectionsListMenuItem.class); - - protected CoserFrame view; - - protected Project project; - - public SelectionsListMenuItem(CoserFrame view) { - this.view = view; - updateMenuContent(); - } - - public Project getProject() { - return project; - } - - public void setProject(Project project) { - if (this.project != null) { - this.project.removePropertyChangeListener(Project.PROPERTY_SELECTIONS, this); - this.project.getControl().removePropertyChangeListener(Control.PROPERTY_VALIDATED, this); - } - this.project = project; - if (this.project != null) { - this.project.addPropertyChangeListener(Project.PROPERTY_SELECTIONS, this); - this.project.getControl().addPropertyChangeListener(Control.PROPERTY_VALIDATED, this); - } - updateMenuContent(); - } - - protected void updateMenuContent() { - removeAll(); - - if (log.isDebugEnabled()) { - log.debug("Refresh selection menu items"); - } - - if (project != null) { - - if (!project.getControl().isValidated()) { - JMenuItem menuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.noValidation")); - menuItem.setFont(menuItem.getFont().deriveFont(Font.ITALIC)); - menuItem.setEnabled(false); - add(menuItem); - } - else { - Map<String, Selection> selections = project.getSelections(); - - if (selections == null || selections.isEmpty()) { - JMenuItem menuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.noSelection")); - menuItem.setFont(menuItem.getFont().deriveFont(Font.ITALIC)); - menuItem.setEnabled(false); - add(menuItem); - } - else { - for (String selectionName : selections.keySet()) { - // new selection - JMenuItem menuItem = new JMenuItem(selectionName); - menuItem.setActionCommand(selectionName); - menuItem.addActionListener(this); - add(menuItem); - } - } - - // seperator - add(new JSeparator()); - - // new selection - JMenuItem newMenuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.newSelection")); - // les chaines ne doivent pas poser pb, on ne peut pas - // avoir de selection avec espace - newMenuItem.setActionCommand("new selection"); - newMenuItem.addActionListener(this); - add(newMenuItem); - - // replay selection - JMenuItem replayMenuItem = new JMenuItem(_("coser.ui.mainframe.menu.data.replaySelection")); - // les chaines ne doivent pas poser pb, on ne peut pas - // avoir de selection avec espace - replayMenuItem.setActionCommand("replay selection"); - replayMenuItem.addActionListener(this); - add(replayMenuItem); - } - } - } - - /* - * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) - */ - @Override - public void actionPerformed(ActionEvent event) { - String actionCommand = event.getActionCommand(); - - // new selection case - if ("new selection".equals(actionCommand)) { - view.getHandler().showSelectionView(); - } - else if ("replay selection".equals(actionCommand)) { - view.getHandler().replaySelection(); - } - else { - view.getHandler().showSelectionView(actionCommand); - } - } - - /* - * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) - */ - @Override - public void propertyChange(PropertyChangeEvent evt) { - - // quand la liste de selection change - // ou la validation du controle - updateMenuContent(); - } -} Modified: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlDuplicatedLineTableModel.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlDuplicatedLineTableModel.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlDuplicatedLineTableModel.java 2012-01-04 10:14:42 UTC (rev 940) @@ -322,9 +322,12 @@ public void fireTableRowsDeleted(int firstRow, int lastRow) { // fire before to get row count return correct value dataTableModel.fireTableRowsDeleted(firstRow, lastRow); - // then delete line - for (int lineIndex = firstRow; lineIndex <= lastRow; ++lineIndex) { - deleteLine(lineIndex); + + // then delete line in current model + if (linesIndex != null) { + for (int lineIndex = firstRow; lineIndex <= lastRow; ++lineIndex) { + deleteLine(lineIndex); + } } } Modified: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlHandler.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlHandler.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/control/ControlHandler.java 2012-01-04 10:14:42 UTC (rev 940) @@ -77,6 +77,7 @@ import fr.ifremer.coser.CoserConstants.ValidationLevel; import fr.ifremer.coser.CoserException; import fr.ifremer.coser.bean.Project; +import fr.ifremer.coser.command.Command; import fr.ifremer.coser.control.ControlError; import fr.ifremer.coser.control.ControlErrorGroup; import fr.ifremer.coser.control.DiffCatchLengthControlError; @@ -85,6 +86,7 @@ import fr.ifremer.coser.data.Haul; import fr.ifremer.coser.data.Length; import fr.ifremer.coser.data.Strata; +import fr.ifremer.coser.services.CommandService; import fr.ifremer.coser.services.ControlService; import fr.ifremer.coser.services.ProjectService; import fr.ifremer.coser.services.PublicationService; @@ -1091,4 +1093,52 @@ throw new CoserException("Can't generate html report", ex); } } + + /** + * Undo commands. + * + * @param controlView view + * @param commandsCount commands count to undo + * + * @since 1.2 + */ + public void undoCommands(ControlView controlView, int commandsCount) { + CommandService commandeService = controlView.getContextValue(CommandService.class); + Project project = controlView.getContextValue(Project.class); + + try { + commandeService.undoAction(project, project.getControl(), commandsCount); + + // after undo, refresh table, edition zone + JTable controlDataTable = getControlDataTable(controlView); + ControlTableModel model = (ControlTableModel)controlDataTable.getModel(); + model.fireTableDataChanged(); + } catch (CoserBusinessException ex) { + throw new CoserException("Can't undo selected commands", ex); + } + } + + /** + * Redo commands. + * + * @param controlView view + * @param commands commands to redo + * @since 1.2 + */ + public void redoCommands(ControlView controlView, List<Command> commands) { + CommandService commandeService = controlView.getContextValue(CommandService.class); + Project project = controlView.getContextValue(Project.class); + + try { + for (Command command : commands) { + commandeService.doAction(command, project, project.getControl()); + } + // after redo, refresh table, edition zone + JTable controlDataTable = getControlDataTable(controlView); + ControlTableModel model = (ControlTableModel)controlDataTable.getModel(); + model.fireTableDataChanged(); + } catch (CoserBusinessException ex) { + throw new CoserException("Can't redo selected commands", ex); + } + } } Modified: trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/selection/replay/SelectionReplayHandler.java =================================================================== --- trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/selection/replay/SelectionReplayHandler.java 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/java/fr/ifremer/coser/ui/selection/replay/SelectionReplayHandler.java 2012-01-04 10:14:42 UTC (rev 940) @@ -207,7 +207,7 @@ projectService.filterDataStrata(project, selection, strata); // init next step - List<Command> commands = replayedSelection.getHistoryCommand(); + List<Command> commands = replayedSelection.getHistoryCommands(); if (CollectionUtils.isNotEmpty(commands)) { view.getCommandListModel().setCommands(commands); view.getWizardLayout().show(view.getWizardPanel(), "step4"); Modified: trunk/coser-ui/src/main/resources/i18n/coser-ui_en_GB.properties =================================================================== --- trunk/coser-ui/src/main/resources/i18n/coser-ui_en_GB.properties 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/resources/i18n/coser-ui_en_GB.properties 2012-01-04 10:14:42 UTC (rev 940) @@ -78,9 +78,11 @@ coser.ui.mainframe.menu.data.newSelection=New selection coser.ui.mainframe.menu.data.noSelection=No selection coser.ui.mainframe.menu.data.noValidation=Control not validated +coser.ui.mainframe.menu.data.redo=Redo coser.ui.mainframe.menu.data.replaySelection=Replay selection coser.ui.mainframe.menu.data.selections=Selections coser.ui.mainframe.menu.data.summary=Summary +coser.ui.mainframe.menu.data.undo=Undo coser.ui.mainframe.menu.editProject=Edit project coser.ui.mainframe.menu.file=File coser.ui.mainframe.menu.help=Help Modified: trunk/coser-ui/src/main/resources/i18n/coser-ui_fr_FR.properties =================================================================== --- trunk/coser-ui/src/main/resources/i18n/coser-ui_fr_FR.properties 2012-01-04 09:16:37 UTC (rev 939) +++ trunk/coser-ui/src/main/resources/i18n/coser-ui_fr_FR.properties 2012-01-04 10:14:42 UTC (rev 940) @@ -78,9 +78,11 @@ coser.ui.mainframe.menu.data.newSelection=Nouvelle sélection coser.ui.mainframe.menu.data.noSelection=Aucune sélection coser.ui.mainframe.menu.data.noValidation=Contrôle non validé +coser.ui.mainframe.menu.data.redo=Refaire coser.ui.mainframe.menu.data.replaySelection=Rejouer une sélection coser.ui.mainframe.menu.data.selections=Sélections coser.ui.mainframe.menu.data.summary=Résumé +coser.ui.mainframe.menu.data.undo=Annuler coser.ui.mainframe.menu.editProject=Modifier le projet coser.ui.mainframe.menu.file=Fichier coser.ui.mainframe.menu.help=Aide
participants (1)
-
echatellier@users.forge.codelutin.com