View Javadoc
1   package fr.ifremer.dali.ui.swing.util.table;
2   
3   /*
4    * #%L
5    * Dali :: 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.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   * <p>Abstract AbstractDaliTableUIHandler class.</p>
73   *
74   * @param <R>  type of a row
75   * @param <M>  type of the ui model
76   * @param <UI> type of the ui
77   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
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      // initial border of full screen toggle button
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          // Assume a single selection mode is over a checkbox selection
94          if (singleSelection && checkBoxSelection) {
95              checkBoxSelection = false;
96          }
97  
98          // Main Init
99          super.initTable(table, singleSelection, checkBoxSelection);
100 
101         addErrorHighlighters(table);
102         if (getFixedTable() != null) {
103             addErrorHighlighters(getFixedTable());
104         }
105 
106         // Add default renderer if not already exists
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         /* PREDICATES */
124         HighlightPredicate notSelectedPredicate = new HighlightPredicate.NotHighlightPredicate(HighlightPredicate.IS_SELECTED);
125         HighlightPredicate invalidPredicate = (renderer, adapter) -> {
126             boolean result = false;
127             AbstractDaliRowUIModel row = null;
128 
129 //                if (table instanceof JXTreeTable) {
130 //                    JXTreeTable treeTable = (JXTreeTable) table;
131 //                    TreePath path = treeTable.getPathForRow(adapter.row);
132 //                    row = ((AbstractDaliTreeTableNode<?>) 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 = (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         // predicate for calculated row
164         HighlightPredicate calculatedPredicate = (renderer, adapter) -> {
165             AbstractDaliRowUIModel row;
166 
167 //                if (table instanceof JXTreeTable) {
168 //                    JXTreeTable treeTable = (JXTreeTable) table;
169 //                    TreePath path = treeTable.getPathForRow(adapter.row);
170 //                    row = ((AbstractDaliTreeTableNode) path.getLastPathComponent()).getUserObject();
171 //                } else {
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         // 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 = 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         // add error highlighter
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         // add warning highlighter
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             // Get all columns
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             // Build map of columns indexed by property name
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      * <p>addAttachementHighlighter.</p>
371      *
372      * @param table      a {@link SwingTable} object.
373      * @param identifier a {@link DaliColumnIdentifier} object.
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                         // for not null value
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      * Select a row by its ID
392      *
393      * @param rowId the row id to select
394      */
395     public void selectRowById(int rowId) {
396 
397         // Selection de la ligne du tableau
398         for (final R row : getModel().getRows()) {
399             if (row.getId() == rowId) {
400                 selectRow(row);
401                 break;
402             }
403         }
404     }
405 
406     /**
407      * Ajouter des colonnes dynamiques en utilisant le decorateur de PMFM par défaut.
408      *
409      * @param pmfms        La liste des psfm
410      * @param propertyName Le nom de la propriete des Psfms
411      */
412     protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName) {
413 
414         addPmfmColumns(pmfms, propertyName, null, null);
415 
416     }
417 
418     /**
419      * <p>addPmfmColumns.</p>
420      *
421      * @param pmfms         a {@link java.util.Collection} object.
422      * @param propertyName  a {@link java.lang.String} object.
423      * @param decoratorName a {@link java.lang.String} object.
424      */
425     protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName) {
426 
427         addPmfmColumns(pmfms, propertyName, decoratorName, null);
428 
429     }
430 
431     /**
432      * Ajouter des colonnes dynamiques en utilisant le decorateur de PMFM par défaut.
433      *
434      * @param pmfms          La liste des psfm
435      * @param propertyName   Le nom de la propriete des Psfms
436      * @param insertPosition a {@link DaliColumnIdentifier} object.
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      * Ajouter des colonnes dynamiques.
446      *
447      * @param pmfms                La liste des psfm
448      * @param propertyName         Le nom de la propriete des Psfms
449      * @param decoratorName        Le contexte du decorateur de PMFM
450      * @param insertColumnPosition identifiant de la colonne après laquelle les colonnes dynamiques seront placées
451      */
452     @SuppressWarnings("unchecked")
453     protected void addPmfmColumns(Collection<PmfmDTO> pmfms, String propertyName, String decoratorName, DaliColumnIdentifier<R> insertColumnPosition) {
454 
455         // First, remove old dynamic columns
456         removePmfmColumns();
457 
458         if (CollectionUtils.isNotEmpty(pmfms)) {
459 
460             // before add the column, reset row sorter to prevent exceptions
461             uninstallSortController();
462 
463             // compute insert position
464             int insertPosition = -1;
465             if (insertColumnPosition != null) {
466 
467                 // find view index of the specified identifier
468                 TableColumnExt insertColumn = getTable().getColumnExt(insertColumnPosition);
469                 int modelIndex = insertColumn.getModelIndex();
470                 insertPosition = getTable().convertColumnIndexToView(modelIndex);
471                 // while the column is not visible, try to find a visible column in left direction
472                 while (insertPosition == -1 && modelIndex > 0) {
473                     insertPosition = getTable().convertColumnIndexToView(--modelIndex);
474                 }
475 
476             }
477 
478             // pmfm index
479             int index = 0;
480 
481             // create a column for each pmfm
482             for (final PmfmDTO pmfm : pmfms) {
483 
484                 // Create corresponding editor and renderer
485                 TableCellEditor editor;
486                 TableCellRenderer renderer; // default table render should be used
487 
488                 // Define value type
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                     // an arbitrary pattern for BigDecimal input
498                     editor = newNumberCellEditor(BigDecimal.class, false, "\\d{0,10}(\\.\\d{0,10})?");
499                     renderer = newBigDecimalRenderer();
500                 }
501 
502                 // Column header
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                 // mantis #25139 : all PMFM columns must not be mandatory
514 
515                 // Create dynamic column
516                 final PmfmTableColumn column = addPmfmColumn(
517                         editor,
518                         renderer,
519                         identifier);
520 
521                 // Add new identifier
522                 getTableModel().getIdentifiers().add(identifier);
523 
524                 // Move move if insertPosition is set
525                 if (insertPosition > -1) {
526                     getTable().moveColumn(
527                             getTable().convertColumnIndexToView(column.getModelIndex()),
528                             insertPosition + 1 + index);
529                 }
530 
531                 index++;
532             }
533 
534             // recreate row sorter
535             installSortController();
536 
537         }
538     }
539 
540     protected void removePmfmColumns() {
541         removeColumns(getModel().getPmfmColumns());
542         // Reset pmfm columns in model to avoid duplication (Mantis #51850)
543         getModel().getPmfmColumns().clear();
544     }
545 
546     /**
547      * <p>addPmfmColumn.</p>
548      *
549      * @param editor     a {@link javax.swing.table.TableCellEditor} object.
550      * @param renderer   a {@link javax.swing.table.TableCellRenderer} object.
551      * @param identifier a {@link DaliPmfmColumnIdentifier} object.
552      * @return a {@link fr.ifremer.dali.ui.swing.util.table.PmfmTableColumn} object.
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         // if the column is mandatory, override headerValue and toolTip
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         // set default pmfm column min width (Mantis #52362)
576         setDefaultColumnMinWidth(column);
577         // Add the new column in UI model before add it to Table model
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             // keep its initial border
621             initialToggleButtonBorder = toggleButton.getBorder();
622         }
623 
624         super.toggleFullScreen(panel, toggleButton);
625 
626         // Set highlight border when selected (Mantis #48586)
627         toggleButton.setBorder(
628             toggleButton.isSelected()
629                 ? BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, getConfig().getColorHighlightButtonBorder()), initialToggleButtonBorder)
630                 : initialToggleButtonBorder
631         );
632     }
633 }