View Javadoc
1   package fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency;
2   
3   /*
4    * #%L
5    * Tutti :: UI
6    * %%
7    * Copyright (C) 2012 - 2014 Ifremer
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the 
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public 
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import fr.ifremer.tutti.caliper.feed.event.CaliperFeedReaderEvent;
26  import fr.ifremer.tutti.caliper.feed.event.CaliperFeedReaderListener;
27  import fr.ifremer.tutti.caliper.feed.record.CaliperFeedReaderMeasureRecord;
28  import fr.ifremer.tutti.ichtyometer.feed.event.IchtyometerFeedReaderEvent;
29  import fr.ifremer.tutti.ichtyometer.feed.event.IchtyometerFeedReaderListener;
30  import fr.ifremer.tutti.ichtyometer.feed.record.IchtyometerFeedReaderMeasureRecord;
31  import fr.ifremer.tutti.persistence.ProgressionModel;
32  import fr.ifremer.tutti.persistence.entities.TuttiEntities;
33  import fr.ifremer.tutti.persistence.entities.data.CopyIndividualObservationMode;
34  import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
35  import fr.ifremer.tutti.persistence.entities.data.SampleCategoryModel;
36  import fr.ifremer.tutti.persistence.entities.protocol.Rtp;
37  import fr.ifremer.tutti.persistence.entities.protocol.SpeciesProtocol;
38  import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocol;
39  import fr.ifremer.tutti.persistence.entities.referential.Caracteristic;
40  import fr.ifremer.tutti.persistence.entities.referential.CaracteristicQualitativeValue;
41  import fr.ifremer.tutti.persistence.entities.referential.Sexs;
42  import fr.ifremer.tutti.persistence.entities.referential.Species;
43  import fr.ifremer.tutti.persistence.entities.referential.TaxonCache;
44  import fr.ifremer.tutti.persistence.entities.referential.TaxonCaches;
45  import fr.ifremer.tutti.service.DecoratorService;
46  import fr.ifremer.tutti.service.TuttiDataContext;
47  import fr.ifremer.tutti.service.cruise.CruiseCache;
48  import fr.ifremer.tutti.service.cruise.CruiseCacheLoader;
49  import fr.ifremer.tutti.type.WeightUnit;
50  import fr.ifremer.tutti.ui.swing.TuttiUIContext;
51  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.EditSpeciesBatchPanelUI;
52  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.SpeciesOrBenthosBatchUISupport;
53  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.edit.SpeciesBatchRowModel;
54  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.SpeciesFrequencyCellComponent.FrequencyCellEditor;
55  import fr.ifremer.tutti.ui.swing.content.operation.catches.species.frequency.actions.ApplySpeciesFrequencyRafaleAction;
56  import fr.ifremer.tutti.ui.swing.util.SoundEngine;
57  import fr.ifremer.tutti.ui.swing.util.TuttiBeanMonitor;
58  import fr.ifremer.tutti.ui.swing.util.TuttiUI;
59  import fr.ifremer.tutti.ui.swing.util.TuttiUIUtil;
60  import fr.ifremer.tutti.ui.swing.util.caracteristics.CaracteristicMapColumnRowModel;
61  import fr.ifremer.tutti.ui.swing.util.caracteristics.CaracteristicMapColumnUIHandler;
62  import fr.ifremer.tutti.ui.swing.util.caracteristics.CaracteristicMapEditorUI;
63  import fr.ifremer.tutti.ui.swing.util.table.AbstractTuttiTableUIHandler;
64  import fr.ifremer.tutti.ui.swing.util.table.CaracteristicColumnIdentifier;
65  import fr.ifremer.tutti.util.Units;
66  import jaxx.runtime.swing.editor.bean.BeanFilterableComboBox;
67  import jaxx.runtime.validator.swing.SwingValidator;
68  import org.apache.commons.collections4.CollectionUtils;
69  import org.apache.commons.io.IOUtils;
70  import org.apache.commons.logging.Log;
71  import org.apache.commons.logging.LogFactory;
72  import org.jdesktop.swingx.JXTable;
73  import org.jdesktop.swingx.decorator.ComponentAdapter;
74  import org.jdesktop.swingx.decorator.HighlightPredicate;
75  import org.jdesktop.swingx.decorator.Highlighter;
76  import org.jdesktop.swingx.table.DefaultTableColumnModelExt;
77  import org.jdesktop.swingx.table.TableColumnExt;
78  import org.jdesktop.swingx.table.TableColumnModelExt;
79  import org.nuiton.decorator.Decorator;
80  import org.nuiton.jaxx.application.ApplicationBusinessException;
81  import org.nuiton.jaxx.application.swing.AbstractApplicationUIHandler;
82  import org.nuiton.jaxx.application.swing.action.ApplicationUIAction;
83  import org.nuiton.jaxx.application.swing.table.ColumnIdentifier;
84  
85  import javax.swing.JComponent;
86  import javax.swing.JLabel;
87  import javax.swing.JOptionPane;
88  import javax.swing.JSplitPane;
89  import javax.swing.JTable;
90  import javax.swing.JTextField;
91  import javax.swing.SwingUtilities;
92  import javax.swing.table.TableCellEditor;
93  import javax.swing.table.TableCellRenderer;
94  import javax.swing.table.TableColumnModel;
95  import java.awt.Color;
96  import java.awt.Component;
97  import java.awt.Font;
98  import java.awt.event.ActionEvent;
99  import java.awt.event.KeyAdapter;
100 import java.awt.event.KeyEvent;
101 import java.beans.PropertyChangeEvent;
102 import java.beans.PropertyChangeListener;
103 import java.beans.PropertyVetoException;
104 import java.io.Serializable;
105 import java.util.ArrayList;
106 import java.util.List;
107 import java.util.Map;
108 import java.util.Objects;
109 import java.util.Optional;
110 import javax.swing.KeyStroke;
111 
112 import static org.nuiton.i18n.I18n.t;
113 
114 /**
115  * @author Tony Chemit - chemit@codelutin.com
116  * @since 0.2
117  */
118 public class SpeciesFrequencyUIHandler extends AbstractTuttiTableUIHandler<SpeciesFrequencyRowModel, SpeciesFrequencyUIModel, SpeciesFrequencyUI>
119         implements CaracteristicMapColumnUIHandler {
120 
121     /** Logger. */
122     private static final Log log = LogFactory.getLog(SpeciesFrequencyUIHandler.class);
123 
124     public static final String OBS_TABLE_CARD = "obsTableCard";
125     public static final String EDIT_CARACTERISTICS_CARD = "editCaracteristicsCard";
126 
127     private FrequencyCellEditor frequencyEditor;
128 
129     private TaxonCache taxonCache;
130 
131     private Map<String, Caracteristic> lengthStepCaracteristics;
132 
133     /**
134      * Weight unit.
135      *
136      * @since 2.5
137      */
138     protected WeightUnit weightUnit;
139 
140     /**
141      * To consume measures when they arrive from the ichtyometer.
142      */
143     protected final IchtyometerFeedReaderListener ichtyometerFeedReaderListener;
144     /**
145      * To consume measures when they arrive from the caliper.
146      */
147     protected final CaliperFeedReaderListener caliperFeedReaderListener;
148     /**
149      * Pour écouter quand le big fin se connecte.
150      */
151     protected final PropertyChangeListener listenIchtyomerIsConnected;
152     /**
153      * Pour écouter quand le pied à coulisse se connecte.
154      */
155     protected final PropertyChangeListener listenCaliperIsConnected;
156 
157     protected ApplySpeciesFrequencyRafaleAction applySpeciesFrequencyRafaleAction;
158 
159     protected SpeciesOrBenthosBatchUISupport speciesOrBenthosBatchUISupport;
160 
161     protected IndividualObservationBatchTableHandler individualObservationBatchTableHandler;
162     protected AverageWeightsHistogramHandler averageWeightsHistogramHandler;
163     protected FrequenciesHistogramHandler frequenciesHistogramHandler;
164     protected SamplingNotificationZoneHandler samplingNotificationZoneHandler;
165 
166     protected Decorator<Caracteristic> caracteristicDecorator;
167     protected Decorator<Caracteristic> caracteristicTipDecorator;
168     protected Decorator<CaracteristicQualitativeValue> caracteristicQualitativeDecorator;
169     protected SoundEngine soundEngine;
170     protected PropertyChangeListener listenFishingOperationReloadInDataContext;
171 
172     public SpeciesFrequencyUIHandler() {
173 
174         super(SpeciesFrequencyRowModel.PROPERTY_LENGTH_STEP,
175               SpeciesFrequencyRowModel.PROPERTY_NUMBER,
176               SpeciesFrequencyRowModel.PROPERTY_WEIGHT);
177 
178         this.ichtyometerFeedReaderListener = new IchtyometerFeedReaderListener() {
179 
180             @Override
181             public void recordRead(IchtyometerFeedReaderEvent event) {
182                 final IchtyometerFeedReaderMeasureRecord record = event.getRecord();
183 
184                 SwingUtilities.invokeLater(
185                         () -> {
186                             if (!getModel().isSimpleCountingMode()) {
187 
188                                 // can try to consume value
189                                 consumeIchtyometerFeedRecord(record);
190                             }
191                         }
192                 );
193             }
194         };
195 
196         this.caliperFeedReaderListener = new CaliperFeedReaderListener() {
197 
198             @Override
199             public void recordRead(CaliperFeedReaderEvent event) {
200                 final CaliperFeedReaderMeasureRecord record = event.getRecord();
201 
202                 SwingUtilities.invokeLater(
203                         () -> {
204                             if (!getModel().isSimpleCountingMode()) {
205 
206                                 // can try to consume value
207                                 consumeCaliperFeedRecord(record);
208                             }
209                         }
210                 );
211             }
212         };
213 
214         this.listenIchtyomerIsConnected = new PropertyChangeListener() {
215             @Override
216             public void propertyChange(PropertyChangeEvent evt) {
217                 boolean connected = (boolean) evt.getNewValue();
218 
219                 if (connected && SpeciesFrequencyUIHandler.this.frequencyEditor != null) {
220 
221                     // listen when itchtyometer is connected and this ui is showing
222                     SpeciesFrequencyUIHandler.this.listenItchtyometer();
223                 }
224 
225                 SwingUtilities.invokeLater(SpeciesFrequencyUIHandler.this::updateLogVisibility);
226             }
227         };
228 
229         this.listenCaliperIsConnected = new PropertyChangeListener() {
230             @Override
231             public void propertyChange(PropertyChangeEvent evt) {
232                 boolean connected = (boolean) evt.getNewValue();
233 
234                 if (connected && SpeciesFrequencyUIHandler.this.frequencyEditor != null) {
235 
236                     // listen when caliper is connected and this ui is showing
237                     SpeciesFrequencyUIHandler.this.listenCaliper();
238                 }
239 
240                 SwingUtilities.invokeLater(SpeciesFrequencyUIHandler.this::updateLogVisibility);
241             }
242         };
243 
244         // pour recharger l'opération dans le modèle quand celle ci a changée (après une sauvegarde de l'opération par exemple)
245         this.listenFishingOperationReloadInDataContext = new PropertyChangeListener() {
246             @Override
247             public void propertyChange(PropertyChangeEvent event) {
248                 TuttiDataContext source = (TuttiDataContext) event.getSource();
249                 FishingOperation fishingOperation = source.getFishingOperation();
250                 if (log.isInfoEnabled()) {
251                     log.info("Reloading fishing operation in model: " + fishingOperation);
252                 }
253                 SpeciesFrequencyUIHandler.this.getModel().setFishingOperation(fishingOperation);
254             }
255         };
256 
257     }
258 
259     //------------------------------------------------------------------------//
260     //-- AbstractTuttiTableUIHandler methods                                --//
261     //------------------------------------------------------------------------//
262 
263     @Override
264     public SpeciesFrequencyTableModel getTableModel() {
265         return (SpeciesFrequencyTableModel) getTable().getModel();
266     }
267 
268     @Override
269     public JXTable getTable() {
270         return ui.getTable();
271     }
272 
273     @Override
274     public boolean isRowValid(SpeciesFrequencyRowModel row) {
275 
276         SpeciesFrequencyUIModel model = getModel();
277         return model.isRowValid(row);
278 
279     }
280 
281     @Override
282     public CaracteristicMapEditorUI getCaracteristicMapEditor() {
283         return ui.getObsCaracteristicCaracteristicMapEditor();
284     }
285 
286     @Override
287     public void showCaracteristicMapEditor(CaracteristicMapColumnRowModel editRow) {
288         IndividualObservationBatchRowModel row = (IndividualObservationBatchRowModel) editRow;
289         WeightUnit individualObservationWeightUnit = getConfig().getIndividualObservationWeightUnit();
290         String title = String.format("<html><body style='color:black;'><strong>%s - %s %s - %s %s</strong> - %s</body></html>",
291                                      row.getRankOrder(),
292                                      row.getSize(),
293                                      getModel().getLengthStepCaracteristicUnit(),
294                                      individualObservationWeightUnit.renderWeight(row.getWeight()),
295                                      individualObservationWeightUnit.getShortLabel(),
296                                      t("tutti.editIndividualObservationBatch.table.header.otherCaracteristics"));
297         ui.getObsCaracteristicMapEditorReminderLabel().setTitle(title);
298         ui.getObsPanelLayout().setSelected(EDIT_CARACTERISTICS_CARD);
299 
300     }
301 
302     @Override
303     public void hideCaracteristicMapEditor() {
304         ui.getObsPanelLayout().setSelected(OBS_TABLE_CARD);
305     }
306 
307     @Override
308     protected void onModelRowsChanged(List<SpeciesFrequencyRowModel> rows) {
309 
310         SpeciesFrequencyUIModel model = getModel();
311 
312         model.reloadRows();
313 
314         getTableModel().setRows(rows);
315 
316         // clean log table
317         SpeciesFrequencyLogsTableModel logsTableModel = (SpeciesFrequencyLogsTableModel) ui.getLogsTable().getModel();
318         logsTableModel.setRows(new ArrayList<>());
319 
320         getModel().setModify(false);
321 
322     }
323 
324     @Override
325     protected void onRowModified(int rowIndex,
326                                  SpeciesFrequencyRowModel row,
327                                  String propertyName,
328                                  Object oldValue,
329                                  Object newValue) {
330 
331         // We do nothing here. This API works only on the selected row.
332         // On this screen, we can interacts with not selected row, so won't come here.
333         // Better then to work directly on rows in the table model
334     }
335 
336     @Override
337     protected void saveSelectedRowIfRequired(TuttiBeanMonitor<SpeciesFrequencyRowModel> rowMonitor,
338                                              SpeciesFrequencyRowModel row) {
339     }
340 
341     @Override
342     protected void onRowValidStateChanged(int rowIndex,
343                                           SpeciesFrequencyRowModel row,
344                                           Boolean oldValue,
345                                           Boolean newValue) {
346         super.onRowValidStateChanged(rowIndex, row, oldValue, newValue);
347         ui.getValidator().doValidate();
348     }
349 
350     //------------------------------------------------------------------------//
351     //-- AbstractTuttiUIHandler methods                                     --//
352     //------------------------------------------------------------------------//
353 
354     @Override
355     public SwingValidator<SpeciesFrequencyUIModel> getValidator() {
356         return ui.getValidator();
357     }
358 
359     @Override
360     public void beforeInit(SpeciesFrequencyUI ui) {
361 
362         super.beforeInit(ui);
363 
364         this.speciesOrBenthosBatchUISupport = ui.getContextValue(SpeciesOrBenthosBatchUISupport.class, ui.getSpeciesOrBenthosContext());
365 
366         this.weightUnit = speciesOrBenthosBatchUISupport.getWeightUnit();
367         this.caracteristicDecorator = getDecorator(Caracteristic.class, DecoratorService.CARACTERISTIC_PARAMETER_ONLY_WITH_UNIT);
368         this.caracteristicTipDecorator = getDecorator(Caracteristic.class, DecoratorService.CARACTERISTIC_WITH_UNIT);
369         this.caracteristicQualitativeDecorator = getDecorator(CaracteristicQualitativeValue.class, null);
370         this.soundEngine = getContext().getSoundEngine();
371 
372         Caracteristic sexCaracteristic = getPersistenceService().getSexCaracteristic();
373 
374         TuttiDataContext dataContext = getDataContext();
375         SampleCategoryModel sampleCategoryModel = dataContext.getSampleCategoryModel();
376 
377         // get the default caracteristics from protocol
378         List<Caracteristic> protocolIndividualObservationCaracteristics = new ArrayList<>(dataContext.getDefaultIndividualObservationCaracteristics());
379 
380         Optional<CruiseCache> optionalCruiseCache = dataContext.getOptionalCruiseCache();
381         if (!optionalCruiseCache.isPresent()) {
382             // fixes https://forge.codelutin.com/issues/8402
383             // Est-ce vraiment la meilleur place pour ce code ? ne doit-on pas factoriser ?
384 //            throw new IllegalStateException("Can't find cruise cache");
385             TuttiUIContext uiContext = getContext();
386             ProgressionModel progressionModel = new ProgressionModel(); //(ProgressionModel)uiContext.getActionUI().getModel().getProgressionModel();
387             progressionModel.setTotal(1);
388 
389             CruiseCacheLoader cruiseCacheLoader = uiContext.createCruiseCacheLoader(progressionModel);
390             dataContext.loadCruiseCache(cruiseCacheLoader);
391             optionalCruiseCache = dataContext.getOptionalCruiseCache();
392             if(log.isDebugEnabled()) {
393                 log.debug("Cruise cache" + optionalCruiseCache.get());
394             }
395         }
396 
397         TuttiProtocol protocol = dataContext.isProtocolFilled() ? dataContext.getProtocol() : null;
398 
399         SpeciesFrequencyUIModel model = new SpeciesFrequencyUIModel(speciesOrBenthosBatchUISupport,
400                                                                     getConfig().getIndividualObservationWeightUnit(),
401                                                                     sampleCategoryModel,
402                                                                     sexCaracteristic,
403                                                                     protocolIndividualObservationCaracteristics,
404                                                                     optionalCruiseCache.get(),
405                                                                     dataContext.getCruiseId(),
406                                                                     protocol);
407 
408         this.ui.setContextValue(model);
409 
410         // listen when ichtyometer is connected or not and adjust the listener
411         getContext().addPropertyChangeListener(TuttiUIContext.PROPERTY_ICHTYOMETER_CONNECTED, listenIchtyomerIsConnected);
412 
413         // listen when caliper is connected or not and adjust the listener
414         getContext().addPropertyChangeListener(TuttiUIContext.PROPERTY_CALIPER_CONNECTED, listenCaliperIsConnected);
415 
416         // listen when fishing operation reloaded in data context to propagate it to our model
417         getDataContext().addPropertyChangeListener(TuttiDataContext.PROPERTY_FISHING_OPERATION_ID, listenFishingOperationReloadInDataContext);
418 
419     }
420 
421     @Override
422     public void afterInit(SpeciesFrequencyUI ui) {
423 
424         // add short cut to menu item
425         getUI().getSearchSpeciesInPrevCatchesButton().setAccelerator(
426                 KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.CTRL_MASK));
427         getUI().getSearchSpeciesInNextCatchesButton().setAccelerator(
428                 KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
429 
430         applySpeciesFrequencyRafaleAction = new ApplySpeciesFrequencyRafaleAction(ui);
431 
432         initUI(ui);
433 
434         List<Caracteristic> lengthStepCaracteristics = new ArrayList<>(getDataContext().getLengthStepCaracteristics());
435 
436         this.lengthStepCaracteristics = TuttiEntities.splitById(lengthStepCaracteristics);
437 
438         SpeciesFrequencyUIModel model = getModel();
439 
440         // poussin 20161230 fixes bug #8674, add if to get right cache
441         if (SpeciesOrBenthosBatchUISupport.SPECIES.equals(ui.getSpeciesOrBenthosContext())) {
442             taxonCache = TaxonCaches.createSpeciesCacheWithoutVernacularCode(getPersistenceService(), getDataContext().getProtocol());
443         } else {
444             taxonCache = TaxonCaches.createBenthosCacheWithoutVernacularCode(getPersistenceService(), getDataContext().getProtocol());
445         }
446 
447         Caracteristic modelCaracteristic = model.getLengthStepCaracteristic();
448         initBeanFilterableComboBox(this.ui.getLengthStepCaracteristicComboBox(),
449                                    lengthStepCaracteristics,
450                                    modelCaracteristic);
451 
452         // get step from the pmfm
453         model.setStep(modelCaracteristic);
454 
455         model.setMinStep(null);
456         model.setMaxStep(null);
457 
458         ui.getRafaleStepField().getTextField().addKeyListener(new KeyAdapter() {
459 
460             @Override
461             public void keyReleased(KeyEvent e) {
462                 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
463                     e.consume();
464                     Float step = (Float) SpeciesFrequencyUIHandler.this.ui.getRafaleStepField().getModel().getNumberValue();
465 
466                     applySpeciesFrequencyRafaleAction.applyRafaleStep(step, false);
467 
468                     //select text
469                     JTextField field = (JTextField) e.getSource();
470                     field.selectAll();
471                 }
472             }
473         });
474 
475         // when lengthStepCaracteristic changed, let's updates all row with the new value
476         model.addPropertyChangeListener(SpeciesFrequencyUIModel.PROPERTY_LENGTH_STEP_CARACTERISTIC, evt -> {
477             Caracteristic newValue = (Caracteristic) evt.getNewValue();
478             // get step from the pmfm
479             model.setStep(newValue);
480 
481             if (CollectionUtils.isNotEmpty(getModel().getRows())) {
482                 for (SpeciesFrequencyRowModel rowModel : getModel().getRows()) {
483                     rowModel.setLengthStepCaracteristic(newValue);
484                     recomputeRowValidState(rowModel);
485                 }
486             }
487 
488             if (!model.isInitBatchEdition()) {
489 
490                 // on valide uniquement si on est pas en mode de chargement de l'écran
491                 SpeciesFrequencyUIHandler.this.ui.getValidator().doValidate();
492             }
493 
494 //            getObsTableModel().setLengthstepCaracteristic(newValue);
495         });
496 
497         // when lengthStepCaracteristicUnit changed, let's updates the label of some fields
498         model.addPropertyChangeListener(SpeciesFrequencyUIModel.PROPERTY_LENGTH_STEP_CARACTERISTIC_UNIT, evt -> {
499 
500             String unit = (String) evt.getNewValue();
501 
502             if (unit == null) {
503 
504                 unit = t("tutti.editSpeciesFrequencies.unkownStepUnit");
505             }
506 
507             getUI().getMinStepLabel().setText(Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.field.minStep"), unit));
508             getUI().getMinStepLabel().setToolTipText(Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.field.minStep.tip"), unit));
509 
510             getUI().getMaxStepLabel().setText(Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.field.maxStep"), unit));
511             getUI().getMaxStepLabel().setToolTipText(Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.field.maxStep.tip"), unit));
512 
513             getUI().getRafaleStepLabel().setText(Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.field.rafaleStep"), unit));
514             getUI().getRafaleStepLabel().setToolTipText(Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.field.rafaleStep.tip"), unit));
515 
516             TableColumnExt column = (TableColumnExt) getUI().getTable().getColumn(SpeciesFrequencyTableModel.LENGTH_STEP);
517             String lengthStepLabelWithUnit = Units.getLabelWithUnit(t("tutti.editSpeciesFrequencies.table.header.lengthStep"), unit);
518             column.setHeaderValue(lengthStepLabelWithUnit);
519             column.setToolTipText(lengthStepLabelWithUnit);
520 
521             column = (TableColumnExt) getUI().getObsTable().getColumn(IndividualObservationBatchTableModel.SIZE);
522             column.setHeaderValue(Units.getLabelWithUnit(t("tutti.editIndividualObservationBatch.table.header.size"), unit));
523             column.setToolTipText(Units.getLabelWithUnit(t("tutti.editIndividualObservationBatch.table.header.size"), unit));
524 
525         });
526 
527         // when configuration mode change, let's focus the best component (see http://forge.codelutin.com/issues/4035)
528         model.addPropertyChangeListener(SpeciesFrequencyUIModel.PROPERTY_FREQUENCIES_CONFIGURATION_MODE, evt -> {
529             FrequencyConfigurationMode newValue = (FrequencyConfigurationMode) evt.getNewValue();
530             SwingUtilities.invokeLater(
531                     () -> {
532                         JComponent componentToFocus = getComponentToFocus(newValue);
533                         if (componentToFocus != null) {
534                             componentToFocus.grabFocus();
535                         }
536                         updateLogVisibility();
537                     }
538             );
539         });
540 
541         // Pour bloquer le changement de copie des poids rtp s'il y a deja des poids
542         model.addVetoableChangeListener(SpeciesFrequencyUIModel.PROPERTY_COPY_RTP_WEIGHTS, evt -> {
543 
544             boolean newCopyRtpWeights = (boolean) evt.getNewValue();
545 
546             if (newCopyRtpWeights) {
547 
548                 long rowsWithUserData = model.getRows().stream().filter(SpeciesFrequencyRowModel::withWeight).count();
549 
550                 if (rowsWithUserData > 0) {
551 
552                     String htmlMessage = String.format(
553                             CONFIRMATION_FORMAT,
554                             t("tutti.editSpeciesFrequencies.changeCopyRtpWeights.confirm.message"),
555                             t("tutti.editSpeciesFrequencies.changeCopyRtpWeights.confirm.help"));
556                     int i = JOptionPane.showConfirmDialog(
557                             getTopestUI(),
558                             htmlMessage,
559                             t("tutti.editSpeciesFrequencies.changeCopyRtpWeights.confirm.title"),
560                             JOptionPane.OK_CANCEL_OPTION,
561                             JOptionPane.QUESTION_MESSAGE);
562 
563                     if (i == JOptionPane.CANCEL_OPTION) {
564                         throw new PropertyVetoException("The user does not want to erase his data.", evt);
565                     }
566                 }
567             }
568         });
569 
570         model.addPropertyChangeListener(SpeciesFrequencyUIModel.PROPERTY_COPY_RTP_WEIGHTS, evt -> {
571             SpeciesFrequencyUIModel source = (SpeciesFrequencyUIModel) evt.getSource();
572             source.getRows().forEach(source::computeRowWeightWithRtp);
573             source.reloadRows();
574             source.getFrequencyTableModel().fireTableDataChanged();
575         });
576 
577         // si le tableau des observations est en erreur, on recalcule si il existe une ligne non vide en erreur
578         model.getIndividualObservationModel().addPropertyChangeListener(IndividualObservationBatchUIModel.PROPERTY_ROWS_IN_ERROR, evt -> {
579             IndividualObservationBatchUIModel source = (IndividualObservationBatchUIModel) evt.getSource();
580             if (source.getRows() != null) {
581                 boolean nonEmptyRow = source.isNonEmptyRowInError();
582                 model.setNonEmptyIndividualObservationRowsInError(nonEmptyRow);
583             }
584         });
585 
586         // set the pattern to the weight in simple counting mode according to the weight unit
587 
588         ui.getSimpleCountingWeightField().setNumberPattern(weightUnit.getNumberEditorPattern());
589 
590         initDataTable();
591 
592         initLogTable();
593 
594         this.individualObservationBatchTableHandler = new IndividualObservationBatchTableHandler(ui);
595         this.averageWeightsHistogramHandler = new AverageWeightsHistogramHandler(ui);
596         this.frequenciesHistogramHandler = new FrequenciesHistogramHandler(ui);
597         this.samplingNotificationZoneHandler = new SamplingNotificationZoneHandler(ui,
598                                                                                    model.getIndividualObservationModel().getSamplingNotificationZoneModel(),
599                                                                                    model.getIndividualObservationUICache());
600 
601         listenValidatorValid(ui.getValidator(), model);
602 
603         final Color bgSaveBt = ui.getSaveAndStayButton().getBackground();
604         final boolean opacity = ui.getSaveAndStayButton().isOpaque();
605         model.addPropertyChangeListener(SpeciesFrequencyUIModel.PROPERTY_MODIFY, evt -> {
606             if (Boolean.FALSE.equals(evt.getNewValue())) {
607                 ui.getSaveAndStayButton().setOpaque(opacity);
608                 ui.getSaveAndStayButton().setBackground(bgSaveBt);
609             } else {
610                 ui.getSaveAndStayButton().setOpaque(true);
611                 ui.getSaveAndStayButton().setBackground(Color.RED);
612             }
613         });
614 
615    }
616 
617     @Override
618     protected JComponent getComponentToFocus() {
619         FrequencyConfigurationMode configurationMode = getModel().getConfigurationMode();
620         JComponent componentToFocus = getComponentToFocus(configurationMode);
621         if (componentToFocus == null) {
622             componentToFocus = getUI().getLengthStepCaracteristicComboBox();
623         }
624         return componentToFocus;
625     }
626 
627     @Override
628     public void onCloseUI() {
629         if (log.isDebugEnabled()) {
630             log.debug("closing: " + ui);
631         }
632 
633         getDataContext().removePropertyChangeListener(TuttiDataContext.PROPERTY_FISHING_OPERATION_ID, listenFishingOperationReloadInDataContext);
634 
635         TuttiUIContext context = getContext();
636         context.removePropertyChangeListener(TuttiUIContext.PROPERTY_ICHTYOMETER_CONNECTED, listenIchtyomerIsConnected);
637         context.removePropertyChangeListener(TuttiUIContext.PROPERTY_CALIPER_CONNECTED, listenCaliperIsConnected);
638 
639         if (context.isIchtyometerConnected()) {
640 
641             context.getIchtyometerReader().removeFeedModeReaderListener(ichtyometerFeedReaderListener);
642         }
643 
644         if (context.isCaliperConnected()) {
645 
646             context.getCaliperReader().removeFeedModeReaderListener(caliperFeedReaderListener);
647         }
648 
649         SpeciesFrequencyUIModel model = getModel();
650 
651         IOUtils.closeQuietly(individualObservationBatchTableHandler);
652         IOUtils.closeQuietly(averageWeightsHistogramHandler);
653         IOUtils.closeQuietly(frequenciesHistogramHandler);
654         IOUtils.closeQuietly(samplingNotificationZoneHandler);
655 
656         frequencyEditor = null;
657 
658         // evict model from validator
659         ui.getValidator().setBean(null);
660         clearValidators();
661 
662         // when canceling always invalid model (in that way)
663         model.setValid(false);
664         model.setSimpleCount(null);
665         model.setModify(false);
666 
667         EditSpeciesBatchPanelUI parent = getParentContainer(EditSpeciesBatchPanelUI.class);
668         parent.switchToEditBatch();
669 
670     }
671 
672     public void editBatch(FrequencyCellEditor editor, String title) {
673 
674         SpeciesBatchRowModel speciesBatch = editor.getEditRow();
675         Objects.requireNonNull(speciesBatch, "Impossible d'éditer un lot non renseigné");
676 
677         SpeciesFrequencyUIModel model = getModel();
678 
679         Objects.requireNonNull(title, "title can't be null here ?!");
680 
681         model.getAverageWeightsHistogramModel().setTitle(title);
682         model.getFrequenciesHistogramModel().setTitle(title);
683 
684         frequencyEditor = editor;
685 
686         model.setNextEditableRowIndex(frequencyEditor.getNextEditableRowIndex());
687 
688         List<SpeciesFrequencyRowModel> frequency = speciesBatch.getFrequency();
689         List<IndividualObservationBatchRowModel> individualObservations = speciesBatch.getIndividualObservation();
690 
691         // keep batch (will be used to push back editing entry)
692         model.setBatch(speciesBatch);
693         model.setFishingOperation(getDataContext().getFishingOperation());
694         model.setMinStep(null);
695         model.setMaxStep(null);
696         model.setRtp(null);
697         model.setCopyRtpWeights(false);
698 
699         // get species from protocol
700         SpeciesProtocol speciesProtocol = getDataContext().isProtocolFilled() ? speciesOrBenthosBatchUISupport.getSpeciesProtocol(speciesBatch.getSpecies()) : null;
701 
702         // set rtp
703         Rtp rtp = null;
704 
705         if (speciesProtocol != null) {
706 
707             Caracteristic sexCaracteristic = model.getIndividualObservationModel().getSexCaracteristic();
708             CaracteristicQualitativeValue sampleCategoryValue = (CaracteristicQualitativeValue) speciesBatch.getSampleCategoryValue(sexCaracteristic.getIdAsInt());
709 
710             if (sampleCategoryValue != null) {
711 
712                 if (Sexs.isMale(sampleCategoryValue)) {
713                     rtp = speciesProtocol.getRtpMale();
714                 } else if (Sexs.isFemale(sampleCategoryValue)) {
715                     rtp = speciesProtocol.getRtpFemale();
716                 } else {
717                     rtp = speciesProtocol.getRtpUndefined();
718                 }
719 
720             } else {
721                 rtp = speciesProtocol.getRtpUndefined();
722             }
723 
724         }
725 
726         model.setRtp(rtp);
727 
728         individualObservationBatchTableHandler.initMaturityCaracteristic(speciesProtocol);
729         individualObservationBatchTableHandler.initDefaultCaracteristics(speciesBatch);
730 
731         model.getIndividualObservationUICache().initFishingOperation(model.getFishingOperation());
732 
733         loadFrequenciesAndObservations(frequency, individualObservations, false);
734 
735         samplingNotificationZoneHandler.editBatch(speciesBatch);
736 
737         if (getContext().isIchtyometerConnected()) {
738 
739             // let's listen the ichtyometer
740             listenItchtyometer();
741 
742         }
743 
744         if (getContext().isCaliperConnected()) {
745 
746             // let's listen the caliper
747             listenCaliper();
748 
749         }
750 
751         model.setModify(false);
752 
753     }
754 
755     public boolean leaveIfConfirmed() {
756 
757         boolean result = !getModel().isModify() || askCancelEditBeforeLeaving();
758 
759         if (result) {
760 
761             // Cancel edit screen
762             ApplicationUIAction action = (ApplicationUIAction) getUI().getCancelButton().getAction();
763             getContext().getActionEngine().runInternalAction(action.getLogicAction());
764 
765         } else {
766 
767             // Use cancel this operation
768             if (log.isInfoEnabled()) {
769                 log.info("Use cancel change on tab, stay on frequencies screen.");
770             }
771 
772         }
773 
774         return result;
775 
776     }
777 
778     public boolean askCancelEditBeforeLeaving() {
779         // Ask confirmation to quit screen
780         String htmlMessage = String.format(AbstractApplicationUIHandler.CONFIRMATION_FORMAT,
781                                            t("tutti.askToCancelEditFrequencies.message"),
782                                            t("tutti.askToCancelEditFrequencies.help"));
783 
784         int saveResponse = JOptionPane.showOptionDialog(getTopestUI(),
785                                                         htmlMessage,
786                                                         t("tutti.askToCancelEditFrequencies.title"),
787                                                         JOptionPane.OK_CANCEL_OPTION,
788                                                         JOptionPane.QUESTION_MESSAGE,
789                                                         null,
790                                                         new String[]{t("tutti.option.continue"), t("tutti.option.cancel")},
791                                                         t("tutti.option.cancel"));
792 
793         return saveResponse == 0;
794     }
795 
796     public void setCopyIndividualObservationMode(CopyIndividualObservationMode newCopyMode) {
797 
798         SpeciesFrequencyUIModel model = getModel();
799 
800         if (model.isInitBatchEdition()) {
801 
802             if (log.isInfoEnabled()) {
803                 log.info("Skip ask user to confirm copyIndividualObservationMode changed from " + model.getCopyIndividualObservationMode() + " to " + newCopyMode);
804             }
805             return;
806         }
807 
808         // le seul mode où l'utilisateur ne peut rien saisir est le mode tout
809         if (!model.isCopyIndividualObservationAll()) {
810 
811             long rowsWithUserData;
812 
813             // si on etait en mode taille et que l'utilisateur avait saisi des tailles
814             if (model.isCopyIndividualObservationSize()) {
815                 rowsWithUserData = model.getRows().stream()
816                                         .filter(SpeciesFrequencyRowModel::withWeight)
817                                         .count();
818 
819             } else {
820                 rowsWithUserData = model.getRows().stream()
821                                         .filter(row -> row.withLengthStep() || row.withNumber() || row.withWeight())
822                                         .count();
823             }
824 
825             if (rowsWithUserData > 0) {
826 
827                 String htmlMessage = String.format(
828                         CONFIRMATION_FORMAT,
829                         t("tutti.editSpeciesFrequencies.changeCopyMode.confirm.message"),
830                         t("tutti.editSpeciesFrequencies.changeCopyMode.confirm.help"));
831                 int i = JOptionPane.showConfirmDialog(
832                         ui.getHandler().getTopestUI(),
833                         htmlMessage,
834                         t("tutti.editSpeciesFrequencies.changeCopyMode.confirm.title"),
835                         JOptionPane.OK_CANCEL_OPTION,
836                         JOptionPane.QUESTION_MESSAGE);
837 
838                 if (i == JOptionPane.CANCEL_OPTION) {
839                     if (log.isDebugEnabled()) {
840                         log.debug("User cancel modification...");
841                     }
842 
843                     // on repositionne sur le bon radio-bouton
844                     // le code n'est pas optimal mais est moins dangeureux que de relancer des fires je pense
845                     switch (model.getCopyIndividualObservationMode()) {
846                         case ALL:
847                             ui.getCopyAllButton().setSelected(true);
848                             break;
849                         case NOTHING:
850                             ui.getCopyNothingButton().setSelected(true);
851                             break;
852                         case SIZE:
853                             ui.getCopySizesButton().setSelected(true);
854                             break;
855                     }
856 
857                     return;
858                 }
859             }
860 
861         }
862 
863         model.setCopyIndividualObservationMode(newCopyMode);
864     }
865 
866     //------------------------------------------------------------------------//
867     //-- Internal methods                                                   --//
868     //------------------------------------------------------------------------//
869 
870     protected void initDataTable() {
871         JXTable table = getTable();
872 
873         // create table column model
874         DefaultTableColumnModelExt columnModel = new DefaultTableColumnModelExt();
875 
876         { // LengthStep
877 
878             addFloatColumnToModel(columnModel,
879                                   SpeciesFrequencyTableModel.LENGTH_STEP,
880                                   TuttiUI.DECIMAL1_PATTERN,
881                                   table);
882         }
883 
884         { // Number
885 
886             addIntegerColumnToModel(columnModel,
887                                     SpeciesFrequencyTableModel.NUMBER,
888                                     TuttiUI.INT_6_DIGITS_PATTERN,
889                                     table);
890         }
891 
892         { // Weight
893             addFloatColumnToModel(columnModel, SpeciesFrequencyTableModel.WEIGHT, weightUnit, table);
894         }
895 
896         { // RTP computed Weight
897 
898             Color computedDataColor = getConfig().getColorComputedWeights();
899             TableCellRenderer renderer = new TableCellRenderer() {
900 
901                 TableCellRenderer delegate = newWeightCellRenderer(table.getDefaultRenderer(Number.class), weightUnit);
902 
903                 @Override
904                 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
905                     Component result = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
906                     if (result instanceof JLabel) {
907                         JLabel jLabel = (JLabel) result;
908                         jLabel.setForeground(computedDataColor);
909                         jLabel.setFont(jLabel.getFont().deriveFont(Font.ITALIC));
910                     }
911                     return result;
912                 }
913             };
914 
915             addColumnToModel(columnModel,
916                              null,
917                              renderer,
918                              SpeciesFrequencyTableModel.RTP_COMPUTED_WEIGHT,
919                              weightUnit);
920         }
921 
922         // create table model
923         SpeciesFrequencyTableModel tableModel = new SpeciesFrequencyTableModel(weightUnit,
924                                                                                getConfig().getIndividualObservationWeightUnit(),
925                                                                                columnModel,
926                                                                                getModel());
927         getModel().setFrequencyTableModel(tableModel);
928 
929         table.setModel(tableModel);
930         table.setColumnModel(columnModel);
931 
932         initTable(table);
933 
934         installTableKeyListener(columnModel, table);
935     }
936 
937     @Override
938     protected void addHighlighters(final JXTable table) {
939 
940         super.addHighlighters(table);
941 
942         HighlightPredicate notSelectedPredicate = new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED);
943         HighlightPredicate weightTooDifferentFromRtpPredicate = (Component renderer, ComponentAdapter adapter) -> {
944 
945             boolean result = false;
946             if (table.getModel() instanceof SpeciesFrequencyTableModel) {
947 
948                 SpeciesFrequencyTableModel tableModel = (SpeciesFrequencyTableModel) table.getModel();
949                 int viewRow = adapter.row;
950                 int modelRow = adapter.convertRowIndexToModel(viewRow);
951                 SpeciesFrequencyRowModel row = tableModel.getEntry(modelRow);
952 
953                 Float rate = getConfig().getDifferenceRateBetweenWeightAndRtpWeight();
954                 result = row.withWeight() && row.withRtpComputedWeight()
955                         && Math.abs(row.getWeight() - row.getRtpComputedWeight()) >= row.getWeight() * rate / 100;
956             }
957 
958             return result;
959         };
960 
961         // paint in a special color rows with weight in warning (not selected)
962         Highlighter weightTooDifferentFromRtpHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
963                 new HighlightPredicate.AndHighlightPredicate(
964                         notSelectedPredicate,
965                         weightTooDifferentFromRtpPredicate),
966                 getConfig().getColorWarningRow());
967         table.addHighlighter(weightTooDifferentFromRtpHighlighter);
968 
969         // paint in a special color rows with weight in warning (selected)
970         Highlighter weightTooDifferentFromRtpSelectedHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
971                 new HighlightPredicate.AndHighlightPredicate(
972                         HighlightPredicate.IS_SELECTED,
973                         weightTooDifferentFromRtpPredicate),
974                 getConfig().getColorWarningRow().darker());
975         table.addHighlighter(weightTooDifferentFromRtpSelectedHighlighter);
976 
977     }
978 
979     protected void initLogTable() {
980         JXTable logTable = ui.getLogsTable();
981 
982         // create log table column model
983         DefaultTableColumnModelExt logColumnModel = new DefaultTableColumnModelExt();
984 
985         { // Date
986             addColumnToModel(logColumnModel,
987                              SpeciesFrequencyLogCellComponent.newEditor(ui),
988                              SpeciesFrequencyLogCellComponent.newRender(),
989                              SpeciesFrequencyLogsTableModel.LABEL);
990         }
991 
992         // create log table model
993         SpeciesFrequencyLogsTableModel logTableModel = new SpeciesFrequencyLogsTableModel(logColumnModel);
994         logTableModel.setRows(new ArrayList<>());
995 
996         logTable.setModel(logTableModel);
997         logTable.setColumnModel(logColumnModel);
998 
999         // by default do not authorize to change column orders
1000         logTable.getTableHeader().setReorderingAllowed(false);
1001         Highlighter evenHighlighter = TuttiUIUtil.newBackgroundColorHighlighter(
1002                 HighlightPredicate.ODD,
1003                 getConfig().getColorAlternateRow());
1004         logTable.addHighlighter(evenHighlighter);
1005     }
1006 
1007     protected CaracteristicColumnIdentifier<IndividualObservationBatchRowModel> addCaracteristicColumnToModel(JXTable table,
1008                                                                                                               TableColumnModelExt columnModel,
1009                                                                                                               Caracteristic caracteristic) {
1010 
1011         String header = caracteristicDecorator.toString(caracteristic);
1012         String headerTip = caracteristicTipDecorator.toString(caracteristic);
1013 
1014         CaracteristicColumnIdentifier<IndividualObservationBatchRowModel> id = CaracteristicColumnIdentifier.newCaracteristicId(
1015                 caracteristic,
1016                 IndividualObservationBatchRowModel.PROPERTY_DEFAULT_CARACTERISTICS,
1017                 header,
1018                 headerTip
1019         );
1020 
1021         if (columnModel.getColumnExt(id) == null) {
1022 
1023             switch (caracteristic.getCaracteristicType()) {
1024 
1025                 case NUMBER:
1026 
1027                     addFloatColumnToModel(columnModel,
1028                                           id,
1029                                           TuttiUI.DECIMAL3_PATTERN,
1030                                           table);
1031 
1032                     break;
1033                 case QUALITATIVE:
1034                     List<CaracteristicQualitativeValue> values = caracteristic.getQualitativeValue();
1035                     addComboDataColumnToModel(columnModel,
1036                                               id,
1037                                               caracteristicQualitativeDecorator,
1038                                               values);
1039                     break;
1040                 case TEXT:
1041 
1042                     addColumnToModel(columnModel, id);
1043 
1044                     break;
1045             }
1046         }
1047 
1048         return id;
1049     }
1050 
1051     // override la méthode pour mettre en index de modèle l'index après la dernière colonne cachée
1052     @Override
1053     protected <R> TableColumnExt addColumnToModel(TableColumnModel model,
1054                                                   TableCellEditor editor,
1055                                                   TableCellRenderer renderer,
1056                                                   ColumnIdentifier<R> identifier) {
1057 
1058         //TODO à remonter dans jaxx
1059         TableColumnExt col = new TableColumnExt(((DefaultTableColumnModelExt) model).getColumnCount(true));
1060         col.setCellEditor(editor);
1061         col.setCellRenderer(renderer);
1062         String label = t(identifier.getHeaderI18nKey());
1063 
1064         col.setHeaderValue(label);
1065         String tip = t(identifier.getHeaderTipI18nKey());
1066 
1067         col.setToolTipText(tip);
1068 
1069         col.setIdentifier(identifier);
1070         model.addColumn(col);
1071         // by default no column is sortable, must specify it
1072         col.setSortable(false);
1073         return col;
1074     }
1075 
1076     protected void consumeIchtyometerFeedRecord(IchtyometerFeedReaderMeasureRecord record) {
1077         if (record.isValid()) {
1078 
1079             float length = getModel().convertFromMm(record.getMeasure());
1080 //            String unit = getModel().getLengthStepCaracteristicUnit();
1081 
1082 //            // board measurements are in mm
1083 //
1084 //            float length;
1085 //
1086 //            if ("mm".equals(unit)) {
1087 //
1088 //                // measurement in mm asked
1089 //                length = record.getMeasure();
1090 //
1091 //            } else {
1092 //
1093 //                // measurement in cm asked
1094 //                length = record.getMeasure() / 10f;
1095 //
1096 //            }
1097 
1098             applySpeciesFrequencyRafaleAction.applyRafaleStep(length, true);
1099 
1100         } else {
1101 
1102             soundEngine.beepOnExternalDeviceErrorReception();
1103 
1104             throw new ApplicationBusinessException(t("tutti.editSpeciesFrequencies.error.itchyometer.bad.record", record.getRecord()));
1105         }
1106     }
1107 
1108     protected void consumeCaliperFeedRecord(CaliperFeedReaderMeasureRecord record) {
1109         if (record.isValid()) {
1110 
1111             float length = getModel().convertFromMm(record.getMeasure());
1112 
1113 //            String unit = getModel().getLengthStepCaracteristicUnit();
1114 //
1115 //            // board measurements are in mm
1116 //
1117 //            float length;
1118 //
1119 //            if ("mm".equals(unit)) {
1120 //
1121 //                // measurement in mm asked
1122 //                length = record.getMeasure();
1123 //
1124 //            } else {
1125 //
1126 //                // measurement in cm asked
1127 //                length = record.getMeasure() / 10f;
1128 //
1129 //            }
1130 
1131             applySpeciesFrequencyRafaleAction.applyRafaleStep(length, true);
1132 
1133         } else {
1134 
1135             soundEngine.beepOnExternalDeviceErrorReception();
1136 
1137             throw new ApplicationBusinessException(t("tutti.editSpeciesFrequencies.error.caliper.bad.record", record.getRecord()));
1138         }
1139     }
1140 
1141     protected void listenItchtyometer() {
1142 
1143         // always remove the listener before adding it to be sure it will not be there twice
1144         getContext().getIchtyometerReader().removeAllFeedModeReaderListeners();
1145         if (log.isInfoEnabled()) {
1146             log.info("Start listen ichtyometer");
1147         }
1148         getContext().getIchtyometerReader().addFeedModeReaderListener(ichtyometerFeedReaderListener);
1149     }
1150 
1151     protected void listenCaliper() {
1152 
1153         // always remove the listener before adding it to be sure it will not be there twice
1154         getContext().getCaliperReader().removeAllFeedModeReaderListeners();
1155         if (log.isInfoEnabled()) {
1156             log.info("Start listen caliper");
1157         }
1158         getContext().getCaliperReader().addFeedModeReaderListener(caliperFeedReaderListener);
1159     }
1160 
1161     protected JComponent getComponentToFocus(FrequencyConfigurationMode mode) {
1162         JComponent componentToFocus = null;
1163         if (mode != null) {
1164             boolean withLengthStepCaracteristic =
1165                     getModel().getLengthStepCaracteristic() != null;
1166             switch (mode) {
1167                 case AUTO_GEN:
1168                     if (withLengthStepCaracteristic) {
1169 
1170                         componentToFocus = ui.getMinStepField();
1171                     } else {
1172                         componentToFocus = ui.getLengthStepCaracteristicComboBox();
1173                     }
1174                     break;
1175                 case RAFALE:
1176 
1177                     if (withLengthStepCaracteristic) {
1178 
1179                         componentToFocus = ui.getRafaleStepField();
1180                     } else {
1181                         componentToFocus = ui.getLengthStepCaracteristicComboBox();
1182                     }
1183 
1184                     break;
1185                 case SIMPLE_COUNTING:
1186                     componentToFocus = ui.getSimpleCountingNumberField();
1187                     break;
1188                 default:
1189                     componentToFocus = null;
1190             }
1191         }
1192         return componentToFocus;
1193     }
1194 
1195     protected void updateLogVisibility() {
1196 
1197         boolean logVisible = getModel().isRafaleMode() || getContext().isIchtyometerConnected() || getContext().isCaliperConnected();
1198         JSplitPane firstSplitPane = ui.getFirstSplitPane();
1199         JSplitPane secondSplitPane = ui.getSecondSplitPane();
1200 
1201         int lastDividerLocation = secondSplitPane.getLastDividerLocation();
1202         if (lastDividerLocation == 0) {
1203             lastDividerLocation = 200;
1204         }
1205         secondSplitPane.setDividerLocation(logVisible ? lastDividerLocation : 0);
1206         secondSplitPane.setDividerSize(logVisible ? firstSplitPane.getDividerSize() : 0);
1207 
1208         ui.getLogsScrollPane().setVisible(logVisible);
1209 
1210     }
1211 
1212     @Override
1213     public <E> void initBeanFilterableComboBox(BeanFilterableComboBox<E> comboBox, List<E> data, E selectedData) {
1214         super.initBeanFilterableComboBox(comboBox, data, selectedData);
1215     }
1216 
1217     public FrequencyCellEditor getFrequencyEditor() {
1218         return frequencyEditor;
1219     }
1220 
1221     @Override
1222     protected void beforeOpenPopup(int modelRowIndex, int modelColumnIndex) {
1223         super.beforeOpenPopup(modelRowIndex, modelColumnIndex);
1224 
1225         boolean sampleCodeMenusEnabled = individualObservationBatchTableHandler.isSampleCodeMenusEnabled(modelRowIndex);
1226 
1227         ui.getEditSampleCodeMenu().setEnabled(sampleCodeMenusEnabled);
1228         ui.getDeleteSampleCodeMenu().setEnabled(sampleCodeMenusEnabled);
1229     }
1230 
1231     public void loadFrequenciesAndObservations(List<SpeciesFrequencyRowModel> frequency, List<IndividualObservationBatchRowModel> individualObservations, boolean addToCache) {
1232 
1233         SpeciesFrequencyUIModel model = getModel();
1234         SpeciesBatchRowModel speciesBatch = model.getBatch();
1235 
1236         model.setInitBatchEdition(true);
1237 
1238         try {
1239 
1240             model.loadSpeciesBatch(speciesBatch);
1241 
1242             Species species = speciesBatch.getSpecies();
1243 
1244             // default to rafale see https://forge.codelutin.com/issues/8361
1245             model.setFrequenciesConfigurationMode(FrequencyConfigurationMode.RAFALE);
1246             // protocol value change addIndividualObservationOnRafale see https://forge.codelutin.com/issues/8361
1247             SpeciesProtocol speciesProtocol =
1248                     speciesOrBenthosBatchUISupport.getSpeciesProtocol(species);
1249             model.setAddIndividualObservationOnRafale(
1250                     speciesProtocol != null && speciesProtocol.isIndividualObservationEnabled());
1251 
1252             List<SpeciesFrequencyRowModel> frequencyRows = getTableModel().loadRows(frequency);
1253 
1254             List<IndividualObservationBatchRowModel> individualObservationRows = individualObservationBatchTableHandler.loadRows(species, individualObservations);
1255 
1256             if (log.isDebugEnabled()) {
1257                 log.debug("Will edit batch row: " + speciesBatch + " with " + frequencyRows.size() + " frequencies and " + individualObservationRows.size() + " individual observations.");
1258             }
1259 
1260             CopyIndividualObservationMode copyIndividualObservationMode =
1261                     computeCopyIndividualObservationMode(
1262                             model.isAddIndividualObservationOnRafale(),
1263                             frequencyRows, individualObservationRows);
1264 
1265             if (log.isInfoEnabled()) {
1266                 log.info("copyIndividualObservationMode: " + copyIndividualObservationMode);
1267             }
1268 
1269             Caracteristic lengthStepCaracteristic = computeLengthStepCaracteristic(species, frequencyRows, individualObservationRows);
1270 
1271             if (log.isInfoEnabled()) {
1272                 log.info("lengthStepCaracteristic: " + lengthStepCaracteristic);
1273             }
1274 
1275 //            model.setLengthStepCaracteristic(null);
1276             model.setLengthStepCaracteristic(lengthStepCaracteristic);
1277 
1278             FrequencyConfigurationMode configurationMode = model.guessFrequencyConfigurationMode();
1279 
1280             if (log.isInfoEnabled()) {
1281                 log.info("configurationMode: " + configurationMode);
1282             }
1283 
1284             // make sure configuration mode will be rebound
1285             model.setConfigurationMode(null);
1286             model.setConfigurationMode(configurationMode);
1287 
1288             model.setFrequenciesConfigurationMode(null);
1289             // default to RAFALE see https://forge.codelutin.com/issues/8361
1290             model.setFrequenciesConfigurationMode(FrequencyConfigurationMode.RAFALE);
1291 //            model.setFrequenciesConfigurationMode(FrequencyConfigurationMode.AUTO_GEN);
1292             
1293 
1294             // connect model to validator
1295             ui.getValidator().setBean(model);
1296 
1297             model.setRows(frequencyRows);
1298 
1299             // let's change the copy mode (mark it in init mode to avoid user change confirmation and some recomputations)
1300             model.setCopyIndividualObservationMode(null);
1301             model.setCopyIndividualObservationMode(copyIndividualObservationMode);
1302 
1303             // chargement des observations après le changement de mode de copie, pour que la validation des lignes prenne en compte le nouveau mode de copie
1304             individualObservationBatchTableHandler.loadSpeciesBatch(speciesBatch, individualObservationRows, addToCache);
1305 
1306             model.computeRowWeightWithRtp();
1307 
1308         } finally {
1309 
1310             model.setInitBatchEdition(false);
1311 
1312         }
1313     }
1314 
1315     // Attention on surcharge les méthodes suivantes pour pouvoir les utiliser dans l'autre handler, ne rien changer (pour le moment)...
1316 
1317     @Override
1318     protected <R> TableColumnExt addIntegerColumnToModel(TableColumnModel model,
1319                                                          ColumnIdentifier<R> identifier,
1320                                                          String numberPattern,
1321                                                          JTable table) {
1322         return super.addIntegerColumnToModel(model, identifier, numberPattern, table);
1323     }
1324 
1325     @Override
1326     protected <R> TableColumnExt addFloatColumnToModel(TableColumnModel model,
1327                                                        ColumnIdentifier<R> identifier,
1328                                                        String numberPattern,
1329                                                        JTable table) {
1330         return super.addFloatColumnToModel(model, identifier, numberPattern, table);
1331     }
1332 
1333     @Override
1334     protected <R> TableColumnExt addFloatColumnToModel(TableColumnModel model, ColumnIdentifier<R> identifier, WeightUnit weightUnit, JTable table) {
1335         return super.addFloatColumnToModel(model, identifier, weightUnit, table);
1336     }
1337 
1338     @Override
1339     protected <R, B> TableColumnExt addComboDataColumnToModel(TableColumnModel model,
1340                                                               ColumnIdentifier<R> identifier,
1341                                                               Decorator<B> decorator,
1342                                                               List<B> data) {
1343         return super.addComboDataColumnToModel(model, identifier, decorator, data);
1344     }
1345 
1346     @Override
1347     protected void installTableKeyListener(TableColumnModel columnModel, JTable table, boolean enterToChangeRow) {
1348         super.installTableKeyListener(columnModel, table, enterToChangeRow);
1349     }
1350 
1351     @Override
1352     protected String decorate(Serializable object, String context) {
1353         return super.decorate(object, context);
1354     }
1355 
1356     private CopyIndividualObservationMode computeCopyIndividualObservationMode(
1357             boolean isAddIndividualObservationOnRafale,
1358             List<SpeciesFrequencyRowModel> frequencyRows,
1359             List<IndividualObservationBatchRowModel> individualObservationRows) {
1360         CopyIndividualObservationMode result = CopyIndividualObservationMode.NOTHING;
1361         if (frequencyRows.isEmpty() && individualObservationRows.isEmpty()) {
1362                 // no data
1363                 if (isAddIndividualObservationOnRafale) {
1364                     result = CopyIndividualObservationMode.SIZE;
1365                 }
1366         } else if (!individualObservationRows.isEmpty()) {
1367             IndividualObservationBatchRowModel firstIndividualObservationRow =
1368                     individualObservationRows.get(0);
1369             result = firstIndividualObservationRow.getCopyIndividualObservationMode();
1370         }
1371         return result;
1372     }
1373 
1374     private Caracteristic computeLengthStepCaracteristic(Species species, List<SpeciesFrequencyRowModel> frequencyRows, List<IndividualObservationBatchRowModel> individualObservationRows) {
1375 
1376         Caracteristic lengthStepCaracteristic = null;
1377 
1378         if (!frequencyRows.isEmpty()) {
1379 
1380             SpeciesFrequencyRowModel firstFrequencyRow = frequencyRows.get(0);
1381             lengthStepCaracteristic = firstFrequencyRow.getLengthStepCaracteristic();
1382             if (log.isInfoEnabled()) {
1383                 log.info("Use existing lengthStep caracteristic / step from first existing frequency: " + decorate(lengthStepCaracteristic));
1384             }
1385 
1386         }
1387 
1388         if (lengthStepCaracteristic == null) {
1389 
1390             if (!individualObservationRows.isEmpty()) {
1391                 IndividualObservationBatchRowModel firstIndividualObservationRow = individualObservationRows.get(0);
1392 
1393                 lengthStepCaracteristic = firstIndividualObservationRow.getLengthStepCaracteristic();
1394 
1395                 if (log.isInfoEnabled()) {
1396                     log.info("Use existing lengthStep caracteristic / step from first individual observation : " + decorate(lengthStepCaracteristic));
1397                 }
1398             }
1399 
1400         }
1401 
1402         SpeciesBatchRowModel previousSiblingRow = frequencyEditor.getPreviousSiblingRow();
1403 
1404         if (lengthStepCaracteristic == null && previousSiblingRow != null) {
1405 
1406             // try to get it from his previous brother row
1407             List<SpeciesFrequencyRowModel> previousFrequency = previousSiblingRow.getFrequency();
1408 
1409             if (CollectionUtils.isNotEmpty(previousFrequency)) {
1410 
1411                 // use the first frequency length step caracteristic / step
1412                 SpeciesFrequencyRowModel rowModel = previousFrequency.get(0);
1413                 lengthStepCaracteristic = rowModel.getLengthStepCaracteristic();
1414                 if (log.isInfoEnabled()) {
1415                     log.info("Use previous sibling existing lengthStep caracteristic / step " + decorate(lengthStepCaracteristic));
1416                 }
1417             }
1418         }
1419 
1420         if (lengthStepCaracteristic == null) {
1421 
1422             if (taxonCache.containsLengthStepPmfmId(species)) {
1423 
1424                 String lengthStepPmfmId = taxonCache.getLengthStepPmfmId(species);
1425 
1426                 lengthStepCaracteristic = lengthStepCaracteristics.get(lengthStepPmfmId);
1427 
1428                 if (log.isInfoEnabled()) {
1429                     log.info("Use existing from protocol lengthStep caracteristic / step " + decorate(lengthStepCaracteristic));
1430                 }
1431             }
1432 
1433         }
1434 
1435         return lengthStepCaracteristic;
1436 
1437     }
1438 
1439 }