View Javadoc
1   package fr.ifremer.tutti.ui.swing.content.operation.catches;
2   
3   /*
4    * #%L
5    * Tutti :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2012 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as
13   * published by the Free Software Foundation, either version 3 of the
14   * License, or (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 General Public
22   * License along with this program.  If not, see
23   * <http://www.gnu.org/licenses/gpl-3.0.html>.
24   * #L%
25   */
26  
27  import com.google.common.base.Function;
28  import com.google.common.collect.HashMultimap;
29  import com.google.common.collect.ImmutableSet;
30  import com.google.common.collect.Multimap;
31  import com.google.common.collect.Sets;
32  import fr.ifremer.tutti.TuttiConfiguration;
33  import fr.ifremer.tutti.persistence.entities.data.CatchBatch;
34  import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
35  import fr.ifremer.tutti.type.WeightUnit;
36  import fr.ifremer.tutti.ui.swing.TuttiUIContext;
37  import fr.ifremer.tutti.ui.swing.util.TuttiUIUtil;
38  import fr.ifremer.tutti.ui.swing.util.catches.EnterWeightUI;
39  import fr.ifremer.tutti.ui.swing.util.computable.ComputableData;
40  import fr.ifremer.tutti.util.Numbers;
41  import jaxx.runtime.JAXXUtil;
42  import org.apache.batik.bridge.UpdateManager;
43  import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
44  import org.apache.batik.dom.svg.SVGOMRectElement;
45  import org.apache.batik.dom.svg.SVGOMTextElement;
46  import org.apache.batik.swing.JSVGCanvas;
47  import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
48  import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
49  import org.apache.batik.swing.svg.SVGUserAgentAdapter;
50  import org.apache.batik.util.XMLResourceDescriptor;
51  import org.apache.commons.beanutils.BeanUtils;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  import org.nuiton.jaxx.application.bean.JavaBeanObjectUtil;
55  import org.nuiton.util.Resource;
56  import org.w3c.dom.Document;
57  import org.w3c.dom.Element;
58  import org.w3c.dom.css.CSSStyleDeclaration;
59  import org.w3c.dom.events.EventListener;
60  import org.w3c.dom.events.EventTarget;
61  import org.w3c.dom.svg.SVGRect;
62  import org.w3c.dom.svg.SVGStylable;
63  
64  import java.awt.BorderLayout;
65  import java.awt.Color;
66  import java.awt.Dimension;
67  import java.beans.PropertyChangeEvent;
68  import java.beans.PropertyChangeListener;
69  import java.io.IOException;
70  import java.net.URL;
71  import java.util.Objects;
72  import java.util.Set;
73  
74  import static org.nuiton.i18n.I18n.n;
75  import static org.nuiton.i18n.I18n.t;
76  
77  /**
78   * Created on 2/10/15.
79   *
80   * @author Tony Chemit - chemit@codelutin.com
81   * @since 3.13
82   */
83  public class EditCatchesSvgHandler {
84  
85      /** Logger. */
86      private static final Log log = LogFactory.getLog(EditCatchesSvgHandler.class);
87  
88      protected Multimap<String, PropertyChangeListener> svgRelatedPropertyChangeListeners = HashMultimap.create();
89  
90      protected JSVGCanvas canvas;
91  
92      protected Document svgDocument;
93  
94      protected final TuttiUIContext context;
95  
96      protected final EditCatchesUI ui;
97  
98      protected final EditCatchesUIModel model;
99  
100     protected final SVGUserAgentAdapter ua = new SVGUserAgentAdapter() {
101         @Override
102         public void displayError(String message) {
103             if (log.isErrorEnabled()) {
104                 log.error("Canvas error: " + message);
105             }
106         }
107 
108         @Override
109         public void displayError(Exception ex) {
110             if (log.isErrorEnabled()) {
111                 log.error("Canvas error", ex);
112             }
113         }
114     };
115 
116     public EditCatchesSvgHandler(TuttiUIContext context, EditCatchesUI ui, EditCatchesUIModel model) {
117         this.context = context;
118         this.ui = ui;
119         this.model = model;
120     }
121 
122 
123     public void initResumeSvg() {
124         // remove all in case the previous canvas has not been removed
125         ui.getSvgCanvasPanel().removeAll();
126 
127         try {
128             String parser = XMLResourceDescriptor.getXMLParserClassName();
129             SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
130             URL url = Resource.getURL("EcranResume.svg");
131             svgDocument = f.createDocument(url.toString());
132 
133             // redefine user agent to avoid displaying errors about css
134             canvas = new JSVGCanvas(ua, true, true);
135             canvas.setBackground(Color.decode("#d6d9df"));
136             canvas.setSize(new Dimension(1, 1));
137             canvas.setMySize(new Dimension(1, 1));
138 
139             canvas.setRecenterOnResize(true);
140 
141             final TuttiConfiguration config = context.getConfig();
142 
143             final Color catchColor = config.getColorCatch();
144             final Color speciesColor = config.getColorSpecies();
145             final Color benthosColor = config.getColorBenthos();
146             final Color speciesOrBenthosUnsortedComputedWeightInWarningColor = config.getColorSpeciesOrBenthosUnsortedComputedWeightInWarning();
147             final Color marineLitterColor = config.getColorMarineLitter();
148 
149             canvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
150                 @Override
151                 public void gvtRenderingCompleted(GVTTreeRendererEvent gvtTreeRendererEvent) {
152                     if (log.isDebugEnabled()) {
153                         log.debug("gvtRenderingCompleted");
154                     }
155                     WeightUnit catchWeightUnit = model.getCatchWeightUnit();
156                     WeightUnit speciesWeightUnit = config.getSpeciesWeightUnit();
157                     WeightUnit benthosWeightUnit = config.getBenthosWeightUnit();
158                     WeightUnit marineLitterWeightUnit = config.getMarineLitterWeightUnit();
159 
160                     initSvgField(n("tutti.editCatchBatch.field.catchTotalWeight"),
161                                  CatchBatch.PROPERTY_CATCH_TOTAL_WEIGHT,
162                                  CatchBatch.PROPERTY_CATCH_TOTAL_COMPUTED_WEIGHT,
163                                  model.getCatchTotalComputedOrNotWeight(),
164                                  catchWeightUnit,
165                                  catchColor);
166 
167                     initSvgField(n("tutti.editCatchBatch.field.catchTotalSortedComputedWeight"),
168                                  CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_COMPUTED_WEIGHT, catchWeightUnit, catchColor);
169 
170                     initSvgField(n("tutti.editCatchBatch.field.catchTotalRejectedWeight"),
171                                  CatchBatch.PROPERTY_CATCH_TOTAL_REJECTED_WEIGHT,
172                                  CatchBatch.PROPERTY_CATCH_TOTAL_REJECTED_COMPUTED_WEIGHT,
173                                  model.getCatchTotalRejectedComputedOrNotWeight(),
174                                  catchWeightUnit,
175                                  catchColor);
176                     initSvgField(n("tutti.editCatchBatch.field.catchTotalSortedSortedComputedWeight"),
177                                  CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_SORTED_COMPUTED_WEIGHT, catchWeightUnit, catchColor);
178 
179                     initSvgField(n("tutti.editCatchBatch.field.speciesTotalSortedWeight"),
180                                  CatchBatch.PROPERTY_SPECIES_TOTAL_SORTED_WEIGHT,
181                                  CatchBatch.PROPERTY_SPECIES_TOTAL_SORTED_COMPUTED_WEIGHT,
182                                  model.getSpeciesTotalSortedComputedOrNotWeight(),
183                                  speciesWeightUnit,
184                                  speciesColor);
185                     initSvgField(n("tutti.editCatchBatch.field.speciesTotalSampleSortedComputedWeight"),
186                                  CatchBatch.PROPERTY_SPECIES_TOTAL_SAMPLE_SORTED_COMPUTED_WEIGHT, speciesWeightUnit, speciesColor);
187 
188                     initSvgField(n("tutti.editCatchBatch.field.speciesTotalInertWeight"),
189                                  CatchBatch.PROPERTY_SPECIES_TOTAL_INERT_WEIGHT,
190                                  CatchBatch.PROPERTY_SPECIES_TOTAL_INERT_COMPUTED_WEIGHT,
191                                  model.getSpeciesTotalInertComputedOrNotWeight(),
192                                  speciesWeightUnit,
193                                  null);
194                     initSvgField(n("tutti.editCatchBatch.field.speciesTotalLivingNotItemizedWeight"),
195                                  CatchBatch.PROPERTY_SPECIES_TOTAL_LIVING_NOT_ITEMIZED_WEIGHT,
196                                  CatchBatch.PROPERTY_SPECIES_TOTAL_LIVING_NOT_ITEMIZED_COMPUTED_WEIGHT,
197                                  model.getSpeciesTotalLivingNotItemizedComputedOrNotWeight(),
198                                  speciesWeightUnit,
199                                  null);
200 
201                     initSvgField(n("tutti.editCatchBatch.field.benthosTotalSortedWeight"),
202                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_SORTED_WEIGHT,
203                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_SORTED_COMPUTED_WEIGHT,
204                                  model.getBenthosTotalSortedComputedOrNotWeight(),
205                                  benthosWeightUnit,
206                                  benthosColor);
207 
208                     initSvgField(n("tutti.editCatchBatch.field.benthosTotalSampleSortedComputedWeight"),
209                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_SAMPLE_SORTED_COMPUTED_WEIGHT, benthosWeightUnit, benthosColor);
210 
211                     initSvgField(n("tutti.editCatchBatch.field.benthosTotalInertWeight"),
212                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_INERT_WEIGHT,
213                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_INERT_COMPUTED_WEIGHT,
214                                  model.getBenthosTotalInertComputedOrNotWeight(),
215                                  benthosWeightUnit,
216                                  null);
217 
218                     initSvgField(n("tutti.editCatchBatch.field.benthosTotalLivingNotItemizedWeight"),
219                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_LIVING_NOT_ITEMIZED_WEIGHT,
220                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_LIVING_NOT_ITEMIZED_COMPUTED_WEIGHT,
221                                  model.getBenthosTotalLivingNotItemizedComputedOrNotWeight(),
222                                  benthosWeightUnit,
223                                  null);
224 
225                     initSvgField(n("tutti.editCatchBatch.field.catchTotalUnsortedComputedWeight"),
226                                  CatchBatch.PROPERTY_CATCH_TOTAL_UNSORTED_COMPUTED_WEIGHT, catchWeightUnit, catchColor);
227                     initSvgField(n("tutti.editCatchBatch.field.speciesTotalUnsortedComputedWeight"),
228                                  CatchBatch.PROPERTY_SPECIES_TOTAL_UNSORTED_COMPUTED_WEIGHT, speciesWeightUnit, speciesColor);
229                     initSvgField(n("tutti.editCatchBatch.field.benthosTotalUnsortedComputedWeight"),
230                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_UNSORTED_COMPUTED_WEIGHT, benthosWeightUnit, benthosColor);
231 
232                     addSvgRelatedPropertyChangeListener(null, new ChangeElementBackgroundColorPropertyChangeListener(
233                             CatchBatch.PROPERTY_SPECIES_TOTAL_UNSORTED_COMPUTED_WEIGHT,
234                             Sets.newHashSet(CatchBatch.PROPERTY_CATCH_TOTAL_REJECTED_WEIGHT,
235                                             CatchBatch.PROPERTY_CATCH_TOTAL_REJECTED_COMPUTED_WEIGHT,
236                                             CatchBatch.PROPERTY_SPECIES_TOTAL_SORTED_WEIGHT,
237                                             CatchBatch.PROPERTY_SPECIES_TOTAL_SORTED_COMPUTED_WEIGHT,
238                                             CatchBatch.PROPERTY_SPECIES_TOTAL_SAMPLE_SORTED_COMPUTED_WEIGHT),
239                             model1 -> {
240                                 boolean warning = model1.isSpeciesTotalUnsortedComputedWeightInWarning();
241                                 return warning ? speciesOrBenthosUnsortedComputedWeightInWarningColor : speciesColor;
242                             }));
243 
244                     addSvgRelatedPropertyChangeListener(null, new ChangeElementBackgroundColorPropertyChangeListener(
245                             CatchBatch.PROPERTY_BENTHOS_TOTAL_UNSORTED_COMPUTED_WEIGHT,
246                             Sets.newHashSet(CatchBatch.PROPERTY_CATCH_TOTAL_REJECTED_WEIGHT,
247                                             CatchBatch.PROPERTY_CATCH_TOTAL_REJECTED_COMPUTED_WEIGHT,
248                                             CatchBatch.PROPERTY_BENTHOS_TOTAL_SORTED_WEIGHT,
249                                             CatchBatch.PROPERTY_BENTHOS_TOTAL_SORTED_COMPUTED_WEIGHT,
250                                             CatchBatch.PROPERTY_BENTHOS_TOTAL_SAMPLE_SORTED_COMPUTED_WEIGHT),
251                             model1 -> {
252                                 boolean warning = model1.isBenthosTotalUnsortedComputedWeightInWarning();
253                                 return warning ? speciesOrBenthosUnsortedComputedWeightInWarningColor : benthosColor;
254                             }));
255 
256                     initSvgField(n("tutti.editCatchBatch.field.speciesTotalComputedWeight"),
257                                  CatchBatch.PROPERTY_SPECIES_TOTAL_COMPUTED_WEIGHT,
258                                  speciesWeightUnit,
259                                  speciesColor,
260                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_COMPUTED_WEIGHT,
261                                  CatchBatch.PROPERTY_MARINE_LITTER_TOTAL_WEIGHT);
262                     initSvgField(n("tutti.editCatchBatch.field.benthosTotalComputedWeight"),
263                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_COMPUTED_WEIGHT,
264                                  benthosWeightUnit,
265                                  benthosColor,
266                                  CatchBatch.PROPERTY_SPECIES_TOTAL_COMPUTED_WEIGHT,
267                                  CatchBatch.PROPERTY_MARINE_LITTER_TOTAL_WEIGHT);
268                     initSvgField(n("tutti.editCatchBatch.field.marineLitterTotalWeight"),
269                                  CatchBatch.PROPERTY_MARINE_LITTER_TOTAL_WEIGHT,
270                                  CatchBatch.PROPERTY_MARINE_LITTER_TOTAL_COMPUTED_WEIGHT,
271                                  model.getMarineLitterTotalComputedOrNotWeight(),
272                                  marineLitterWeightUnit,
273                                  marineLitterColor,
274                                  CatchBatch.PROPERTY_SPECIES_TOTAL_COMPUTED_WEIGHT,
275                                  CatchBatch.PROPERTY_BENTHOS_TOTAL_COMPUTED_WEIGHT);
276 
277                     addSvgRelatedPropertyChangeListener(null, new RatioPropertyChangeListener("ratioSpeciesSampleSortedOverSpeciesSortedWeightLabel",
278                                                                                               CatchBatch.PROPERTY_SPECIES_TOTAL_SAMPLE_SORTED_COMPUTED_WEIGHT,
279                                                                                               CatchBatch.PROPERTY_SPECIES_TOTAL_SORTED_WEIGHT,
280                                                                                               CatchBatch.PROPERTY_SPECIES_TOTAL_SORTED_COMPUTED_WEIGHT));
281                     addSvgRelatedPropertyChangeListener(null, new RatioPropertyChangeListener("ratioBenthosSampleSortedOverBenthosSortedWeightLabel",
282                                                                                               CatchBatch.PROPERTY_BENTHOS_TOTAL_SAMPLE_SORTED_COMPUTED_WEIGHT,
283                                                                                               CatchBatch.PROPERTY_BENTHOS_TOTAL_SORTED_WEIGHT,
284                                                                                               CatchBatch.PROPERTY_BENTHOS_TOTAL_SORTED_COMPUTED_WEIGHT));
285                     addSvgRelatedPropertyChangeListener(null, new RatioPropertyChangeListener("ratioSortedSortedOverSortedWeightLabel",
286                                                                                               CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_SORTED_COMPUTED_WEIGHT,
287                                                                                               CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_COMPUTED_WEIGHT,
288                                                                                               CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_COMPUTED_WEIGHT));
289 
290                     initSpeciesCount(n("tutti.editCatchBatch.field.speciesDistinctSortedSpeciesCount"),
291                                      CatchBatch.PROPERTY_SPECIES_DISTINCT_SORTED_SPECIES_COUNT);
292                     initSpeciesCount(n("tutti.editCatchBatch.field.benthosDistinctSortedSpeciesCount"),
293                                      CatchBatch.PROPERTY_BENTHOS_DISTINCT_SORTED_SPECIES_COUNT);
294 
295                     initSpeciesCount(n("tutti.editCatchBatch.field.speciesDistinctUnsortedSpeciesCount"),
296                                      CatchBatch.PROPERTY_SPECIES_DISTINCT_UNSORTED_SPECIES_COUNT);
297                     initSpeciesCount(n("tutti.editCatchBatch.field.benthosDistinctUnsortedSpeciesCount"),
298                                      CatchBatch.PROPERTY_BENTHOS_DISTINCT_UNSORTED_SPECIES_COUNT);
299 
300                     initTremieCarrouselField(n("tutti.editCatchBatch.field.catchTotalSortedCarousselWeight"),
301                                              CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_CAROUSSEL_WEIGHT, catchWeightUnit);
302                     initTremieCarrouselField(n("tutti.editCatchBatch.field.catchTotalSortedTremisWeight"),
303                                              CatchBatch.PROPERTY_CATCH_TOTAL_SORTED_TREMIS_WEIGHT, catchWeightUnit);
304 
305                     ui.getSvgCanvasPanel().add(canvas, BorderLayout.CENTER);
306                 }
307             });
308 
309             canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
310             canvas.setDocument(svgDocument);
311 
312         } catch (IOException err) {
313             if (log.isErrorEnabled()) {
314                 log.error("error while initializing the resume background", err);
315             }
316             context.getErrorHelper().showErrorDialog(t("tutti.editCatchBatch.svgLoading.error"), err);
317         }
318     }
319 
320     public void clearSVG() {
321 
322         for (String property : svgRelatedPropertyChangeListeners.keySet()) {
323             for (PropertyChangeListener listener : svgRelatedPropertyChangeListeners.get(property)) {
324                 if (property != null) {
325                     model.removePropertyChangeListener(property, listener);
326                 } else {
327                     model.removePropertyChangeListener(listener);
328                 }
329             }
330         }
331         svgRelatedPropertyChangeListeners.clear();
332 
333         if (canvas != null) {
334             UpdateManager updateManager = canvas.getUpdateManager();
335             if (updateManager != null ) {
336                 try {
337                     updateManager.suspend();
338                 } catch (Exception e) {
339                     // FIXME Should not come here, but still can happen...
340                 }
341             }
342             try {
343                 canvas.dispose();
344             } catch (Exception e) {
345                 // FIXME Should not come here, but still can happen...
346             }
347             ui.getSvgCanvasPanel().remove(canvas);
348         }
349     }
350 
351     protected void initSvgField(String label, String computedProperty, WeightUnit weightUnit, Color bgColor, String... idsInGroup) {
352         initSvgField(label, null, computedProperty, null, weightUnit, bgColor, idsInGroup);
353     }
354 
355     protected void initSvgField(final String label,
356                                 String property,
357                                 String computedProperty,
358                                 final ComputableData<Float> computableData,
359                                 final WeightUnit weightUnit,
360                                 final Color bgColor,
361                                 final String... idsInGroup) {
362 
363         if (log.isDebugEnabled()) {
364             log.debug("init " + property + " field");
365         }
366 
367         final String notNullProperty = property != null ? property : computedProperty;
368 
369         OnDataOrComputedDataValueChangedListener listener = new OnDataOrComputedDataValueChangedListener(notNullProperty, computableData, weightUnit, idsInGroup);
370         if (property != null) {
371             addSvgRelatedPropertyChangeListener(property, listener);
372         }
373         if (computedProperty != null) {
374             addSvgRelatedPropertyChangeListener(computedProperty, listener);
375         }
376 
377         if (computableData != null) {
378             // data or computed data value
379             Element element = svgDocument.getElementById(property);
380             EventTarget target = (EventTarget) element;
381             target.addEventListener("click", new OnValueClickListener(computableData, property, weightUnit), false);
382         }
383 
384         updateOnCanvas(() -> {
385 
386             SVGOMRectElement rectElement = (SVGOMRectElement) svgDocument.getElementById(notNullProperty + "Rect");
387             SVGRect bbox = rectElement.getBBox();
388             Float x = bbox.getX();
389 
390             SVGOMTextElement labelElement = (SVGOMTextElement) svgDocument.getElementById(notNullProperty + "Label");
391             CSSStyleDeclaration style = labelElement.getStyle();
392             if (computableData == null) {
393                 style.setProperty("font-style", "italic", null);
394             }
395 
396             if (bgColor != null) {
397                 int colorBrightness = TuttiUIUtil.getColorBrightness(bgColor);
398                 String textColor = colorBrightness > 150 ? "#000000" : "#FFFFFF";
399                 style.setProperty("fill", textColor, null);
400             }
401 
402             labelElement.setTextContent(weightUnit.decorateLabel(t(label)));
403             bbox = labelElement.getBBox();
404             float labelX = bbox.getX();
405             float width = Math.abs(x - labelX) + 10;
406 
407             SVGOMRectElement labelRectElement = (SVGOMRectElement) svgDocument.getElementById(notNullProperty + "LabelRect");
408             if (labelRectElement != null) {
409                 float actualWidth = Math.abs(labelRectElement.getBBox().getX() - labelX) + 10;
410 
411                 if (actualWidth < width) {
412                     labelRectElement.setAttribute("width", Float.toString(width));
413                     labelRectElement.setAttribute("x", String.valueOf(labelX - 10));
414 
415                     for (String id : idsInGroup) {
416                         Element el = svgDocument.getElementById(id + "LabelRect");
417                         el.setAttribute("width", Float.toString(width));
418                         el.setAttribute("x", String.valueOf(labelX - 10));
419                     }
420                 }
421 
422                 style = labelRectElement.getStyle();
423                 String hexaColor = "#" + Integer.toHexString(bgColor.getRGB()).substring(2);
424                 style.setProperty("fill", hexaColor, null);
425             }
426 
427             Float value;
428             boolean computed;
429             if (computableData == null) {
430                 computed = true;
431                 value = getModelPropertyValue(notNullProperty);
432 
433             } else if (computableData.getData() == null) {
434                 computed = true;
435                 value = computableData.getComputedData();
436 
437             } else {
438                 computed = false;
439                 value = computableData.getData();
440             }
441 
442             updateValue(notNullProperty, value, weightUnit, computed, idsInGroup);
443         });
444 
445     }
446 
447     protected void updateOnCanvas(Runnable runnable) {
448         try {
449             canvas.getUpdateManager().getUpdateRunnableQueue().invokeLater(runnable);
450         } catch (IllegalStateException e) {
451             if (log.isErrorEnabled()) {
452                 log.error("error while updating canvas, reload it");
453             }
454             clearSVG();
455             initResumeSvg();
456         }
457     }
458 
459     protected void addSvgRelatedPropertyChangeListener(String property, PropertyChangeListener listener) {
460         svgRelatedPropertyChangeListeners.put(property, listener);
461         if (property != null) {
462             model.addPropertyChangeListener(property, listener);
463         } else {
464             model.addPropertyChangeListener(listener);
465         }
466     }
467 
468     protected void updateValue(final String property,
469                                final Float value,
470                                final WeightUnit weightUnit,
471                                final boolean computed,
472                                final String... idsInGroup) {
473 
474         updateOnCanvas(() -> {
475             if (log.isDebugEnabled()) {
476                 log.debug("update " + property + " field");
477             }
478 
479             SVGOMTextElement valueElement = (SVGOMTextElement) svgDocument.getElementById(property + "Value");
480             if (valueElement == null) {
481                 return;
482             }
483 
484             //TODO i18n ?
485             String textContent;
486             if (value != null) {
487                 textContent = weightUnit.renderWeight(value) + " " + weightUnit.getShortLabel();
488             } else {
489                 textContent = null;
490             }
491             valueElement.setTextContent(textContent);
492 
493             CSSStyleDeclaration style = valueElement.getStyle();
494 
495             Color colorComputedWeights = context.getConfig().getColorComputedWeights();
496             String computedColor = "#" + Integer.toHexString(colorComputedWeights.getRGB()).substring(2);
497             style.setProperty("fill", computed ? computedColor : "#000000", null);
498             style.setProperty("font-style", computed ? "italic" : "normal", null);
499 
500             SVGRect bbox = valueElement.getBBox();
501             SVGOMRectElement rectElement = (SVGOMRectElement) svgDocument.getElementById(property + "Rect");
502             if (bbox != null && rectElement != null) {
503                 float width = bbox.getWidth() + 15;
504                 float actualWidth = rectElement.getBBox().getWidth();
505                 if (actualWidth < width) {
506                     rectElement.setAttribute("width", Float.toString(width));
507                     for (String id : idsInGroup) {
508                         Element el = svgDocument.getElementById(id + "Rect");
509                         el.setAttribute("width", Float.toString(width));
510                     }
511                 }
512             }
513         });
514 
515     }
516 
517     protected Float getModelPropertyValue(String property) {
518         Float value;
519         try {
520             String sValue = BeanUtils.getProperty(model, property);
521             if (sValue != null) {
522                 value = Float.parseFloat(sValue);
523             } else {
524                 value = null;
525             }
526 
527         } catch (ReflectiveOperationException e) {
528             if (log.isErrorEnabled()) {
529                 log.error("error on reading model's property " + property, e);
530             }
531             value = null;
532         }
533         return value;
534     }
535 
536     protected void initSpeciesCount(final String label, final String property) {
537 
538         if (log.isDebugEnabled()) {
539             log.debug("init " + property + " field");
540         }
541 
542         updateOnCanvas(() -> {
543             Element labelElement = svgDocument.getElementById(property + "Label");
544             labelElement.setTextContent(t(label));
545 
546             Element valueElement = svgDocument.getElementById(property + "Value");
547             Integer value;
548             try {
549                 String sValue = BeanUtils.getProperty(model, property);
550                 if (sValue != null) {
551                     value = Integer.parseInt(sValue);
552                 } else {
553                     value = null;
554                 }
555 
556             } catch (ReflectiveOperationException e) {
557                 if (log.isErrorEnabled()) {
558                     log.error("error on reading model's property " + property, e);
559                 }
560                 value = null;
561             }
562             valueElement.setTextContent(JAXXUtil.getStringValue(value));
563         });
564 
565         addSvgRelatedPropertyChangeListener(property, evt -> {
566             final Integer value = (Integer) evt.getNewValue();
567             updateOnCanvas(() -> {
568                 Element labelElement = svgDocument.getElementById(property + "Value");
569                 labelElement.setTextContent(JAXXUtil.getStringValue(value));
570             });
571         });
572     }
573 
574     protected void initTremieCarrouselField(final String label, final String property, final WeightUnit weightUnit) {
575         if (log.isDebugEnabled()) {
576             log.debug("init " + property + " field");
577         }
578 
579         updateOnCanvas(() -> {
580             Element labelElement = svgDocument.getElementById(property + "Label");
581             labelElement.setTextContent(weightUnit.decorateLabel(t(label)));
582         });
583 
584         addSvgRelatedPropertyChangeListener(property, evt -> {
585             Float value = (Float) evt.getNewValue();
586             updateValue(property, value, weightUnit, true);
587         });
588 
589         Float value = getModelPropertyValue(property);
590         updateValue(property, value, weightUnit, true);
591 
592         addSvgRelatedPropertyChangeListener(EditCatchesUIModel.PROPERTY_FISHING_OPERATION, evt -> updateTremieCarrouselVisibility(property));
593         updateTremieCarrouselVisibility(property);
594     }
595 
596 
597     protected void updateTremieCarrouselVisibility(final String property) {
598         FishingOperation fishingOperation = model.getFishingOperation();
599         if (fishingOperation != null) {
600             final boolean tremieCarrouselFieldsVisisble = fishingOperation.getVessel() != null
601                                                           && fishingOperation.getVessel().getId().equals(context.getConfig().getTremieCarousselVesselId());
602 
603             updateOnCanvas(() -> {
604                 Element labelElement = svgDocument.getElementById(property + "Label");
605                 labelElement.setAttribute("visibility", tremieCarrouselFieldsVisisble ? "visible" : "hidden");
606 
607                 Element valueElement = svgDocument.getElementById(property + "Value");
608                 valueElement.setAttribute("visibility", tremieCarrouselFieldsVisisble ? "visible" : "hidden");
609             });
610         }
611     }
612 
613     private class OnValueClickListener implements EventListener {
614 
615         private final ComputableData<Float> computableData;
616 
617         private final String property;
618 
619         private final WeightUnit weightUnit;
620 
621         public OnValueClickListener(ComputableData<Float> computableData, String property, WeightUnit weightUnit) {
622             this.computableData = computableData;
623             this.property = property;
624             this.weightUnit = weightUnit;
625         }
626 
627         public void handleEvent(org.w3c.dom.events.Event evt) {
628             log.info("element clicked");
629             EnterWeightUI dialog = new EnterWeightUI(context);
630             Float originalWeight = computableData.getData();
631             Float weight = dialog.openAndGetWeightValue(t("tutti.editCatchBatch.field." + property),
632                                                         originalWeight,
633                                                         weightUnit);
634 
635             if (!Objects.equals(originalWeight, weight)) {
636 
637                 computableData.setData(weight);
638             }
639         }
640     }
641 
642     private class OnDataOrComputedDataValueChangedListener implements PropertyChangeListener {
643 
644         private final String property;
645 
646         private final ComputableData<Float> computableData;
647 
648         private final WeightUnit weightUnit;
649 
650         private final String[] idsInGroup;
651 
652         public OnDataOrComputedDataValueChangedListener(String property,
653                                                         ComputableData<Float> computableData,
654                                                         WeightUnit weightUnit,
655                                                         String... idsInGroup) {
656             this.property = property;
657             this.computableData = computableData;
658             this.weightUnit = weightUnit;
659             this.idsInGroup = idsInGroup;
660         }
661 
662         @Override
663         public void propertyChange(PropertyChangeEvent evt) {
664             boolean computedData;
665             Float newValue;
666 
667             if (computableData == null) {
668                 computedData = true;
669                 newValue = (Float) evt.getNewValue();
670 
671             } else {
672                 if (computableData.getData() == null) {
673                     computedData = true;
674                     newValue = computableData.getComputedData();
675 
676                 } else {
677                     computedData = false;
678                     newValue = computableData.getData();
679                 }
680             }
681             updateValue(property, newValue, weightUnit, computedData, idsInGroup);
682         }
683 
684     }
685 
686     private class ChangeElementBackgroundColorPropertyChangeListener implements PropertyChangeListener {
687 
688         private final String elementId;
689 
690         private Set<String> propertiesToListen;
691 
692         private Function<EditCatchesUIModel, Color> colorFunction;
693 
694         public ChangeElementBackgroundColorPropertyChangeListener(String elementId,
695                                                                   Set<String> propertiesToListen,
696                                                                   Function<EditCatchesUIModel, Color> colorFunction) {
697             this.elementId = elementId;
698             this.propertiesToListen = propertiesToListen;
699             this.colorFunction = colorFunction;
700 
701             updateColor();
702         }
703 
704         @Override
705         public void propertyChange(PropertyChangeEvent evt) {
706             if (propertiesToListen.contains(evt.getPropertyName())) {
707                 updateColor();
708             }
709         }
710 
711         protected void updateColor() {
712             updateOnCanvas(() -> {
713                 if (log.isDebugEnabled()) {
714                     log.debug("update " + elementId + " field");
715                 }
716 
717                 Element rectElement = svgDocument.getElementById(elementId + "LabelRect");
718                 SVGStylable field = (SVGStylable) rectElement;
719                 CSSStyleDeclaration style = field.getStyle();
720 
721                 Color background = colorFunction.apply(model);
722                 String color = "#" + Integer.toHexString(background.getRGB()).substring(2);
723                 style.setProperty("fill", color, null);
724 
725                 SVGOMTextElement labelElement = (SVGOMTextElement) svgDocument.getElementById(elementId + "Label");
726                 CSSStyleDeclaration labelStyle = labelElement.getStyle();
727 
728                 int colorBrightness = TuttiUIUtil.getColorBrightness(background);
729                 String textColor = colorBrightness > 150 ? "#000000" : "#FFFFFF";
730                 labelStyle.setProperty("fill", textColor, null);
731             });
732         }
733     }
734 
735     private class RatioPropertyChangeListener implements PropertyChangeListener {
736 
737         private final String elementId;
738 
739         private final String numeratorProperty;
740 
741         private final String denominatorProperty;
742 
743         private final String denominatorComputedProperty;
744 
745         private Integer ratio = null;
746 
747         private final Set<String> propertyNames;
748 
749         public RatioPropertyChangeListener(String elementId,
750                                            String numeratorProperty,
751                                            String denominatorProperty,
752                                            String denominatorComputedProperty) {
753             this.elementId = elementId;
754             this.numeratorProperty = numeratorProperty;
755             this.denominatorProperty = denominatorProperty;
756             this.denominatorComputedProperty = denominatorComputedProperty;
757             this.propertyNames = ImmutableSet.of(numeratorProperty, denominatorProperty, denominatorComputedProperty);
758             updateRatioText();
759         }
760 
761         @Override
762         public void propertyChange(PropertyChangeEvent evt) {
763             String propertyName = evt.getPropertyName();
764 
765             if (propertyNames.contains(propertyName)) {
766                 updateRatioText();
767             }
768         }
769 
770         protected void updateRatioText() {
771 
772 //            try {
773 //
774 //                String numerator = BeanUtils.getProperty(model, numeratorProperty);
775 //                String denominator = BeanUtils.getProperty(model, denominatorProperty);
776 //                if (denominator == null) {
777 //                    denominator = BeanUtils.getProperty(model, denominatorComputedProperty);
778 //                }
779 //
780 //                if (numerator != null && denominator != null) {
781 //                    Float numeratorValue = Float.valueOf(numerator);
782 //                    Float denominatorValue = Float.valueOf(denominator);
783 //
784 //                    if (denominatorValue != 0) {
785 //                        ratio = Numbers.roundToInt(100f * numeratorValue / denominatorValue);
786 //                    }
787 //                }
788 //
789 //            } catch (ReflectiveOperationException e) {
790 //                if (log.isErrorEnabled()) {
791 //                    log.error("Error while computing the ratio", e);
792 //                }
793 //            }
794 
795             Float  numerator = (Float) JavaBeanObjectUtil.getProperty(model, numeratorProperty);
796             Float  denominator = (Float) JavaBeanObjectUtil.getProperty(model, denominatorProperty);
797             if (denominator == null) {
798                 denominator = (Float) JavaBeanObjectUtil.getProperty(model, denominatorComputedProperty);
799             }
800             if (numerator == null || WeightUnit.KG.isNullOrZero(denominator)) {
801                 ratio = null;
802             } else {
803                 ratio = Numbers.roundToInt(100f * numerator / denominator);
804             }
805 
806             updateOnCanvas(() -> {
807                 if (log.isDebugEnabled()) {
808                     log.debug("update " + elementId + " field");
809                 }
810 
811                 Element ratioElement = svgDocument.getElementById(elementId);
812                 String textContent;
813                 if (ratio != null) {
814                     textContent = ratio + "%";
815                 } else {
816                     textContent = null;
817                 }
818                 ratioElement.setTextContent(textContent);
819             });
820         }
821     }
822 
823 }