View Javadoc
1   package fr.ifremer.dali.ui.swing.content.observation.operation.measurement.grouped;
2   
3   import fr.ifremer.dali.config.DaliConfiguration;
4   import fr.ifremer.dali.dto.referential.UnitDTO;
5   import fr.ifremer.dali.ui.swing.util.DaliUIs;
6   import fr.ifremer.dali.ui.swing.util.table.DaliPmfmColumnIdentifier;
7   import fr.ifremer.dali.ui.swing.util.table.PmfmTableColumn;
8   import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
9   import org.apache.commons.lang3.StringUtils;
10  import org.jdesktop.swingx.action.AbstractActionExt;
11  import org.jdesktop.swingx.event.TableColumnModelExtListener;
12  
13  import javax.swing.*;
14  import javax.swing.event.ChangeEvent;
15  import javax.swing.event.ListSelectionEvent;
16  import javax.swing.event.TableColumnModelEvent;
17  import javax.swing.table.JTableHeader;
18  import javax.swing.table.TableCellRenderer;
19  import javax.swing.table.TableColumn;
20  import javax.swing.table.TableColumnModel;
21  import java.awt.Component;
22  import java.awt.Dimension;
23  import java.awt.event.ActionEvent;
24  import java.beans.PropertyChangeEvent;
25  import java.beans.PropertyChangeListener;
26  import java.math.BigDecimal;
27  import java.util.Arrays;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.Optional;
31  
32  import static org.nuiton.i18n.I18n.t;
33  
34  /**
35   * Action to show/hide total footer on operation's grouped measurements
36   * Need to be a AbstractActionExt with state action set to have a check box menu item in table control
37   *
38   * @author peck7 on 24/04/2019.
39   */
40  public class OperationMeasurementsTotalFooterAction extends AbstractActionExt {
41  
42      private final OperationMeasurementsGroupedTableUIHandler handler;
43      private JLabel label;
44      private FooterTable footer;
45  
46      OperationMeasurementsTotalFooterAction(OperationMeasurementsGroupedTableUIHandler handler) {
47          super(t("dali.samplingOperation.measurement.grouped.total"));
48          this.handler = handler;
49          setStateAction();
50      }
51  
52      @Override
53      public void actionPerformed(ActionEvent e) {
54          if (isSelected()) {
55              getFooterTable().createCalculators();
56              showTotal();
57          } else {
58              hideTotal();
59          }
60      }
61  
62      private void showTotal() {
63  
64          // footer label
65          addIfAbsent(getFooterPanel(), getFooterLabel());
66          getFooterLayout().putConstraint(SpringLayout.NORTH, getFooterLabel(), 0, SpringLayout.NORTH, getFooterPanel());
67          getFooterLayout().putConstraint(SpringLayout.SOUTH, getFooterLabel(), 0, SpringLayout.SOUTH, getFooterPanel());
68          getFooterLayout().putConstraint(SpringLayout.WEST, getFooterLabel(), 0, SpringLayout.WEST, getFooterPanel());
69          getFooterLayout().putConstraint(SpringLayout.EAST, getFooterLabel(), getRowHeaderSize(), SpringLayout.WEST, getFooterPanel());
70  
71          // footer table
72          addIfAbsent(getFooterPanel(), getFooterTable());
73          getFooterLayout().putConstraint(SpringLayout.NORTH, getFooterTable(), 0, SpringLayout.NORTH, getFooterPanel());
74          getFooterLayout().putConstraint(SpringLayout.SOUTH, getFooterTable(), 0, SpringLayout.SOUTH, getFooterPanel());
75          getFooterLayout().putConstraint(SpringLayout.WEST, getFooterTable(), getScrollPosition(), SpringLayout.WEST, getFooterPanel());
76          getFooterLayout().putConstraint(SpringLayout.EAST, getFooterTable(), 0, SpringLayout.EAST, getFooterPanel());
77  
78          // adjust size
79          getFooterTable().setPreferredSize(new Dimension(getFooterTable().getPreferredSize().width, getFooterLabel().getPreferredSize().height));
80          getFooterPanel().setPreferredSize(getFooterTable().getPreferredSize());
81  
82          SwingUtilities.invokeLater(getFooterPanel()::revalidate);
83  
84      }
85  
86      private JLabel getFooterLabel() {
87          if (label == null) {
88              label = new JLabel(t("dali.samplingOperation.measurement.grouped.total.label") + " ");
89              label.setHorizontalAlignment(SwingConstants.RIGHT);
90              // Set Foreground (Mantis #47353)
91              label.setForeground(Optional.ofNullable(UIManager.getColor("thematicLabelColor")).orElse(label.getForeground()));
92  
93              label.setBackground(getTableScrollPane().getBackground());
94              label.setOpaque(true);
95  
96              // init size
97              label.setPreferredSize(new Dimension(label.getPreferredSize().width, DaliUIs.DALI_COMPONENT_HEIGHT));
98          }
99          return label;
100     }
101 
102     private FooterTable getFooterTable() {
103         if (footer == null) {
104             footer = new FooterTable(getTable().getColumnModel());
105             footer.setTable(new JTable(null, getTable().getColumnModel()));
106             initListeners();
107         }
108         return footer;
109     }
110 
111     private void initListeners() {
112 
113         // add listener on column model for column events
114         getTable().getColumnModel().addColumnModelListener(new TableColumnModelExtListener() {
115 
116             @Override
117             public void columnAdded(TableColumnModelEvent e) {
118                 updateUI();
119             }
120 
121             @Override
122             public void columnRemoved(TableColumnModelEvent e) {
123                 updateUI();
124             }
125 
126             @Override
127             public void columnMoved(TableColumnModelEvent e) {
128                 updateUI();
129             }
130 
131             @Override
132             public void columnMarginChanged(ChangeEvent e) {
133                 updateUI();
134             }
135 
136             @Override
137             public void columnSelectionChanged(ListSelectionEvent e) {
138 
139             }
140 
141             @Override
142             public void columnPropertyChange(PropertyChangeEvent event) {
143 
144             }
145         });
146 
147         // add listener on scroll pane's viewport
148         getTableScrollPane().getViewport().addChangeListener(e -> updateUI());
149 
150         // add listener on table row filter
151         getTable().addPropertyChangeListener(SwingTable.PROPERTY_ROW_FILTER, evt -> updateTotals());
152 
153         // add listener on table model
154         getTableModel().addTableModelListener(evt -> updateUI());
155 
156     }
157 
158     private void addIfAbsent(JPanel panel, Component field) {
159         if (Arrays.stream(panel.getComponents()).noneMatch(component -> component == field)) panel.add(field);
160     }
161 
162     private int getScrollPosition() {
163         int scrollPosition = getTableScrollPane().getViewport().getViewPosition().x;
164 
165         // eventually remove row header width
166         scrollPosition -= getRowHeaderSize();
167 
168         return -scrollPosition;
169     }
170 
171     private int getRowHeaderSize() {
172         if (getTableScrollPane().getRowHeader() != null) {
173             return getTableScrollPane().getRowHeader().getViewSize().width;
174         }
175         return 0;
176     }
177 
178     private void hideTotal() {
179 
180         getFooterPanel().remove(getFooterTable());
181 
182         // hide footer panel
183         getFooterPanel().setPreferredSize(new Dimension(0, 0));
184 
185         SwingUtilities.invokeLater(getFooterPanel()::revalidate);
186 
187     }
188 
189     private void updateUI() {
190         if (isSelected())
191             SwingUtilities.invokeLater(() -> {
192                 getFooterTable().createCalculators();
193                 showTotal();
194             });
195     }
196 
197     private void updateTotals() {
198         if (isSelected()) {
199             getFooterTable().updateTotals();
200             getFooterTable().repaint();
201         }
202     }
203 
204     private DaliConfiguration getConfig() {
205         return handler.getConfig();
206     }
207 
208     private SwingTable getTable() {
209         return handler.getTable();
210     }
211 
212     private OperationMeasurementsGroupedTableModel getTableModel() {
213         return handler.getTableModel();
214     }
215 
216     private JScrollPane getTableScrollPane() {
217         return (JScrollPane) SwingUtilities.getAncestorOfClass(JScrollPane.class, getTable());
218     }
219 
220     private JPanel getFooterPanel() {
221         return handler.getUI().getFooterPanel();
222     }
223 
224     private SpringLayout getFooterLayout() {
225         return handler.getUI().getFooterLayout();
226     }
227 
228     private class FooterTable extends JTableHeader {
229 
230         private Map<Integer, TotalCalculator> calculators = null;
231 
232         FooterTable(TableColumnModel columnModel) {
233             super(columnModel);
234 
235             setDefaultRenderer(new FooterRenderer(getDefaultRenderer(), this));
236         }
237 
238         public void createCalculators() {
239             calculators = new HashMap<>();
240 
241             for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
242                 TableColumn column = getColumnModel().getColumn(i);
243                 if (column instanceof PmfmTableColumn) {
244                     PmfmTableColumn pmfmColumn = (PmfmTableColumn) column;
245                     if (pmfmColumn.isNumerical()) {
246                         calculators.put(pmfmColumn.getModelIndex(), new TotalCalculator(pmfmColumn.getPmfmIdentifier()));
247                     }
248                 }
249             }
250         }
251 
252         public void updateTotals() {
253             if (calculators == null)
254                 createCalculators();
255             if (calculators.isEmpty())
256                 return;
257             calculators.values().forEach(TotalCalculator::calculate);
258         }
259 
260         public TotalCalculator getCalculator(int index) {
261             return calculators.get(index);
262         }
263     }
264 
265     private class FooterRenderer implements TableCellRenderer {
266 
267         private final TableCellRenderer delegate;
268         private final FooterTable footer;
269 
270         FooterRenderer(TableCellRenderer delegate, FooterTable footer) {
271             this.delegate = delegate;
272             this.footer = footer;
273         }
274 
275         @Override
276         public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
277             Component component = delegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
278 
279             if (component instanceof JLabel) {
280                 JLabel label = (JLabel) component;
281                 label.setHorizontalAlignment(SwingConstants.RIGHT);
282                 if (getColumn(column) instanceof PmfmTableColumn) {
283                     PmfmTableColumn pmfmTableColumn = (PmfmTableColumn) getColumn(column);
284                     TotalCalculator calculator = getCalculator(column);
285                     if (calculator != null) {
286                         UnitDTO unit = pmfmTableColumn.getPmfmIdentifier().getPmfm().getUnit();
287                         String unitSymbol = (unit == null || getConfig().getExtractionUnitIdsToIgnore().contains(unit.getId())) ? "" : " " + unit.getSymbol();
288                         label.setText(StringUtils.isNotBlank(calculator.getValue()) ? calculator.getValue() + unitSymbol : "");
289                     } else {
290                         label.setText("");
291                     }
292                 } else {
293                     label.setText("");
294                 }
295             } else {
296                 component = new JLabel();
297             }
298             return component;
299         }
300 
301         private TableColumn getColumn(int viewColumnIndex) {
302             return footer.getColumnModel().getColumn(viewColumnIndex);
303         }
304 
305         private TotalCalculator getCalculator(int viewColumnIndex) {
306             return footer.getCalculator(getColumn(viewColumnIndex).getModelIndex());
307         }
308 
309     }
310 
311     private class TotalCalculator implements PropertyChangeListener {
312 
313         private final DaliPmfmColumnIdentifier<OperationMeasurementsGroupedRowModel> identifier;
314         private String value;
315 
316         TotalCalculator(DaliPmfmColumnIdentifier<OperationMeasurementsGroupedRowModel> identifier) {
317             this.identifier = identifier;
318 
319             // add listener on identifier to listen to measurement's value changes
320             identifier.addPropertyChangeListener(this);
321 
322             calculate();
323         }
324 
325         void calculate() {
326             BigDecimal total = new BigDecimal(0);
327             boolean add = false;
328             for (int i = 0; i < getTable().getRowCount(); i++) {
329                 Object value = identifier.getValue(getTableModel().getEntry(getTable().convertRowIndexToModel(i)));
330                 if (value instanceof BigDecimal) {
331                     total = total.add((BigDecimal) value);
332                     add = true;
333                 }
334             }
335             value = add ? total.toString() : "";
336         }
337 
338         String getValue() {
339             return value;
340         }
341 
342         @Override
343         public void propertyChange(PropertyChangeEvent evt) {
344 
345             if (evt instanceof DaliPmfmColumnIdentifier.PmfmChangeEvent) {
346                 calculate();
347                 getFooterTable().repaint();
348             }
349         }
350     }
351 }
352