View Javadoc
1   package fr.ifremer.reefdb.ui.swing.util.table;
2   
3   /*
4    * #%L
5    * Reef DB :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 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.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   * <p>Abstract AbstractReefDbTableUIHandler class.</p>
72   *
73   * @param <R>  type of a row
74   * @param <M>  type of the ui model
75   * @param <UI> type of the ui
76   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
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       * <p>Constructor for AbstractReefDbTableUIHandler.</p>
83       *
84       * @param properties a {@link java.lang.String} object.
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      * <p>initTable.</p>
102      *  @param table a {@link SwingTable} object.
103      * @param forceSingleSelection a boolean.
104      * @param checkBoxSelection a boolean.
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         // Add default renderer if not already exists
113         table.setDefaultRenderer(QualitativeValueDTO.class, newTableCellRender(QualitativeValueDTO.class));
114 
115     }
116 
117     /** {@inheritDoc}
118      */
119     @Override
120     protected void addHighlighters(final JXTable table) {
121 
122         /* PREDICATES */
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 //                if (table instanceof JXTreeTable) {
130 //                    JXTreeTable treeTable = (JXTreeTable) table;
131 //                    TreePath path = treeTable.getPathForRow(adapter.row);
132 //                    row = ((AbstractReefDbTreeTableNode<?>) path.getLastPathComponent()).getUserObject();
133 //                } else {
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         // predicate for calculated row
164         HighlightPredicate calculatedPredicate = (renderer, adapter) -> {
165             AbstractReefDbRowUIModel row;
166 
167 //                if (table instanceof JXTreeTable) {
168 //                    JXTreeTable treeTable = (JXTreeTable) table;
169 //                    TreePath path = treeTable.getPathForRow(adapter.row);
170 //                    row = ((AbstractReefDbTreeTableNode) path.getLastPathComponent()).getUserObject();
171 //                } else {
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         // specific case on hierarchical column of a JXTreeTable
181 //        if (table instanceof JXTreeTable) {
182 //            JXTreeTable treeTable = (JXTreeTable) table;
183 //            HighlightPredicate hierarchicalColumn = new HighlightPredicate.ColumnHighlightPredicate(treeTable.getTreeTableModel()
184 //                    .getHierarchicalColumn());
185 //            HighlightPredicate notHierarchicalColumn = new HighlightPredicate.NotHighlightPredicate(hierarchicalColumn);
186 //
187 //            // modify some predicate to avoid highlight on hierarchical column
188 //            selectedPredicate = new HighlightPredicate.AndHighlightPredicate(selectedPredicate, notHierarchicalColumn);
189 //
190 //            readOnlyPredicate = new HighlightPredicate.AndHighlightPredicate(readOnlyPredicate, notHierarchicalColumn);
191 //            readOnlyPredicate = new HighlightPredicate.OrHighlightPredicate(readOnlyPredicate,
192 //                    new HighlightPredicate.AndHighlightPredicate(calculatedPredicate, hierarchicalColumn));
193 //
194 //            readOnlySelectedPredicate = new HighlightPredicate.AndHighlightPredicate(readOnlySelectedPredicate, notHierarchicalColumn);
195 //            invalidSelectedPredicate = new HighlightPredicate.AndHighlightPredicate(invalidSelectedPredicate, notHierarchicalColumn);
196 //
197 //            // modify editable predicate to simulate an editable hierarchical column but is really read only by the
198 //            // model)
199 //            editablePredicate = new HighlightPredicate.OrHighlightPredicate(editablePredicate, hierarchicalColumn);
200 //
201 //            // add a fake border
202 //            Border border = new BorderUIResource.MatteBorderUIResource(0, 0, 0, 1, table.getBackground());
203 //            table.addHighlighter(new BorderHighlighter(notHierarchicalColumn, border));
204 //        }
205 
206         /* COLORS */
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         /* NORMAL ODD ROW */
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         /* READ ONLY ROW */
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         /* INVALID ROW */
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         /* SELECTED ROW */
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         // paint in a special font selected rows
272         table.addHighlighter(new BoldFontHighlighter(HighlightPredicate.IS_SELECTED));
273 
274         /* CALCULATED ROW */
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         // add error highlighter
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         // add warning highlighter
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     /* LP #50538
302     @Override
303     protected void onRowModified(int rowIndex, R row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
304 
305         if (row instanceof ErrorAware) {
306             ((ErrorAware) row).getErrors().clear();
307         }
308 
309         super.onRowModified(rowIndex, row, propertyName, propertyIndex, oldValue, newValue);
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             // Get all columns
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             // Build map of columns indexed by property name
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      * Select a row by its ID
382      *
383      * @param rowId the row id to select
384      */
385     public void selectRowById(int rowId) {
386 
387         // Selection de la ligne du tableau
388         for (final R row : getModel().getRows()) {
389             if (row.getId() == rowId) {
390                 selectRow(row);
391                 break;
392             }
393         }
394     }
395 
396     /**
397      * Ajouter des colonnes dynamiques en utilisant le decorateur de PMFM par défaut.
398      *
399      * @param pmfms        La liste des psfm
400      * @param propertyName Le nom de la propriete des Psfms
401      */
402     protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName) {
403 
404         addPmfmColumns(pmfms, propertyName, null, null);
405 
406     }
407 
408     /**
409      * <p>addPmfmColumns.</p>
410      *
411      * @param pmfms a {@link java.util.Collection} object.
412      * @param propertyName a {@link java.lang.String} object.
413      * @param decoratorName a {@link java.lang.String} object.
414      */
415     protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName) {
416 
417         addPmfmColumns(pmfms, propertyName, decoratorName, null);
418 
419     }
420 
421     /**
422      * Ajouter des colonnes dynamiques en utilisant le decorateur de PMFM par défaut.
423      *
424      * @param pmfms        La liste des psfm
425      * @param propertyName Le nom de la propriete des Psfms
426      * @param insertPosition a {@link ReefDbColumnIdentifier} object.
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      * Ajouter des colonnes dynamiques.
436      *
437      * @param pmfms                La liste des psfm
438      * @param propertyName         Le nom de la propriete des Psfms
439      * @param decoratorName        Le contexte du decorateur de PMFM
440      * @param insertColumnPosition identifiant de la colonne après laquelle les colonnes dynamiques seront placées
441      */
442     @SuppressWarnings("unchecked")
443     protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName, ReefDbColumnIdentifier<R> insertColumnPosition) {
444 
445         // First, remove old dynamic columns
446         removePmfmColumns();
447 
448         if (CollectionUtils.isNotEmpty(pmfms)) {
449 
450             // before add the column, reset row sorter to prevent exceptions
451             uninstallSortController();
452 
453             // compute insert position
454             int insertPosition = -1;
455             if (insertColumnPosition != null) {
456 
457                 // find view index of the specified identifier
458                 TableColumnExt insertColumn = getTable().getColumnExt(insertColumnPosition);
459                 int modelIndex = insertColumn.getModelIndex();
460                 insertPosition = getTable().convertColumnIndexToView(modelIndex);
461                 // while the column is not visible, try to find a visible column in left direction
462                 while (insertPosition == -1 && modelIndex > 0) {
463                     insertPosition = getTable().convertColumnIndexToView(--modelIndex);
464                 }
465 
466             }
467 
468             // pmfm index
469             int index = 0;
470 
471             // create a column for each pmfm
472             for (final PmfmDTO pmfm : pmfms) {
473 
474                 // Create corresponding editor and renderer
475                 TableCellEditor editor;
476                 TableCellRenderer renderer; // default table render should be used
477 
478                 // Define value type
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                     // an arbitrary pattern for BigDecimal input
488                     editor = newNumberCellEditor(BigDecimal.class, false, "\\d{0,10}(\\.\\d{0,10})?");
489                     renderer = newBigDecimalRenderer();
490                 }
491 
492                 // Column header
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                 // mantis #25139 : all PMFM columns must not be mandatory
504 
505                 // Create dynamic column
506                 final PmfmTableColumn column = addPmfmColumn(
507                         editor,
508                         renderer,
509                         identifier);
510 
511                 // Add new identifier
512                 getTableModel().getIdentifiers().add(identifier);
513 
514                 // Move move if insertPosition is set
515                 if (insertPosition > -1) {
516                     getTable().moveColumn(
517                             getTable().convertColumnIndexToView(column.getModelIndex()),
518                             insertPosition + 1 + index);
519                 }
520 
521                 index++;
522             }
523 
524             // recreate row sorter
525             installSortController();
526 
527         }
528     }
529 
530     protected void removePmfmColumns() {
531         removeColumns(getModel().getPmfmColumns());
532         getModel().getPmfmColumns().clear();
533     }
534 
535     /**
536      * <p>addPmfmColumn.</p>
537      *
538      * @param editor a {@link javax.swing.table.TableCellEditor} object.
539      * @param renderer a {@link javax.swing.table.TableCellRenderer} object.
540      * @param identifier a {@link ReefDbPmfmColumnIdentifier} object.
541      * @return a {@link fr.ifremer.reefdb.ui.swing.util.table.PmfmTableColumn} object.
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         // if the column is mandatory, override headerValue and toolTip
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 //        column.setMinWidth(150);
566         // set default pmfm column min width (Mantis #50452)
567         setDefaultColumnMinWidth(column);
568         // Add the new column in UI model before add it to Table model
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 }