View Javadoc
1   package fr.ifremer.allegro.obsdeb.ui.swing.content.catches.treetable;
2   
3   /*
4    * #%L
5    * SIH Allegro ObsDeb :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2013 - 2014 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.base.Predicate;
27  import com.google.common.collect.Lists;
28  import fr.ifremer.allegro.obsdeb.decorator.ObsdebDecorator;
29  import fr.ifremer.allegro.obsdeb.dto.ObsdebEntities;
30  import fr.ifremer.allegro.obsdeb.dto.ObsdebEntity;
31  import fr.ifremer.allegro.obsdeb.dto.ObsdebEntityComparator;
32  import fr.ifremer.allegro.obsdeb.dto.data.fishingtrip.FishingOperationGroupDTO;
33  import fr.ifremer.allegro.obsdeb.dto.data.sales.SaleDTO;
34  import fr.ifremer.allegro.obsdeb.dto.referential.QualitativeValueDTO;
35  import fr.ifremer.allegro.obsdeb.dto.referential.TaxonGroupDTO;
36  import fr.ifremer.allegro.obsdeb.service.ObsdebDataContext;
37  import fr.ifremer.allegro.obsdeb.service.ObsdebTechnicalException;
38  import fr.ifremer.allegro.obsdeb.ui.swing.content.catches.CatchesUI;
39  import fr.ifremer.allegro.obsdeb.ui.swing.content.catches.CatchesUIHandler;
40  import fr.ifremer.allegro.obsdeb.ui.swing.content.catches.SaveCatchesWorkerAction;
41  import fr.ifremer.allegro.obsdeb.ui.swing.util.ObsdebBeanMonitor;
42  import fr.ifremer.allegro.obsdeb.ui.swing.util.ObsdebUI;
43  import fr.ifremer.allegro.obsdeb.ui.swing.util.ObsdebUIUtil;
44  import fr.ifremer.allegro.obsdeb.ui.swing.util.table.ObsdebColumnIdentifier;
45  import fr.ifremer.allegro.obsdeb.ui.swing.util.table.component.ComboBoxCellRenderer;
46  import fr.ifremer.allegro.obsdeb.ui.swing.util.table.component.FilterableComboBoxCellEditor;
47  import fr.ifremer.allegro.obsdeb.ui.swing.util.treetable.AbstractObsdebTreeTableModel;
48  import fr.ifremer.allegro.obsdeb.ui.swing.util.treetable.AbstractObsdebTreeTableNode;
49  import fr.ifremer.allegro.obsdeb.ui.swing.util.treetable.AbstractObsdebTreeTableUIHandler;
50  import fr.ifremer.allegro.obsdeb.ui.swing.util.treetable.TreeTableCellRenderer;
51  import jaxx.runtime.SwingUtil;
52  import jaxx.runtime.swing.editor.bean.BeanFilterableComboBox;
53  import jaxx.runtime.validator.swing.SwingValidator;
54  import org.apache.commons.logging.Log;
55  import org.apache.commons.logging.LogFactory;
56  import org.jdesktop.swingx.JXTreeTable;
57  import org.jdesktop.swingx.table.TableColumnExt;
58  
59  import javax.swing.*;
60  import javax.swing.event.CellEditorListener;
61  import javax.swing.event.ChangeEvent;
62  import javax.swing.table.TableModel;
63  import javax.swing.tree.DefaultTreeCellRenderer;
64  import java.awt.*;
65  import java.beans.PropertyChangeEvent;
66  import java.beans.PropertyChangeListener;
67  import java.util.ArrayList;
68  import java.util.Collections;
69  import java.util.List;
70  import java.util.Objects;
71  
72  import static org.nuiton.i18n.I18n.t;
73  
74  /**
75   * @author Ludovic Pecquot (ludovic.pecquot@e-is.pro)
76   */
77  public class CatchesTreeTableUIHandler extends AbstractObsdebTreeTableUIHandler<
78          CatchesRowModel, CatchesTreeTableUIModel, CatchesTreeTableNode, CatchesTreeTableHelper, CatchesTreeTableUI> {
79  
80      private static final Log log = LogFactory.getLog(CatchesTreeTableUIHandler.class);
81  
82      private CatchesTreeTableHelper helper;
83  
84      private SaveCatchesWorkerAction saveCatchesWorkerAction;
85  
86      private FilterableComboBoxCellEditor<QualitativeValueDTO> dressingCellEditor;
87      private FilterableComboBoxCellEditor<QualitativeValueDTO> preservationCellEditor;
88      private FilterableComboBoxCellEditor<QualitativeValueDTO> sizeCategoryCellEditor;
89      private FilterableComboBoxCellEditor<QualitativeValueDTO> packagingCellEditor;
90      private FilterableComboBoxCellEditor<FishingOperationGroupDTO> effortCellEditor;
91      private ComboBoxCellRenderer effortCellRenderer;
92  
93      public void setCatchesUIHandler(CatchesUIHandler catchesUIHandler) {
94          saveCatchesWorkerAction = getContext().getActionFactory().createNonBlockingUIAction(catchesUIHandler, SaveCatchesWorkerAction.class);
95      }
96  
97      public CatchesTreeTableUIHandler() {
98          super(CatchesRowModel.PROPERTY_TAXON_GROUP,
99                  CatchesRowModel.PROPERTY_WEIGHT,
100                 CatchesRowModel.PROPERTY_NUMBER,
101                 CatchesRowModel.PROPERTY_PACKAGING,
102                 CatchesRowModel.PROPERTY_OPERATION_GROUP,
103                 CatchesRowModel.PROPERTY_DRESSING,
104                 CatchesRowModel.PROPERTY_PRESERVATION,
105                 CatchesRowModel.PROPERTY_CATCH_FIELD_CATEGORY);
106     }
107 
108     @Override
109     public void beforeInit(CatchesTreeTableUI ui) {
110         super.beforeInit(ui);
111         CatchesTreeTableUIModel model = new CatchesTreeTableUIModel();
112         ui.setContextValue(model);
113     }
114 
115     @Override
116     public void afterInit(CatchesTreeTableUI ui) {
117         initUI(ui);
118 
119         initRenderersAndEditors();
120 
121         initCatchesTreeTable();
122 
123         // add listener on selected effort
124         getModel().addPropertyChangeListener(CatchesTreeTableUIModel.PROPERTY_SELECTED_EFFORT, new PropertyChangeListener() {
125 
126             @Override
127             public void propertyChange(PropertyChangeEvent evt) {
128 
129                 // tell the helper to retrieve data
130                 getHelper().modelChanged();
131 
132             }
133         });
134 
135         ui.applyDataBinding(CatchesTreeTableUI.BINDING_DELETE_BUTTON_ENABLED);
136         ui.applyDataBinding(CatchesTreeTableUI.BINDING_DELETE_MENU_ITEM_ENABLED);
137 
138     }
139 
140     private void initRenderersAndEditors() {
141 
142         /**
143          * DRESSING EDITOR
144          */
145         dressingCellEditor = newFilterableComboBoxCellEditor(
146                 new ArrayList<QualitativeValueDTO>(),
147                 QualitativeValueDTO.class, false);
148 
149         dressingCellEditor.addCellEditorListener(new CellEditorListener() {
150 
151             @Override
152             public void editingStopped(ChangeEvent e) {
153                 getModel().setLastDressing((QualitativeValueDTO) dressingCellEditor.getCellEditorValue());
154             }
155 
156             @Override
157             public void editingCanceled(ChangeEvent e) {
158             }
159         });
160 
161         /**
162          * PRESERVATION EDITOR
163          */
164         preservationCellEditor = newFilterableComboBoxCellEditor(
165                 new ArrayList<QualitativeValueDTO>(),
166                 QualitativeValueDTO.class, false);
167 
168         preservationCellEditor.addCellEditorListener(new CellEditorListener() {
169 
170             @Override
171             public void editingStopped(ChangeEvent e) {
172                 getModel().setLastPreservation((QualitativeValueDTO) preservationCellEditor.getCellEditorValue());
173             }
174 
175             @Override
176             public void editingCanceled(ChangeEvent e) {
177             }
178         });
179 
180         /**
181          * SIZE CATEGORY EDITOR
182          */
183         sizeCategoryCellEditor = newFilterableComboBoxCellEditor(
184                 new ArrayList<QualitativeValueDTO>(),
185                 QualitativeValueDTO.class, false);
186 
187         /**
188          * METHOD EDITOR
189          */
190         packagingCellEditor = newFilterableComboBoxCellEditor(
191                 new ArrayList<QualitativeValueDTO>(),
192                 QualitativeValueDTO.class, false);
193 
194         /**
195          * FISHING OPERATION GROUP RENDERER
196          */
197         effortCellRenderer = new ComboBoxCellRenderer<FishingOperationGroupDTO>(newTableCellRender(FishingOperationGroupDTO.class)) {
198 
199             @Override
200             protected List<FishingOperationGroupDTO> getList() {
201                 return getFishingOperationGroupList();
202             }
203         };
204 
205         /**
206          * FISHING OPERATION GROUP EDITOR
207          */
208         effortCellEditor = newFilterableComboBoxCellEditor(
209                 getFishingOperationGroupList(),
210                 FishingOperationGroupDTO.class, false);
211 
212         // add listener on the fishing operation group list (from EffortUI) to populate this combobox
213         getContext().getDataContext().addPropertyChangeListener(ObsdebDataContext.PROPERTY_FISHING_OPERATION_GROUP, new PropertyChangeListener() {
214 
215             @Override
216             public void propertyChange(PropertyChangeEvent evt) {
217 
218                 // re build efforts list
219                 List<FishingOperationGroupDTO> newList = getFishingOperationGroupList();
220                 effortCellEditor.getCombo().setData(newList);
221                 effortCellEditor.getCombo().firePropertyChange(BeanFilterableComboBox.PROPERTY_DATA, null, newList);
222 
223                 // reset last selected effort
224                 getModel().setLastEffort(null);
225             }
226         });
227 
228         // add listener on the model to catch change of decorator index and sort order
229         getModel().addPropertyChangeListener(new PropertyChangeListener() {
230 
231             @Override
232             public void propertyChange(PropertyChangeEvent evt) {
233 
234                 if (null != evt.getPropertyName()) {
235                     switch (evt.getPropertyName()) {
236 
237                         case CatchesTreeTableUIModel.PROPERTY_EFFORT_DECORATOR_INDEX:
238                             // apply same decorator index
239                             int index = (int) evt.getNewValue();
240                             // on cellEditor
241                             effortCellEditor.getCombo().setIndex(index);
242                             // on cell renderer
243                             TreeTableCellRenderer decoratorRenderer = (TreeTableCellRenderer) effortCellRenderer.getDelegateRenderer();
244                             ObsdebDecorator decorator = (ObsdebDecorator) decoratorRenderer.getDecorator();
245                             decorator.setContextIndex(index);
246                             break;
247 
248                         case CatchesTreeTableUIModel.PROPERTY_EFFORT_DECORATOR_REVERSE_SORT:
249                             // apply same sort order on cellEditor
250                             boolean reverseSort = (boolean) evt.getNewValue();
251                             effortCellEditor.getCombo().setReverseSort(reverseSort);
252                             break;
253                     }
254                 }
255             }
256         });
257 
258         // add listener to save the last operation group selected
259         effortCellEditor.addCellEditorListener(new CellEditorListener() {
260 
261             @Override
262             public void editingStopped(ChangeEvent e) {
263                 FishingOperationGroupDTO opGroup = (FishingOperationGroupDTO) effortCellEditor.getCellEditorValue();
264                 getModel().setLastEffort(opGroup);
265             }
266 
267             @Override
268             public void editingCanceled(ChangeEvent e) {
269             }
270         });
271 
272     }
273 
274     private void initCatchesTreeTable() {
275 
276         // create data provider
277         CatchesDataProvider catchesDataProvider = new CatchesDataProvider() {
278 
279             @Override
280             protected List<CatchesRowModel> getData() {
281 
282                 // return filtered rows
283                 return getFilteredCatchesRowList();
284 
285             }
286 
287             @Override
288             protected void setData(List<CatchesRowModel> list) {
289 
290                 // remove filtered rows first
291                 getModel().deleteRows(ObsdebEntities.getSet(getFilteredCatchesRowList()));
292 
293                 // then add new filtered (or not) list
294                 getModel().addRows(list);
295             }
296         };
297 
298         // create tree table helper who creates the model
299         helper = new CatchesTreeTableHelper(catchesDataProvider, this);
300 
301         /* MAIN INITIALIZATION */
302         initTreeTable();
303 
304         /* ICONS */
305         final Icon batchIcon = SwingUtil.createActionIcon("batch");
306         final Icon fractionBatchIcon = SwingUtil.createActionIcon("fractionBatch");
307 
308         /* RENDERERS */
309         getTreeTable().setTreeCellRenderer(new DefaultTreeCellRenderer() {
310 
311             @Override
312             public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
313                 JLabel label = (JLabel) super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
314 
315                 // set the right icon
316                 CatchesTreeTableNode node = (CatchesTreeTableNode) value;
317                 label.setIcon(null);
318                 if (CatchesTreeTableNode.CONTEXT_NORMAL_ROW.equals(node.getContext())
319                         || CatchesTreeTableNode.CONTEXT_NEW_ROW.equals(node.getContext())) {
320                     label.setIcon(batchIcon);
321                 } else if (CatchesTreeTableNode.CONTEXT_PARENT_ROW.equals(node.getContext())) {
322                     label.setIcon(batchIcon);
323                 } else if (CatchesTreeTableNode.CONTEXT_CHILD_ROW.equals(node.getContext())) {
324                     label.setIcon(fractionBatchIcon);
325                 }
326 
327                 // hide text
328                 label.setText(null);
329 
330                 return label;
331             }
332 
333         });
334 
335         getTreeTable().setDefaultRenderer(QualitativeValueDTO.class, newTableCellRender(QualitativeValueDTO.class));
336 
337     }
338 
339     @Override
340     public void configureTreeTableColumn(TableModel model, TableColumnExt columnExt) {
341 
342         ObsdebColumnIdentifier identifier = (ObsdebColumnIdentifier) columnExt.getIdentifier();
343         switch (identifier.getPropertyName()) {
344 
345             case CatchesRowModel.PROPERTY_TAXON_GROUP: {
346 
347                 // renderer
348                 columnExt.setCellRenderer(newTableCellRender(TaxonGroupDTO.class));
349 
350                 // editor
351                 columnExt.setCellEditor(newFilterableComboBoxCellEditor(
352                         getContext().getReferentialService().getAllTaxonGroup(), TaxonGroupDTO.class, false));
353 
354             }
355             break;
356 
357             case CatchesRowModel.PROPERTY_WEIGHT: {
358 
359                 columnExt.setCellEditor(newNumberCellEditor(Double.class, false, ObsdebUI.DECIMAL2_PATTERN));
360                 columnExt.setCellRenderer(newNumberCellRenderer(2));
361 
362             }
363             break;
364 
365             case CatchesRowModel.PROPERTY_NUMBER: {
366 
367                 columnExt.setCellEditor(newNumberCellEditor(Integer.class, false, ObsdebUI.INT_4_DIGITS_PATTERN));
368                 columnExt.setCellRenderer(newNumberCellRenderer(0));
369 
370             }
371             break;
372 
373             case CatchesRowModel.PROPERTY_PACKAGING: {
374 
375                 columnExt.setCellEditor(packagingCellEditor);
376 
377             }
378             break;
379 
380             case CatchesRowModel.PROPERTY_OPERATION_GROUP: {
381 
382                 // the operation group column should be not hideable
383                 columnExt.setHideable(false);
384 
385                 // specific renderer
386                 columnExt.setCellRenderer(effortCellRenderer);
387 
388                 // specific editor
389                 columnExt.setCellEditor(effortCellEditor);
390             }
391             break;
392 
393             case CatchesRowModel.PROPERTY_DRESSING: {
394 
395                 columnExt.setCellEditor(dressingCellEditor);
396 
397             }
398             break;
399 
400             case CatchesRowModel.PROPERTY_PRESERVATION: {
401 
402                 columnExt.setCellEditor(preservationCellEditor);
403 
404             }
405             break;
406 
407             case CatchesRowModel.PROPERTY_CATCH_FIELD_CATEGORY: {
408 
409                 columnExt.setCellEditor(sizeCategoryCellEditor);
410 
411             }
412             break;
413 
414         }
415     }
416 
417     @Override
418     protected void onAfterSelectedNodeChanged(CatchesTreeTableNode oldNode, CatchesTreeTableNode newNode) {
419         super.onAfterSelectedNodeChanged(oldNode, newNode);
420 
421         // set the available lists for some rowmodel properties
422         if (newNode != null && !AbstractObsdebTreeTableNode.CONTEXT_PARENT_ROW.equals(newNode.getContext())) {
423             populateAvailableLists(newNode.getUserObject());
424         }
425     }
426 
427     private class PopulatorWorker extends SwingWorker<Object, Object> {
428 
429         CatchesRowModel row;
430 
431         List<QualitativeValueDTO> dressings;
432         List<QualitativeValueDTO> preservations;
433         List<QualitativeValueDTO> sizeCategories;
434 
435         public void setRow(CatchesRowModel row) {
436             this.row = row;
437         }
438 
439         @Override
440         protected Object doInBackground() throws Exception {
441             Integer taxonGroupId = row.getTaxonGroup() != null ? row.getTaxonGroup().getId() : null;
442 
443             // dressings
444             dressings = getContext().getRegionalizedDressings(taxonGroupId);
445 
446             // preservations
447             preservations = getContext().getRegionalizedPreservations(taxonGroupId);
448 
449             // size categories 
450             sizeCategories = getContext().getReferentialService().getAllSizeSortingCategory();
451 
452             return null;
453         }
454 
455         @Override
456         protected void done() {
457 
458             // prepare last values
459             if (getModel().getLastDressing() == null) {
460                 if (row.getDressing() != null) {
461                     getModel().setLastDressing(row.getDressing());
462                 } else {
463                     getModel().setLastDressing(getLastDressingInRows());
464                 }
465             }
466             if (getModel().getLastPreservation() == null) {
467                 if (row.getPreservation() != null) {
468                     getModel().setLastPreservation(row.getPreservation());
469                 } else {
470                     getModel().setLastPreservation(getLastPreservationInRows());
471                 }
472             }
473 
474             // default effort
475             row.setOperationGroup(getDefaultValue(row.getOperationGroup(), getModel().getLastEffort(), null, getFishingOperationGroupList()));
476 
477             // default dressing
478             dressingCellEditor.getCombo().setData(dressings);
479             // TODO if we want precedance of default value over lastValue, insert default as last
480             QualitativeValueDTO defaultDressing = getConfig().isCatchesWholeAndFreshByDefaultEnable()
481                     ? getContext().getReferentialService().getDressing(
482                     getConfig().getQualitativeValueIdDressingWhole())
483                     : null;
484             row.setDressing(getDefaultValue(row.getDressing(), getModel().getLastDressing(), defaultDressing, dressings));
485 
486             // default preservation
487             preservationCellEditor.getCombo().setData(preservations);
488             QualitativeValueDTO defaultPreservation = getConfig().isCatchesWholeAndFreshByDefaultEnable()
489                     ? getContext().getReferentialService().getPreservation(
490                     getConfig().getQualitativeValueIdPreservationFresh())
491                     : null;
492             row.setPreservation(getDefaultValue(row.getPreservation(), getModel().getLastPreservation(), defaultPreservation, preservations));
493 
494             // default size category
495             sizeCategoryCellEditor.getCombo().setData(sizeCategories);
496             row.setCatchFieldCategory(getDefaultValue(row.getCatchFieldCategory(),
497                     getContext().getReferentialService().getSizeSortingCategory(
498                             getConfig().getQualitativeValueIdSizeSortingCategoryNone()),
499                     null,
500                     sizeCategories));
501 
502         }
503 
504     }
505 
506     protected void populateAvailableLists(CatchesRowModel row) {
507 
508         if (row == null) {
509             return;
510         }
511 
512         // execute the populator in a separate thread (Mantis #0023448)
513         PopulatorWorker populatorWorker = new PopulatorWorker();
514         populatorWorker.setRow(row);
515         populatorWorker.execute();
516 
517         populateAvailablePackaging(row);
518     }
519 
520     private void populateAvailablePackaging(CatchesRowModel row) {
521         if (row == null) {
522             return;
523         }
524 
525         // packaging
526         List<QualitativeValueDTO> packagings = getContext().getReferentialService().getAllProducePackaging();
527         packagingCellEditor.getCombo().setData(packagings);
528 
529         if (row.getNumber() != null) {
530             // default packaging
531             row.setPackaging(getDefaultValue(row.getPackaging(),
532                     getContext().getReferentialService().getProducePackaging(
533                             getConfig().getQualitativeValueIdDefaultPackaging()),
534                     null,
535                     packagings));
536         } else {
537             row.setPackaging(null);
538         }
539     }
540 
541     private <E extends ObsdebEntity> E getDefaultValue(E actualValue, E lastValue, E fallbackValue, List<E> values) {
542         E defaultValue = actualValue;
543 
544         if (defaultValue == null) {
545             defaultValue = lastValue;
546         }
547 
548         if (!values.contains(defaultValue) && values.size() == 1) {
549             defaultValue = values.get(0);
550         }
551 
552         // if value still null, force default value
553         if (defaultValue == null && values.contains(fallbackValue)) {
554             defaultValue = fallbackValue;
555         }
556 
557         return defaultValue;
558     }
559 
560     private List<CatchesRowModel> getFilteredCatchesRowList() {
561 
562         if (getModel().getRowCount() == 0) {
563             return Lists.newArrayList();
564         }
565 
566         if (getModel().getSelectedEffort() == null) {
567             return getModel().getRows();
568         }
569 
570 //        int effortId = getModel().getSelectedEffort().getId();
571         return ObsdebEntities.filter(getModel().getRows(), new Predicate<CatchesRowModel>() {
572 
573             @Override
574             public boolean apply(CatchesRowModel input) {
575                 return input != null && Objects.equals(getModel().getSelectedEffort(), input.getOperationGroup());
576             }
577         });
578     }
579 
580     public List<FishingOperationGroupDTO> getFishingOperationGroupList() {
581         return getParentHandler().getFishingOperationGroupList();
582     }
583 
584     private CatchesUIHandler getParentHandler() {
585         // get the parent UI
586         ObsdebUI parentUI = ObsdebUIUtil.getParentUI(getUI());
587 
588         if (parentUI instanceof CatchesUI) {
589             CatchesUI catchesUI = (CatchesUI) parentUI;
590             return catchesUI.getHandler();
591         }
592 
593         throw new ObsdebTechnicalException("the CatchesUIHandler has not been found from CatchesTreeTableUIHandler");
594     }
595 
596     @Override
597     public Class<? extends AbstractObsdebTreeTableModel> getTreeTableModelClass() {
598         return CatchesTreeTableModel.class;
599     }
600 
601     @Override
602     public CatchesTreeTableHelper getHelper() {
603         return helper;
604     }
605 
606     @Override
607     public JXTreeTable getTreeTable() {
608         return ui.getTreeTable();
609     }
610 
611     @Override
612     protected boolean isRowValid(CatchesRowModel row) {
613 
614         if (row.isCalculated()) {
615             // no check if calculated
616             return true;
617         }
618 
619         boolean valid = super.isRowValid(row);
620 
621         // weight or number must be set
622         valid &= row.getWeight() != null || row.getNumber() != null;
623 
624         // packaging must be set when number is set
625         valid &= !(row.getNumber() == null ^ row.getPackaging() == null);
626 
627         return valid;
628     }
629 
630     @Override
631     protected void saveSelectedRowIfRequired(ObsdebBeanMonitor<CatchesRowModel> rowMonitor, CatchesTreeTableNode node) {
632     }
633 
634     public void saveDataWithDelete() {
635 
636         // before execute the worker, tell him to force the dirty state even when no row is modified, just deleted
637         saveCatchesWorkerAction.setForceDirty(true);
638 
639         saveData();
640     }
641 
642     public void reloadData() {
643 
644         // before execute the worker, tell him to reload catches on completion
645         saveCatchesWorkerAction.setReloadCatches(true);
646 
647         saveData();
648     }
649 
650     public void saveData() {
651 
652         // execute the worker
653         saveCatchesWorkerAction.execute();
654     }
655 
656     @Override
657     protected void onRowModified(CatchesRowModel row, String propertyName, Object oldValue, Object newValue) {
658 
659         // prevent multiple modification
660         if (!row.isEditable()) {
661             return;
662         }
663         row.setEditable(false);
664 
665         // set dirty on the DTO to reflect the modify property
666         boolean isRowToSave = !row.isDirty() || row.isCreate();
667 
668         if (CatchesRowModel.PROPERTY_TAXON_GROUP.equals(propertyName)) {
669 
670             TaxonGroupDTO oldTaxonGroup = (TaxonGroupDTO) oldValue;
671             // watch the null taxon on existing row
672             if (!row.isCreate() && oldTaxonGroup != null && newValue == null) {
673                 // reset it to oldValue to prevent DAO exception in this particular case
674                 row.setTaxonGroup(oldTaxonGroup);
675                 isRowToSave = false;
676             } else {
677 
678                 // find existing sale with this catch
679                 if (isSaleCantBeDeleted(CatchesTreeTableModel.TAXON_GROUP, oldTaxonGroup, row.getPackaging(), row.getCatchFieldCategory())) {
680                     // the user tell to NOT delete the sale, so reset the taxon to old value
681                     row.setTaxonGroup(oldTaxonGroup);
682                     isRowToSave = false;
683                 }
684             }
685 
686             // populate lists depending on taxon group
687             populateAvailableLists(row);
688         }
689 
690         if (CatchesRowModel.PROPERTY_NUMBER.equals(propertyName)) {
691             Integer oldNumber = (Integer) oldValue;
692             // if number changes, the packaging can also changes, so tell the row to be editable
693             row.setEditable(true);
694             populateAvailablePackaging(row);
695             // if 'isRowToSave' has been set to false by another change interceptor, will restore old value
696             if (!isRowToSave) {
697                 row.setEditable(false); // prevent other changes
698                 row.setNumber(oldNumber); // rollback value
699                 isRowToSave = false; // force no save (again, to be sure)
700             }
701         }
702 
703         if (CatchesRowModel.PROPERTY_PACKAGING.equals(propertyName)) {
704 
705             QualitativeValueDTO oldPackaging = (QualitativeValueDTO) oldValue;
706             // find existing sale with this catch
707             if (isSaleCantBeDeleted(CatchesTreeTableModel.PACKAGING, row.getTaxonGroup(), oldPackaging, row.getCatchFieldCategory())) {
708                 // the user tell to NOT delete the sale, so reset the packaging to old value
709                 row.setPackaging(oldPackaging);
710                 isRowToSave = false;
711             }
712         }
713 
714         if (CatchesRowModel.PROPERTY_CATCH_FIELD_CATEGORY.equals(propertyName)) {
715 
716             QualitativeValueDTO oldCategory = (QualitativeValueDTO) oldValue;
717             // find existing sale with this catch
718             if (isSaleCantBeDeleted(CatchesTreeTableModel.CATCH_FIELD_CATEGORY, row.getTaxonGroup(), row.getPackaging(), oldCategory)) {
719                 // the user tell to NOT delete the sale, so reset the packaging to old value
720                 row.setCatchFieldCategory(oldCategory);
721                 isRowToSave = false;
722             }
723         }
724 
725         if (row.hasParent()) {
726             // calculate the parent aggregation
727             CatchesRowModel parentRow = row.getParent();
728             getHelper().getDataProvider().calculateAggregation(parentRow);
729             recomputeRowValidState(parentRow);
730             getHelper().refreshRow(row);
731         }
732 
733         if (isRowToSave) {
734             super.onRowModified(row, propertyName, oldValue, newValue);
735             row.setDirty(true);
736             if (row.isValid()) {
737                 saveData();
738             }
739         }
740 
741         row.setEditable(true);
742     }
743 
744     private boolean isSaleCantBeDeleted(ObsdebColumnIdentifier columnIdentifier, TaxonGroupDTO taxonGroup, QualitativeValueDTO packaging, QualitativeValueDTO category) {
745         return isCatchHasSale(taxonGroup, packaging, category)
746                 && !askBeforeDelete(
747                 t("obsdeb.action.edit.catches.canDeleteSale.title"),
748                 t("obsdeb.action.edit.catches.canDeleteSale.message", t(columnIdentifier.getHeaderI18nKey())));
749     }
750 
751     private boolean isCatchHasSale(TaxonGroupDTO taxonGroup, QualitativeValueDTO packaging, QualitativeValueDTO category) {
752 
753         List<? extends SaleDTO> sales = getContext().getOverallSale().getSales();
754         for (SaleDTO sale : sales) {
755             if (sale.getId() != null
756                     && Objects.equals(taxonGroup, sale.getTaxonGroup())
757                     && Objects.equals(category, sale.getCategory())
758                     && Objects.equals(packaging, sale.getPackaging())) {
759 
760                 // a sale has been found
761                 return true;
762             }
763         }
764         return false;
765     }
766 
767     @Override
768     protected String[] getRowPropertiesToIgnore() {
769         return new String[]{CatchesRowModel.PROPERTY_DIRTY};
770     }
771 
772     @Override
773     public void onCloseUI() {
774         if (log.isDebugEnabled()) {
775             log.debug("Closing: " + ui);
776         }
777     }
778 
779     @Override
780     public SwingValidator<CatchesTreeTableUIModel> getValidator() {
781         return null;
782     }
783 
784     @Override
785     protected JComponent getComponentToFocus() {
786         return null;
787     }
788 
789     private QualitativeValueDTO getLastDressingInRows() {
790         for (CatchesRowModel row : getSortedRows()) {
791             QualitativeValueDTO dressing = row.getDressing();
792             if (dressing != null) {
793                 return dressing;
794             }
795         }
796         return null;
797     }
798 
799     private QualitativeValueDTO getLastPreservationInRows() {
800         for (CatchesRowModel row : getSortedRows()) {
801             QualitativeValueDTO preservation = row.getPreservation();
802             if (preservation != null) {
803                 return preservation;
804             }
805         }
806         return null;
807     }
808 
809     private List<CatchesRowModel> getSortedRows() {
810         List<CatchesRowModel> rows = Lists.newArrayList(getModel().getRows());
811         // sort by PARAMETER decrescendo
812         Collections.sort(rows, new ObsdebEntityComparator() {
813 
814             @Override
815             public int compare(ObsdebEntity left, ObsdebEntity right) {
816                 return -super.compare(left, right);
817             }
818         });
819         return rows;
820     }
821 
822 }