1 package fr.ifremer.dali.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.dali.config.DaliConfiguration;
29 import fr.ifremer.dali.dto.DaliBeans;
30 import fr.ifremer.dali.dto.ErrorAware;
31 import fr.ifremer.dali.dto.ErrorDTO;
32 import fr.ifremer.dali.dto.referential.pmfm.PmfmDTO;
33 import fr.ifremer.dali.dto.referential.pmfm.QualitativeValueDTO;
34 import fr.ifremer.dali.ui.swing.DaliUIContext;
35 import fr.ifremer.dali.ui.swing.util.DaliUI;
36 import fr.ifremer.dali.ui.swing.util.table.export.ExportToCSVAction;
37 import fr.ifremer.quadrige3.core.dao.technical.Assert;
38 import fr.ifremer.quadrige3.ui.swing.ApplicationUIUtil;
39 import fr.ifremer.quadrige3.ui.swing.table.AbstractTableUIHandler;
40 import fr.ifremer.quadrige3.ui.swing.table.ColumnIdentifier;
41 import fr.ifremer.quadrige3.ui.swing.table.HiddenColumn;
42 import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
43 import fr.ifremer.quadrige3.ui.swing.table.action.AdditionalTableActions;
44 import fr.ifremer.quadrige3.ui.swing.table.renderer.ColorCheckBoxRenderer;
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.border.Border;
59 import javax.swing.table.TableCellEditor;
60 import javax.swing.table.TableCellRenderer;
61 import java.awt.Color;
62 import java.awt.Component;
63 import java.awt.Container;
64 import java.awt.Font;
65 import java.math.BigDecimal;
66 import java.util.*;
67 import java.util.stream.Collectors;
68
69 import static org.nuiton.i18n.I18n.t;
70
71
72
73
74
75
76
77
78
79 public abstract class AbstractDaliTableUIHandler<R extends AbstractDaliRowUIModel<?, ?>, M extends AbstractDaliTableUIModel<?, R, M>, UI extends DaliUI<M, ?>>
80 extends AbstractTableUIHandler<R, M, UI> {
81
82
83 private Border initialToggleButtonBorder;
84
85 public AbstractDaliTableUIHandler(String... properties) {
86 super(properties);
87 }
88
89 @Override
90 protected void initTable(SwingTable table, boolean singleSelection, boolean checkBoxSelection) {
91
92 checkBoxSelection |= getConfig().isShowTableCheckbox();
93
94 if (singleSelection && checkBoxSelection) {
95 checkBoxSelection = false;
96 }
97
98
99 super.initTable(table, singleSelection, checkBoxSelection);
100
101 addErrorHighlighters(table);
102 if (getFixedTable() != null) {
103 addErrorHighlighters(getFixedTable());
104 }
105
106
107 table.setDefaultRenderer(QualitativeValueDTO.class, newTableCellRender(QualitativeValueDTO.class));
108
109 }
110
111 @Override
112 public DaliUIContext getContext() {
113 return (DaliUIContext) super.getContext();
114 }
115
116 @Override
117 public DaliConfiguration getConfig() {
118 return (DaliConfiguration) super.getConfig();
119 }
120
121 @Override
122 protected void addHighlighters(JXTable table) {
123
124 HighlightPredicate notSelectedPredicate = new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED);
125 HighlightPredicate invalidPredicate = (renderer, adapter) -> {
126 boolean result = false;
127 AbstractDaliRowUIModel 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 = (AbstractDaliRowUIModel) 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 AbstractDaliRowUIModel row;
166
167
168
169
170
171
172 AbstractApplicationTableModel model = (AbstractApplicationTableModel) table.getModel();
173 int modelRow = adapter.convertRowIndexToModel(adapter.row);
174 row = (AbstractDaliRowUIModel) 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 = ApplicationUIUtil.newForegroundColorHighlighter(
276 new HighlightPredicate.AndHighlightPredicate(notSelectedPredicate, calculatedPredicate),
277 getConfig().getColorComputedRow());
278 table.addHighlighter(calculatedHighlighter);
279
280 }
281
282 private void addErrorHighlighters(SwingTable table) {
283
284
285 table.addHighlighter(new ErrorHighlighter(table, BorderFactory.createDashedBorder(Color.RED, 1.5f, 4, 4, false), t("dali.table.cell.error")) {
286 @Override
287 public List<String> getMessages(ErrorAware bean, String propertyName, Integer pmfmId) {
288 return DaliBeans.getErrorMessages(bean, propertyName, pmfmId);
289 }
290 });
291
292
293 table.addHighlighter(new ErrorHighlighter(table, BorderFactory.createDashedBorder(Color.ORANGE, 1.5f, 4, 4, false), t("dali.table.cell.warning")) {
294 @Override
295 public List<String> getMessages(ErrorAware bean, String propertyName, Integer pmfmId) {
296 return DaliBeans.getWarningMessages(bean, propertyName, pmfmId);
297 }
298 });
299
300 }
301
302 @Override
303 protected boolean isRowValid(R row) {
304
305 boolean valid = super.isRowValid(row);
306 if (row instanceof ErrorAware) {
307 ErrorAware errorAwareRow = (ErrorAware) row;
308 DaliBeans.removeBlockingErrors(errorAwareRow);
309
310 if (!valid) {
311 row.getInvalidMandatoryIdentifiers().forEach(identifier -> {
312 if (identifier instanceof DaliPmfmColumnIdentifier) {
313 DaliPmfmColumnIdentifier pmfmIdentifier = (DaliPmfmColumnIdentifier) identifier;
314 DaliBeans.addError(errorAwareRow, t("dali.validator.error.field.empty"), pmfmIdentifier.getPmfmId(), pmfmIdentifier.getPropertyName());
315 } else {
316 DaliBeans.addError(errorAwareRow, t("dali.validator.error.field.empty"), identifier.getPropertyName());
317 }
318 });
319 }
320 }
321 return valid;
322 }
323
324 public void ensureColumnsWithErrorAreVisible(Collection<? extends ErrorAware> beans) {
325 if (CollectionUtils.isNotEmpty(beans)) {
326
327 List<TableColumnExt> columns = getTable().getColumns(true).stream()
328 .filter(Objects::nonNull).filter(tableColumn -> !(tableColumn instanceof HiddenColumn))
329 .map(TableColumnExt.class::cast).collect(Collectors.toList());
330
331 Map<String, TableColumnExt> map = Maps.uniqueIndex(columns, column -> {
332 Assert.notNull(column);
333 if (column.getIdentifier() instanceof DaliPmfmColumnIdentifier) {
334 DaliPmfmColumnIdentifier pmfmColumnIdentifier = (DaliPmfmColumnIdentifier) column.getIdentifier();
335 return String.format("%s_%s", pmfmColumnIdentifier.getPropertyName(), pmfmColumnIdentifier.getPmfmId());
336 } else {
337 return ((ColumnIdentifier) column.getIdentifier()).getPropertyName();
338 }
339 });
340 for (ErrorAware bean : beans) {
341 Collection<ErrorDTO> allErrors = CollectionUtils.union(DaliBeans.getErrors(bean, null), DaliBeans.getWarnings(bean, null));
342 for (ErrorDTO error : allErrors) {
343 for (String propertyName : error.getPropertyName()) {
344 if (map.containsKey(propertyName))
345 map.get(propertyName).setVisible(true);
346 }
347 }
348 }
349 }
350 }
351
352 public void ensureColumnsWithErrorAreVisible(ErrorAware bean) {
353 if (bean != null) {
354 ensureColumnsWithErrorAreVisible(ImmutableList.of(bean));
355 }
356 }
357
358 public void addEditionPanelBorder() {
359 addEditionPanelBorder(JScrollPane.class);
360 }
361
362 public void addEditionPanelBorder(Class<?> ancestorClass) {
363 Container container = SwingUtilities.getAncestorOfClass(ancestorClass, getTable());
364 if (container instanceof JComponent) {
365 ((JComponent) container).setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, getConfig().getColorEditionPanelBorder()));
366 }
367 }
368
369
370
371
372
373
374
375 protected void addAttachementHighlighter(SwingTable table, DaliColumnIdentifier<?> identifier) {
376 Color cellWithValueColor = getConfig().getColorCellWithValue();
377
378 Highlighter attachmentHighlighter = ApplicationUIUtil.newBackgroundColorHighlighter(
379 new HighlightPredicate.AndHighlightPredicate(
380 new HighlightPredicate.IdentifierHighlightPredicate(identifier),
381
382 (renderer, adapter) -> {
383 Collection attachments = (Collection) adapter.getValue();
384 return CollectionUtils.isNotEmpty(attachments);
385 }
386 ), cellWithValueColor);
387 table.addHighlighter(attachmentHighlighter);
388 }
389
390
391
392
393
394
395 public void selectRowById(int rowId) {
396
397
398 for (final R row : getModel().getRows()) {
399 if (row.getId() == rowId) {
400 selectRow(row);
401 break;
402 }
403 }
404 }
405
406
407
408
409
410
411
412 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName) {
413
414 addPmfmColumns(pmfms, propertyName, null, null);
415
416 }
417
418
419
420
421
422
423
424
425 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName) {
426
427 addPmfmColumns(pmfms, propertyName, decoratorName, null);
428
429 }
430
431
432
433
434
435
436
437
438 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, DaliColumnIdentifier<R> insertPosition) {
439
440 addPmfmColumns(pmfms, propertyName, null, insertPosition);
441
442 }
443
444
445
446
447
448
449
450
451
452 @SuppressWarnings("unchecked")
453 protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName, DaliColumnIdentifier<R> insertColumnPosition) {
454
455
456 removePmfmColumns();
457
458 if (CollectionUtils.isNotEmpty(pmfms)) {
459
460
461 uninstallSortController();
462
463
464 int insertPosition = -1;
465 if (insertColumnPosition != null) {
466
467
468 TableColumnExt insertColumn = getTable().getColumnExt(insertColumnPosition);
469 int modelIndex = insertColumn.getModelIndex();
470 insertPosition = getTable().convertColumnIndexToView(modelIndex);
471
472 while (insertPosition == -1 && modelIndex > 0) {
473 insertPosition = getTable().convertColumnIndexToView(--modelIndex);
474 }
475
476 }
477
478
479 int index = 0;
480
481
482 for (final PmfmDTO pmfm : pmfms) {
483
484
485 TableCellEditor editor;
486 TableCellRenderer renderer;
487
488
489 Class<?> typeClass;
490 if (pmfm.getParameter().isQualitative()) {
491 typeClass = QualitativeValueDTO.class;
492 editor = newExtendedComboBoxCellEditor(new ArrayList<>(pmfm.getQualitativeValues()), QualitativeValueDTO.class, false);
493 renderer = newTableCellRender(QualitativeValueDTO.class);
494
495 } else {
496 typeClass = BigDecimal.class;
497
498 editor = newNumberCellEditor(BigDecimal.class, false, "\\d{0,10}(\\.\\d{0,10})?");
499 renderer = newBigDecimalRenderer();
500 }
501
502
503 String headerLabel = decorate(pmfm, decoratorName);
504 String headerTip = decorate(pmfm);
505
506 DaliPmfmColumnIdentifier identifier = DaliPmfmColumnIdentifier.newId(
507 propertyName,
508 pmfm,
509 headerLabel,
510 headerTip,
511 typeClass,
512 false);
513
514
515
516 final PmfmTableColumn column = addPmfmColumn(
517 editor,
518 renderer,
519 identifier);
520
521
522 getTableModel().getIdentifiers().add(identifier);
523
524
525 if (insertPosition > -1) {
526 getTable().moveColumn(
527 getTable().convertColumnIndexToView(column.getModelIndex()),
528 insertPosition + 1 + index);
529 }
530
531 index++;
532 }
533
534
535 installSortController();
536
537 }
538 }
539
540 protected void removePmfmColumns() {
541 removeColumns(getModel().getPmfmColumns());
542
543 getModel().getPmfmColumns().clear();
544 }
545
546
547
548
549
550
551
552
553
554 protected PmfmTableColumn addPmfmColumn(
555 final TableCellEditor editor,
556 final TableCellRenderer renderer,
557 final DaliPmfmColumnIdentifier<R> identifier) {
558
559 final PmfmTableColumn column = new PmfmTableColumn(getTable().getColumnModel().getColumnCount(true));
560
561 column.setCellEditor(editor);
562 column.setCellRenderer(renderer);
563
564
565 if (identifier.isMandatory()) {
566 column.setHeaderValue(t("dali.table.mandatoryColumn.header", identifier.getHeaderLabel()));
567 column.setToolTipText(t("dali.table.mandatoryColumn.tip", identifier.getHeaderLabelTip()));
568 } else {
569 column.setHeaderValue(ApplicationUIUtil.getHtmlString(identifier.getHeaderLabel()));
570 column.setToolTipText(ApplicationUIUtil.getHtmlString(identifier.getHeaderLabelTip()));
571 }
572 column.setIdentifier(identifier);
573 column.setHideable(false);
574 column.setSortable(true);
575
576 setDefaultColumnMinWidth(column);
577
578 getModel().addPmfmColumn(column);
579 getTable().getColumnModel().addColumn(column);
580 return column;
581 }
582
583 private class BoldFontHighlighter extends AbstractHighlighter {
584
585 BoldFontHighlighter(HighlightPredicate predicate) {
586 super(predicate);
587 }
588
589 @Override
590 protected Component doHighlight(Component component, ComponentAdapter adapter) {
591 if (component.getFont().isItalic()) {
592 component.setFont(component.getFont().deriveFont(Font.BOLD + Font.ITALIC));
593 } else {
594 component.setFont(component.getFont().deriveFont(Font.BOLD));
595 }
596 return component;
597 }
598 }
599
600 @SafeVarargs
601 protected final void addExportToCSVAction(String tableName, DaliColumnIdentifier<R>... columnIdentifiersToIgnore) {
602 ExportToCSVAction<M, UI, ?> exportAction = new ExportToCSVAction<M, UI, AbstractDaliTableUIHandler<?, M, UI>>(
603 this,
604 tableName,
605 columnIdentifiersToIgnore
606 );
607 Action tableAction = getContext().getActionFactory().createUIAction(null, exportAction);
608 tableAction.putValue(Action.NAME, exportAction.getActionDescription());
609 Icon actionIcon = SwingUtil.createActionIcon("export");
610 tableAction.putValue(Action.SMALL_ICON, actionIcon);
611 tableAction.putValue(Action.LARGE_ICON_KEY, actionIcon);
612 tableAction.putValue(AdditionalTableActions.ACTION_TARGET_GROUP, 1);
613 getAdditionalTableActions().addAction(tableAction);
614 }
615
616 @Override
617 public void toggleFullScreen(JPanel panel, JToggleButton toggleButton) {
618
619 if (toggleButton.isSelected()) {
620
621 initialToggleButtonBorder = toggleButton.getBorder();
622 }
623
624 super.toggleFullScreen(panel, toggleButton);
625
626
627 toggleButton.setBorder(
628 toggleButton.isSelected()
629 ? BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, getConfig().getColorHighlightButtonBorder()), initialToggleButtonBorder)
630 : initialToggleButtonBorder
631 );
632 }
633 }