1 package fr.ifremer.reefdb.ui.swing.util.table;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Maps;
28 import fr.ifremer.quadrige3.core.dao.technical.Assert;
29 import fr.ifremer.quadrige3.ui.swing.table.AbstractTableUIHandler;
30 import fr.ifremer.quadrige3.ui.swing.table.ColumnIdentifier;
31 import fr.ifremer.quadrige3.ui.swing.table.HiddenColumn;
32 import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
33 import fr.ifremer.quadrige3.ui.swing.table.action.AdditionalTableActions;
34 import fr.ifremer.quadrige3.ui.swing.table.renderer.ColorCheckBoxRenderer;
35 import fr.ifremer.reefdb.config.ReefDbConfiguration;
36 import fr.ifremer.reefdb.dto.ErrorAware;
37 import fr.ifremer.reefdb.dto.ErrorDTO;
38 import fr.ifremer.reefdb.dto.ReefDbBeans;
39 import fr.ifremer.reefdb.dto.referential.pmfm.PmfmDTO;
40 import fr.ifremer.reefdb.dto.referential.pmfm.QualitativeValueDTO;
41 import fr.ifremer.reefdb.ui.swing.ReefDbUIContext;
42 import fr.ifremer.reefdb.ui.swing.util.ReefDbUI;
43 import fr.ifremer.reefdb.ui.swing.util.ReefDbUIs;
44 import fr.ifremer.reefdb.ui.swing.util.table.export.ExportToCSVAction;
45 import jaxx.runtime.SwingUtil;
46 import org.apache.commons.collections4.CollectionUtils;
47 import org.jdesktop.swingx.JXTable;
48 import org.jdesktop.swingx.decorator.AbstractHighlighter;
49 import org.jdesktop.swingx.decorator.ComponentAdapter;
50 import org.jdesktop.swingx.decorator.HighlightPredicate;
51 import org.jdesktop.swingx.decorator.Highlighter;
52 import org.jdesktop.swingx.table.TableColumnExt;
53 import org.jdesktop.swingx.util.PaintUtils;
54 import org.nuiton.jaxx.application.swing.table.AbstractApplicationTableModel;
55 import org.nuiton.jaxx.application.swing.util.ApplicationColorHighlighter;
56
57 import javax.swing.*;
58 import javax.swing.table.TableCellEditor;
59 import javax.swing.table.TableCellRenderer;
60 import java.awt.Color;
61 import java.awt.Component;
62 import java.awt.Container;
63 import java.awt.Font;
64 import java.math.BigDecimal;
65 import java.util.*;
66 import java.util.stream.Collectors;
67
68 import static org.nuiton.i18n.I18n.t;
69
70
71
72
73
74
75
76
77
78 public abstract class AbstractReefDbTableUIHandler<R extends AbstractReefDbRowUIModel<?, ?>, M extends AbstractReefDbTableUIModel<?, R, M>, UI extends ReefDbUI<M, ?>>
79 extends AbstractTableUIHandler<R, M, UI> {
80
81
82
83
84
85
86 protected AbstractReefDbTableUIHandler(String... properties) {
87 super(properties);
88 }
89
90 @Override
91 public ReefDbUIContext getContext() {
92 return (ReefDbUIContext) super.getContext();
93 }
94
95 @Override
96 public ReefDbConfiguration getConfig() {
97 return (ReefDbConfiguration) super.getConfig();
98 }
99
100
101
102
103
104
105
106 protected void initTable(SwingTable table, boolean forceSingleSelection, boolean checkBoxSelection) {
107
108 super.initTable(table, forceSingleSelection, checkBoxSelection || getConfig().isShowTableCheckbox());
109
110 addErrorHighlighters(table);
111
112
113 table.setDefaultRenderer(QualitativeValueDTO.class, newTableCellRender(QualitativeValueDTO.class));
114
115 }
116
117
118
119 @Override
120 protected void addHighlighters(final JXTable table) {
121
122
123 HighlightPredicate notSelectedPredicate = new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED);
124 HighlightPredicate invalidPredicate = (renderer, adapter) -> {
125
126 boolean result = false;
127 AbstractReefDbRowUIModel row = null;
128
129
130
131
132
133
134 if (adapter.row >= 0 && adapter.row < table.getRowCount()) {
135 int modelRow = adapter.convertRowIndexToModel(adapter.row);
136 AbstractApplicationTableModel<?> model = (AbstractApplicationTableModel<?>) table.getModel();
137 row = (AbstractReefDbRowUIModel) model.getEntry(modelRow);
138 }
139
140
141 if (row != null) {
142 result = !row.isValid();
143 }
144 return result;
145 };
146 HighlightPredicate selectedPredicate = HighlightPredicate.IS_SELECTED;
147 HighlightPredicate editablePredicate = HighlightPredicate.EDITABLE;
148 HighlightPredicate readOnlyPredicate = HighlightPredicate.READ_ONLY;
149 HighlightPredicate validPredicate = new HighlightPredicate.NotHighlightPredicate(invalidPredicate);
150 HighlightPredicate readOnlySelectedPredicate = new HighlightPredicate.AndHighlightPredicate(selectedPredicate, readOnlyPredicate);
151 HighlightPredicate invalidSelectedPredicate = new HighlightPredicate.AndHighlightPredicate(selectedPredicate, invalidPredicate);
152 HighlightPredicate notColorizedColumnPredicate = (renderer, adapter) -> {
153
154 if (adapter.getComponent() instanceof JXTable) {
155 JXTable table1 = (JXTable) adapter.getComponent();
156 TableColumnExt column = table1.getColumnExt(adapter.column);
157 return !(column.getCellRenderer() instanceof ColorCheckBoxRenderer);
158 }
159
160 return true;
161 };
162
163
164 HighlightPredicate calculatedPredicate = (renderer, adapter) -> {
165 AbstractReefDbRowUIModel row;
166
167
168
169
170
171
172 AbstractApplicationTableModel model = (AbstractApplicationTableModel) table.getModel();
173 int modelRow = adapter.convertRowIndexToModel(adapter.row);
174 row = (AbstractReefDbRowUIModel) model.getEntry(modelRow);
175
176
177 return row.isCalculated();
178 };
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207 Color selectedColor = getConfig().getColorSelectedRow();
208 Color oddColor = getConfig().getColorAlternateRow();
209 Color readOnlyColor = getConfig().getColorRowReadOnly();
210 Color readOnlySelectedColor = PaintUtils.interpolate(readOnlyColor, selectedColor, 0.25f);
211 Color readOnlyOddColor = PaintUtils.interpolate(readOnlyColor, oddColor, 0.75f);
212 Color invalidColor = getConfig().getColorRowInvalid();
213 Color invalidSelectedColor = PaintUtils.interpolate(invalidColor, selectedColor, 0.25f);
214 Color invalidOddColor = PaintUtils.interpolate(oddColor, invalidColor, 0.25f);
215
216
217 table.addHighlighter(new ApplicationColorHighlighter(
218 new HighlightPredicate.AndHighlightPredicate(
219 HighlightPredicate.ODD,
220 notSelectedPredicate,
221 validPredicate,
222 notColorizedColumnPredicate,
223 editablePredicate),
224 oddColor, false));
225
226
227 table.addHighlighter(new ApplicationColorHighlighter(
228 new HighlightPredicate.AndHighlightPredicate(
229 readOnlyPredicate,
230 notSelectedPredicate,
231 notColorizedColumnPredicate,
232 validPredicate),
233 readOnlyColor, false));
234
235 table.addHighlighter(new ApplicationColorHighlighter(
236 new HighlightPredicate.AndHighlightPredicate(
237 HighlightPredicate.ODD,
238 readOnlyPredicate,
239 notSelectedPredicate,
240 notColorizedColumnPredicate,
241 validPredicate),
242 readOnlyOddColor, false));
243
244
245 table.addHighlighter(new ApplicationColorHighlighter(
246 new HighlightPredicate.AndHighlightPredicate(
247 notSelectedPredicate,
248 notColorizedColumnPredicate,
249 invalidPredicate),
250 invalidColor, false));
251
252 table.addHighlighter(new ApplicationColorHighlighter(
253 new HighlightPredicate.AndHighlightPredicate(
254 HighlightPredicate.ODD,
255 notSelectedPredicate,
256 notColorizedColumnPredicate,
257 invalidPredicate),
258 invalidOddColor, false));
259
260
261 table.addHighlighter(new ApplicationColorHighlighter(
262 selectedPredicate,
263 selectedColor, false));
264 table.addHighlighter(new ApplicationColorHighlighter(
265 readOnlySelectedPredicate,
266 readOnlySelectedColor, false));
267 table.addHighlighter(new ApplicationColorHighlighter(
268 invalidSelectedPredicate,
269 invalidSelectedColor, false));
270
271
272 table.addHighlighter(new BoldFontHighlighter(HighlightPredicate.IS_SELECTED));
273
274
275 Highlighter calculatedHighlighter = ReefDbUIs.newForegroundColorHighlighter(
276 new HighlightPredicate.AndHighlightPredicate(notSelectedPredicate, calculatedPredicate),
277 getConfig().getColorComputedRow());
278 table.addHighlighter(calculatedHighlighter);
279 }
280
281 private void addErrorHighlighters(JXTable table) {
282
283
284 table.addHighlighter(new ErrorHighlighter(table, BorderFactory.createDashedBorder(Color.RED, 1.5f, 4, 4, false), t("reefdb.table.cell.error")) {
285 @Override
286 public List<String> getMessages(ErrorAware bean, String propertyName, Integer pmfmId) {
287 return ReefDbBeans.getErrorMessages(bean, propertyName, pmfmId);
288 }
289 });
290
291
292 table.addHighlighter(new ErrorHighlighter(table, BorderFactory.createDashedBorder(Color.ORANGE, 1.5f, 4, 4, false), t("reefdb.table.cell.warning")) {
293 @Override
294 public List<String> getMessages(ErrorAware bean, String propertyName, Integer pmfmId) {
295 return ReefDbBeans.getWarningMessages(bean, propertyName, pmfmId);
296 }
297 });
298
299 }
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314 @Override
315 protected boolean isRowValid(R row) {
316
317 boolean valid = super.isRowValid(row);
318 if (row instanceof ErrorAware) {
319 ErrorAware errorAwareRow = (ErrorAware) row;
320
321 ReefDbBeans.removeBlockingErrors(errorAwareRow);
322
323 if (!valid) {
324 row.getInvalidMandatoryIdentifiers().forEach(identifier -> {
325 if (identifier instanceof ReefDbPmfmColumnIdentifier) {
326 ReefDbPmfmColumnIdentifier pmfmIdentifier = (ReefDbPmfmColumnIdentifier) identifier;
327 ReefDbBeans.addError(errorAwareRow, t("reefdb.validator.error.field.empty"), pmfmIdentifier.getPmfmId(), pmfmIdentifier.getPropertyName());
328 } else {
329 ReefDbBeans.addError(errorAwareRow, t("reefdb.validator.error.field.empty"), identifier.getPropertyName());
330 }
331 });
332 }
333
334 }
335 return valid;
336 }
337
338 public void ensureColumnsWithErrorAreVisible(Collection<? extends ErrorAware> beans) {
339 if (CollectionUtils.isNotEmpty(beans)) {
340
341 List<TableColumnExt> columns = getTable().getColumns(true).stream()
342 .filter(Objects::nonNull).filter(tableColumn -> !(tableColumn instanceof HiddenColumn))
343 .map(TableColumnExt.class::cast).collect(Collectors.toList());
344
345 Map<String, TableColumnExt> map = Maps.uniqueIndex(columns, column -> {
346 Assert.notNull(column);
347 if (column.getIdentifier() instanceof ReefDbPmfmColumnIdentifier) {
348 ReefDbPmfmColumnIdentifier pmfmColumnIdentifier = (ReefDbPmfmColumnIdentifier) column.getIdentifier();
349 return String.format("%s_%s", pmfmColumnIdentifier.getPropertyName(), pmfmColumnIdentifier.getPmfmId());
350 } else {
351 return ((ColumnIdentifier) column.getIdentifier()).getPropertyName();
352 }
353 });
354 for (ErrorAware bean : beans) {
355 Collection<ErrorDTO> allErrors = CollectionUtils.union(ReefDbBeans.getErrors(bean, null), ReefDbBeans.getWarnings(bean, null));
356 for (ErrorDTO error : allErrors) {
357 for (String propertyName : error.getPropertyName()) {
358 if (map.containsKey(propertyName))
359 map.get(propertyName).setVisible(true);
360 }
361 }
362 }
363 }
364 }
365
366 public void ensureColumnsWithErrorAreVisible(ErrorAware bean) {
367 if (bean != null) {
368 ensureColumnsWithErrorAreVisible(ImmutableList.of(bean));
369 }
370 }
371
372 public ReefDbColumnIdentifier<R> findColumnIdentifierByPropertyName(String propertyName) {
373
374 return getTableModel().getIdentifiers().stream()
375 .map(identifier -> (ReefDbColumnIdentifier<R>) identifier)
376 .filter(identifier -> identifier.getPropertyName().equals(propertyName))
377 .findFirst().orElse(null);
378 }
379
380
381
382
383
384
385 public void selectRowById(int rowId) {
386
387
388 for (final R row : getModel().getRows()) {
389 if (row.getId() == rowId) {
390 selectRow(row);
391 break;
392 }
393 }
394 }
395
396
397
398
399
400
401
402 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName) {
403
404 addPmfmColumns(pmfms, propertyName, null, null);
405
406 }
407
408
409
410
411
412
413
414
415 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName) {
416
417 addPmfmColumns(pmfms, propertyName, decoratorName, null);
418
419 }
420
421
422
423
424
425
426
427
428 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, ReefDbColumnIdentifier<R> insertPosition) {
429
430 addPmfmColumns(pmfms, propertyName, null, insertPosition);
431
432 }
433
434
435
436
437
438
439
440
441
442 @SuppressWarnings("unchecked")
443 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName, ReefDbColumnIdentifier<R> insertColumnPosition) {
444
445
446 removePmfmColumns();
447
448 if (CollectionUtils.isNotEmpty(pmfms)) {
449
450
451 uninstallSortController();
452
453
454 int insertPosition = -1;
455 if (insertColumnPosition != null) {
456
457
458 TableColumnExt insertColumn = getTable().getColumnExt(insertColumnPosition);
459 int modelIndex = insertColumn.getModelIndex();
460 insertPosition = getTable().convertColumnIndexToView(modelIndex);
461
462 while (insertPosition == -1 && modelIndex > 0) {
463 insertPosition = getTable().convertColumnIndexToView(--modelIndex);
464 }
465
466 }
467
468
469 int index = 0;
470
471
472 for (final PmfmDTO pmfm : pmfms) {
473
474
475 TableCellEditor editor;
476 TableCellRenderer renderer;
477
478
479 Class<?> typeClass;
480 if (pmfm.getParameter().isQualitative()) {
481 typeClass = QualitativeValueDTO.class;
482 editor = newExtendedComboBoxCellEditor(new ArrayList<>(pmfm.getQualitativeValues()), QualitativeValueDTO.class, false);
483 renderer = newTableCellRender(QualitativeValueDTO.class);
484
485 } else {
486 typeClass = BigDecimal.class;
487
488 editor = newNumberCellEditor(BigDecimal.class, false, "\\d{0,10}(\\.\\d{0,10})?");
489 renderer = newBigDecimalRenderer();
490 }
491
492
493 String headerLabel = decorate(pmfm, decoratorName);
494 String headerTip = decorate(pmfm);
495
496 ReefDbPmfmColumnIdentifier identifier = ReefDbPmfmColumnIdentifier.newId(
497 propertyName,
498 pmfm,
499 headerLabel,
500 headerTip,
501 typeClass,
502 false);
503
504
505
506 final PmfmTableColumn column = addPmfmColumn(
507 editor,
508 renderer,
509 identifier);
510
511
512 getTableModel().getIdentifiers().add(identifier);
513
514
515 if (insertPosition > -1) {
516 getTable().moveColumn(
517 getTable().convertColumnIndexToView(column.getModelIndex()),
518 insertPosition + 1 + index);
519 }
520
521 index++;
522 }
523
524
525 installSortController();
526
527 }
528 }
529
530 protected void removePmfmColumns() {
531 removeColumns(getModel().getPmfmColumns());
532 getModel().getPmfmColumns().clear();
533 }
534
535
536
537
538
539
540
541
542
543 protected PmfmTableColumn addPmfmColumn(
544 final TableCellEditor editor,
545 final TableCellRenderer renderer,
546 final ReefDbPmfmColumnIdentifier<R> identifier) {
547
548 final PmfmTableColumn column = new PmfmTableColumn(getTable().getColumnModel().getColumnCount(true));
549
550 column.setCellEditor(editor);
551 column.setCellRenderer(renderer);
552
553
554 if (identifier.isMandatory()) {
555 column.setHeaderValue(t("reefdb.table.mandatoryColumn.header", identifier.getHeaderLabel()));
556 column.setToolTipText(t("reefdb.table.mandatoryColumn.tip", identifier.getHeaderLabelTip()));
557 column.setHideable(false);
558 } else {
559 column.setHeaderValue(ReefDbUIs.getHtmlString(identifier.getHeaderLabel()));
560 column.setToolTipText(ReefDbUIs.getHtmlString(identifier.getHeaderLabelTip()));
561 }
562 column.setIdentifier(identifier);
563 column.setHideable(false);
564 column.setSortable(true);
565
566
567 setDefaultColumnMinWidth(column);
568
569 getModel().addPmfmColumn(column);
570 getTable().getColumnModel().addColumn(column);
571 return column;
572 }
573
574 protected PmfmTableColumn findPmfmColumnByPmfmId(List<PmfmTableColumn> pmfmTableColumns, int pmfmId) {
575
576 if (pmfmTableColumns != null) {
577 for (PmfmTableColumn pmfmTableColumn : pmfmTableColumns) {
578 if (pmfmTableColumn.getPmfmId() == pmfmId) return pmfmTableColumn;
579 }
580 }
581
582 return null;
583 }
584
585 private class BoldFontHighlighter extends AbstractHighlighter {
586
587 BoldFontHighlighter(HighlightPredicate predicate) {
588 super(predicate);
589 }
590
591 @Override
592 protected Component doHighlight(Component component, ComponentAdapter adapter) {
593 if (component.getFont().isItalic()) {
594 component.setFont(component.getFont().deriveFont(Font.BOLD + Font.ITALIC));
595 } else {
596 component.setFont(component.getFont().deriveFont(Font.BOLD));
597 }
598 return component;
599 }
600 }
601
602 @SafeVarargs
603 protected final void addExportToCSVAction(String tableName, ReefDbColumnIdentifier<R>... columnIdentifiersToIgnore) {
604 ExportToCSVAction<M, UI, ?> exportAction = new ExportToCSVAction<M, UI, AbstractReefDbTableUIHandler<?, M, UI>>(
605 this,
606 tableName,
607 columnIdentifiersToIgnore);
608 Action tableAction = getContext().getActionFactory().createUIAction(null, exportAction);
609 tableAction.putValue(Action.NAME, exportAction.getActionDescription());
610 Icon actionIcon = SwingUtil.createActionIcon("export");
611 tableAction.putValue(Action.SMALL_ICON, actionIcon);
612 tableAction.putValue(Action.LARGE_ICON_KEY, actionIcon);
613 tableAction.putValue(AdditionalTableActions.ACTION_TARGET_GROUP, 1);
614 getAdditionalTableActions().addAction(tableAction);
615 }
616
617 public void addEditionPanelBorder() {
618 addEditionPanelBorder(JScrollPane.class);
619 }
620
621 public void addEditionPanelBorder(Class<?> ancestorClass) {
622 Container container = SwingUtilities.getAncestorOfClass(ancestorClass, getTable());
623 if (container instanceof JComponent) {
624 ((JComponent) container).setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, getConfig().getColorEditionPanelBorder()));
625 }
626 }
627
628 }