View Javadoc
1   package fr.ifremer.quadrige2.ui.swing.common.table;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 UI Common
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 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.base.Preconditions;
27  import com.google.common.collect.Sets;
28  import fr.ifremer.quadrige2.core.dao.technical.decorator.DecoratorComparator;
29  import fr.ifremer.quadrige2.core.service.ClientServiceLocator;
30  import fr.ifremer.quadrige2.core.service.decorator.DecoratorService;
31  import fr.ifremer.quadrige2.ui.swing.common.AbstractUIHandler;
32  import fr.ifremer.quadrige2.ui.swing.common.ApplicationUI;
33  import fr.ifremer.quadrige2.ui.swing.common.model.BeanMonitor;
34  import fr.ifremer.quadrige2.ui.swing.common.table.action.AbstractCellSelectionAction;
35  import fr.ifremer.quadrige2.ui.swing.common.table.action.NextCellSelectionAction;
36  import fr.ifremer.quadrige2.ui.swing.common.table.action.NextRowSelectionAction;
37  import fr.ifremer.quadrige2.ui.swing.common.table.action.PreviousCellSelectionAction;
38  import fr.ifremer.quadrige2.ui.swing.common.table.editor.DatePickerCellEditor;
39  import fr.ifremer.quadrige2.ui.swing.common.table.editor.DatePickerCellEditor2;
40  import fr.ifremer.quadrige2.ui.swing.common.table.editor.StringGenericCellEditor;
41  import fr.ifremer.quadrige2.ui.swing.common.table.renderer.DateCellRenderer;
42  import fr.ifremer.quadrige2.ui.swing.common.table.renderer.ExtendedDecoratorCellRenderer;
43  import org.apache.commons.collections4.CollectionUtils;
44  import org.apache.commons.collections4.comparators.ComparableComparator;
45  import org.apache.commons.lang3.ArrayUtils;
46  import org.apache.commons.lang3.StringUtils;
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  import org.jdesktop.swingx.JXTable;
50  import org.jdesktop.swingx.table.TableColumnExt;
51  import org.nuiton.decorator.Decorator;
52  
53  import javax.swing.JComponent;
54  import javax.swing.KeyStroke;
55  import javax.swing.ListSelectionModel;
56  import javax.swing.SwingUtilities;
57  import javax.swing.event.*;
58  import javax.swing.table.*;
59  import java.awt.Component;
60  import java.beans.IndexedPropertyChangeEvent;
61  import java.beans.PropertyChangeEvent;
62  import java.beans.PropertyChangeListener;
63  import java.text.Collator;
64  import java.util.Comparator;
65  import java.util.Date;
66  import java.util.List;
67  import java.util.Set;
68  
69  /**
70   * <p>Abstract AbstractTableUIHandler class.</p>
71   *
72   * @param <R>  type of a row
73   * @param <M>  type of the ui model
74   * @param <UI> type of the ui
75   * @author Ludovic Pecquot <ludovic.pecquot@e-is.pro>
76   */
77  public abstract class AbstractTableUIHandler<R extends AbstractRowUIModel<?, ?>, M extends AbstractTableUIModel<?, R, M>, UI extends ApplicationUI<M, ?>>
78          extends AbstractUIHandler<M, UI> {
79  
80      /**
81       * Constant <code>DEFAULT_ROW_HEIGHT=24</code>
82       */
83      public static final int DEFAULT_ROW_HEIGHT = 24;
84      /**
85       * Minimun width of a column
86       */
87      protected static final int DEFAULT_MIN_COLUMN_WIDTH = 80;
88      /**
89       * Logger.
90       */
91      private static final Log LOG = LogFactory.getLog(AbstractTableUIHandler.class);
92      /**
93       * Monitor the selected row (save it only if something has changed).
94       *
95       * @since 0.2
96       */
97      private final BeanMonitor<R> rowMonitor;
98      //------------------------------------------------------------------------//
99      //-- Internal methods (listener methods)                                --//
100     //------------------------------------------------------------------------//
101     private ListSelectionListener tableSelectionListener;
102     private ListSelectionListener tableScrollSelectionListener;
103     private TableColumnModelListener columnModelListener;
104     private RowSorterListener rowSorterListener;
105 
106     /**
107      * <p>Constructor for AbstractTableUIHandler.</p>
108      *
109      * @param properties a {@link String} object.
110      */
111     @SuppressWarnings("unchecked")
112     protected AbstractTableUIHandler(String... properties) {
113 
114         rowMonitor = new BeanMonitor<>(properties);
115 
116         // listen when bean is changed
117         rowMonitor.addPropertyChangeListener(BeanMonitor.PROPERTY_BEAN, new PropertyChangeListener() {
118 
119             final Set<String> propertiesToSkip = Sets.newHashSet(getRowPropertiesToIgnore());
120 
121             final PropertyChangeListener l = new PropertyChangeListener() {
122 
123                 @Override
124                 public void propertyChange(PropertyChangeEvent evt) {
125                     String propertyName = evt.getPropertyName();
126 
127                     R row = (R) evt.getSource();
128 
129                     Object oldValue = evt.getOldValue();
130                     Object newValue = evt.getNewValue();
131 
132                     int rowIndex = getTableModel().getRowIndex(row);
133 
134                     if (AbstractRowUIModel.PROPERTY_VALID.equals(propertyName)) {
135                         onRowValidStateChanged(rowIndex, row,
136                                 (Boolean) oldValue,
137                                 (Boolean) newValue);
138                     } else if (AbstractRowUIModel.PROPERTY_MODIFY.equals(propertyName)) {
139                         onRowModifyStateChanged(rowIndex, row,
140                                 (Boolean) oldValue,
141                                 (Boolean) newValue);
142                     } else if (AbstractRowUIModel.PROPERTY_SELECTED.equals(propertyName)) {
143                         onRowSelectedStateChanged(rowIndex, row,
144                                 (Boolean) oldValue,
145                                 (Boolean) newValue);
146                     } else if (!propertiesToSkip.contains(propertyName)) {
147                         if (LOG.isDebugEnabled()) {
148                             LOG.debug(
149                                     "row [" + rowIndex + "] property " + propertyName + " changed from " + oldValue + " to " + newValue);
150                         }
151 
152                         // don't fire event if old and value both null
153                         if (oldValue != null || newValue != null) {
154                             onRowModified(rowIndex, row, propertyName,
155                                     evt instanceof IndexedPropertyChangeEvent ? ((IndexedPropertyChangeEvent)evt).getIndex() : null,
156                                     oldValue, newValue);
157                         }
158                     }
159                 }
160             };
161 
162             @Override
163             public void propertyChange(PropertyChangeEvent evt) {
164                 R oldValue = (R) evt.getOldValue();
165                 R newValue = (R) evt.getNewValue();
166                 if (LOG.isDebugEnabled()) {
167                     LOG.debug("Monitor row changed from " + oldValue + " to " + newValue);
168                 }
169                 if (oldValue != null) {
170                     oldValue.removePropertyChangeListener(l);
171                 }
172                 if (newValue != null) {
173                     newValue.addPropertyChangeListener(l);
174                 }
175             }
176         });
177     }
178 
179     /**
180      * <p>getTableModel.</p>
181      *
182      * @return the table model handled by the main table.
183      * @since 0.2
184      */
185     public abstract AbstractTableModel<R> getTableModel();
186 
187     /**
188      * <p>getTable.</p>
189      *
190      * @return the main table of the ui.
191      * @since 0.2
192      */
193     public abstract JXTable getTable();
194 
195     /**
196      * Validates the given row.
197      *
198      * @param row row to validate
199      * @return {@code true} if row is valid, {@code false} otherwise.
200      * @since 1.0.1
201      */
202     protected boolean isRowValid(R row) {
203 
204         // try to validate the mandatory columns
205         for (ColumnIdentifier<R> identifier : getTableModel().getMandatoryIdentifiers()) {
206             Object value = identifier.getValue(row);
207             if (value == null) {
208                 row.setMandatoryValid(false);
209                 return false;
210             }
211             if (value instanceof String && StringUtils.isBlank((String) value)) {
212                 row.setMandatoryValid(false);
213                 return false;
214             }
215         }
216 
217         row.setMandatoryValid(true);
218         return true;
219     }
220 
221     /**
222      * Invoke each time the {@link fr.ifremer.quadrige2.ui.swing.common.model.AbstractBeanUIModel#modify} state on the current selected row changed.
223      *
224      * @param rowIndex      row index of the modified row
225      * @param row           modified row
226      * @param propertyName  name of the modified property of the row
227      * @param propertyIndex index of the modified property of the row
228      * @param oldValue      old value of the modified property
229      * @param newValue      new value of the modified property
230      * @since 0.3
231      */
232     protected void onRowModified(int rowIndex, R row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
233         getModel().setModify(true);
234         getTable().repaint();
235         recomputeRowValidState(row);
236     }
237 
238     //------------------------------------------------------------------------//
239     //-- Internal methods (row methods)                                     --//
240     //------------------------------------------------------------------------//
241 
242     /**
243      * <p>getRowPropertiesToIgnore.</p>
244      *
245      * @return an array of {@link String} objects.
246      */
247     protected String[] getRowPropertiesToIgnore() {
248         return ArrayUtils.EMPTY_STRING_ARRAY;
249     }
250 
251     /**
252      * <p>onModelRowsChanged.</p>
253      *
254      * @param rows a {@link List} object.
255      */
256     protected void onModelRowsChanged(List<R> rows) {
257         if (LOG.isDebugEnabled()) {
258             LOG.debug("Will set " + (rows == null ? 0 : rows.size())
259                     + " rows on model.");
260         }
261         cleanRowMonitor();
262         getTableModel().setRows(rows);
263     }
264 
265     /**
266      * <p>onRowsAdded.</p>
267      *
268      * @param addedRows a {@link List} object.
269      */
270     protected void onRowsAdded(List<R> addedRows) {
271         if (LOG.isDebugEnabled()) {
272             LOG.debug((addedRows == null ? 0 : addedRows.size())
273                     + " rows added on model.");
274         }
275     }
276 
277     /**
278      * <p>onRowModifyStateChanged.</p>
279      *
280      * @param rowIndex a int.
281      * @param row      a R object.
282      * @param oldValue a {@link Boolean} object.
283      * @param newValue a {@link Boolean} object.
284      */
285     protected void onRowModifyStateChanged(int rowIndex, R row, Boolean oldValue, Boolean newValue) {
286         if (LOG.isDebugEnabled()) {
287             LOG.debug("row [" + rowIndex + "] modify state changed from "
288                     + oldValue + " to " + newValue);
289         }
290     }
291 
292     /**
293      * <p>onRowValidStateChanged.</p>
294      *
295      * @param rowIndex a int.
296      * @param row      a R object.
297      * @param oldValue a {@link Boolean} object.
298      * @param newValue a {@link Boolean} object.
299      */
300     protected void onRowValidStateChanged(int rowIndex, R row, Boolean oldValue, Boolean newValue) {
301         if (LOG.isDebugEnabled()) {
302             LOG.debug("row [" + rowIndex + "] valid state changed from "
303                     + oldValue + " to " + newValue);
304         }
305         if (rowIndex > -1) {
306             getTableModel().fireTableRowsUpdated(rowIndex, rowIndex);
307         }
308     }
309 
310     /**
311      * <p>onRowSelectedStateChanged.</p>
312      *
313      * @param rowIndex a int.
314      * @param row      a R object.
315      * @param oldValue a {@link Boolean} object.
316      * @param newValue a {@link Boolean} object.
317      */
318     protected void onRowSelectedStateChanged(int rowIndex, R row, Boolean oldValue, Boolean newValue) {
319         if (LOG.isDebugEnabled()) {
320             LOG.debug("row [" + rowIndex + "] selected state changed from "
321                     + oldValue + " to " + newValue);
322         }
323         recalculateRowSelection(row);
324     }
325 
326     private void recalculateRowSelection(R currentRow) {
327 
328         // recalculate selected rows
329         getModel().populateSelectedRows();
330 
331         // redraw header
332         if (currentRow == null) {
333             return;
334         }
335 
336         // find CheckTableColumn
337         for (TableColumn column : getTable().getColumns()) {
338             if (column instanceof CheckTableColumn) {
339                 CheckTableColumn checkTableColumn = (CheckTableColumn) column;
340                 boolean all = true;
341                 Boolean currentSelected = currentRow.isSelected();
342                 for (R row : getTableModel().getRows()) {
343                     if (!currentSelected.equals(row.isSelected())) {
344                         all = false;
345                         break;
346                     }
347                 }
348                 checkTableColumn.setAllEnabled(all);
349                 checkTableColumn.setAllSelected(!all || currentSelected);
350 
351                 // repaint
352                 JTableHeader h = getTable().getTableHeader();
353                 h.repaint(h.getHeaderRect(getTable().convertColumnIndexToView(checkTableColumn.getModelIndex())));
354                 break;
355             }
356         }
357     }
358 
359     /**
360      * <p>selectAllValidRows.</p>
361      */
362     public void selectAllValidRows() {
363         if (getModel().getRowCount() > 0) {
364             for (R row : getModel().getRows()) {
365                 if (row.isValid()) {
366                     row.selected = true;
367                 }
368             }
369             recalculateRowSelection(getModel().getRows().get(0));
370         }
371     }
372 
373     /**
374      * <p>selectAllEditableRows.</p>
375      */
376     public void selectAllEditableRows() {
377         if (getModel().getRowCount() > 0) {
378             for (R row : getModel().getRows()) {
379                 if (!row.isCalculated() && row.isEditable()) {
380                     row.selected = true;
381                 }
382             }
383             recalculateRowSelection(getModel().getRows().get(0));
384         }
385     }
386 
387     /**
388      * <p>unselectAllRows.</p>
389      */
390     public void unselectAllRows() {
391         if (getModel().getRowCount() > 0) {
392             for (R row : getModel().getRows()) {
393                 row.selected = false;
394             }
395             recalculateRowSelection(getModel().getRows().get(0));
396         }
397     }
398 
399     //------------------------------------------------------------------------//
400     //-- Internal methods (init methods)                                    --//
401     //------------------------------------------------------------------------//
402 
403     /**
404      * <p>initTable.</p>
405      *
406      * @param table a {@link JXTable} object.
407      */
408     protected void initTable(JXTable table) {
409         initTable(table, false);
410     }
411 
412     /**
413      * <p>initTable.</p>
414      *
415      * @param table                a {@link JXTable} object.
416      * @param forceSingleSelection a boolean.
417      */
418     protected void initTable(JXTable table, boolean forceSingleSelection) {
419         initTable(table, forceSingleSelection, false);
420     }
421 
422     /**
423      * <p>initTable.</p>
424      *
425      * @param table             a {@link JXTable} object.
426      * @param singleSelection   a boolean.
427      * @param checkBoxSelection a boolean.
428      */
429     @SuppressWarnings("unchecked")
430     protected void initTable(JXTable table, boolean singleSelection, boolean checkBoxSelection) {
431         Preconditions.checkArgument(!(singleSelection && checkBoxSelection), "Could not have both 'singleSelection' and 'checkBoxSelection' to 'true'");
432 
433         if (!(table.getTableHeader() instanceof SwingTableHeader)) {
434             // change table header
435             table.setTableHeader(new SwingTableHeader(getTable().getColumnModel()));
436             table.getTableHeader().setTable(table);
437             table.getTableHeader().updateUI();
438         }
439 
440         // affect table model to table UI model
441         getModel().setTableModel(getTableModel());
442         // also affect table UI model to table model
443         getTableModel().setTableUIModel(getModel());
444 
445         // size properties
446         table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF);
447         // set a larger row height
448         table.setRowHeight(DEFAULT_ROW_HEIGHT);
449         table.setHorizontalScrollEnabled(true);
450         table.packAll();
451 
452         // by default authorize to change column orders
453         table.getTableHeader().setReorderingAllowed(true);
454 
455         // disable JXTable hack
456         table.putClientProperty(JXTable.USE_DTCR_COLORMEMORY_HACK, Boolean.FALSE);
457         addHighlighters(table);
458 
459         // when model data change let's propagate it to table model
460         getModel().addPropertyChangeListener(AbstractTableUIModel.PROPERTY_ROWS, new PropertyChangeListener() {
461 
462             @Override
463             public void propertyChange(PropertyChangeEvent evt) {
464                 onModelRowsChanged((List<R>) evt.getNewValue());
465             }
466         });
467         // first assignment of model data to table model
468         getTableModel().setRows(getModel().getRows());
469 
470         // when new rows are added
471         getModel().addPropertyChangeListener(AbstractTableUIModel.PROPERTY_ROWS_ADDED, new PropertyChangeListener() {
472             @Override
473             public void propertyChange(PropertyChangeEvent evt) {
474                 onRowsAdded((List<R>) evt.getNewValue());
475             }
476         });
477 
478         // always scroll to selected row
479 //        uninstallTableScrollSelectionListener();
480 
481         // before add the column, reset rowsorter to prevent exceptions
482         uninstallSortController();
483 
484         if (singleSelection) {
485             // only one selected row at a time
486             table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
487 
488         } else {
489 
490             // selection mode
491             if (checkBoxSelection) {
492 
493                 // only one selected row at a time
494                 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
495 
496                 // add selection check box
497                 CheckTableColumn<R, M> selectCol = new CheckTableColumn<>(table, getModel());
498                 table.getColumnModel().addColumn(selectCol);
499                 getTableModel().addCheckTableColumnIdentifier(selectCol.getIdentifier());
500 
501             } else {
502 
503                 // multiple rows selection
504                 getTable().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
505             }
506         }
507 
508         // Install sort controller
509         installSortController();
510 
511         // autoscroll disable
512 //        installTableScrollSelectionListener();
513 
514         // always force to uninstall listener
515         uninstallTableSelectionListener();
516 
517         // save when row changed and was modified
518         installTableSelectionListener();
519 
520         // add custom column control button
521         table.setColumnControlVisible(true);
522         table.setColumnControl(new ColumnControlButton(table));
523 
524         // Add default renderer if not already exists
525 //        table.setDefaultRenderer(QualitativeValueDTO.class, newTableCellRender(QualitativeValueDTO.class));
526 
527         // Add default String editor
528         table.setDefaultEditor(String.class, new StringGenericCellEditor());
529 
530         // Override the TAB and MAJ+TAB keys to perform specific actions
531         overrideTabKeyActions(table);
532 
533     }
534 
535     private void overrideTabKeyActions(JXTable table) {
536 
537         overrideKeyAction(table, "pressed TAB", AbstractCellSelectionAction.Direction.NEXT_CELL);
538         overrideKeyAction(table, "shift pressed TAB", AbstractCellSelectionAction.Direction.PREVIOUS_CELL);
539         overrideKeyAction(table, "pressed RIGHT", AbstractCellSelectionAction.Direction.NEXT_CELL);
540         overrideKeyAction(table, "pressed LEFT", AbstractCellSelectionAction.Direction.PREVIOUS_CELL);
541         overrideKeyAction(table, "pressed DOWN", AbstractCellSelectionAction.Direction.NEXT_ROW);
542         overrideKeyAction(table, "pressed ENTER", AbstractCellSelectionAction.Direction.NEXT_ROW);
543 
544     }
545 
546     private void overrideKeyAction(JXTable table, String key, AbstractCellSelectionAction.Direction direction) {
547 
548         String actionName = "actionFor" + StringUtils.deleteWhitespace(StringUtils.capitalize(key));
549 
550         // create new action
551         AbstractCellSelectionAction action = null;
552         switch (direction) {
553 
554             case NEXT_CELL:
555                 action = new NextCellSelectionAction(actionName, this);
556                 break;
557             case PREVIOUS_CELL:
558                 action = new PreviousCellSelectionAction(actionName, this);
559                 break;
560             case NEXT_ROW:
561                 action = new NextRowSelectionAction(actionName, this);
562                 break;
563             case PREVIOUS_ROW:
564                 break;
565         }
566 
567         if (action == null) {
568             return;
569         }
570 
571         // override the input map entry
572         table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(key), actionName);
573 
574         // affect the new action
575         table.getActionMap().put(actionName, action);
576 
577     }
578 
579     /**
580      * <p>installTableSelectionListener.</p>
581      */
582     protected void installTableSelectionListener() {
583 
584         Preconditions.checkState(
585                 tableSelectionListener == null,
586                 "There is already a tableSelectionListener registered, remove it before invoking this method.");
587 
588         // create new listener
589         tableSelectionListener = new ListSelectionListener() {
590 
591             int lastMinIndex = -1;
592             int lastMaxIndex = -1;
593 
594             @Override
595             public void valueChanged(ListSelectionEvent e) {
596 
597                 ListSelectionModel source = (ListSelectionModel) e.getSource();
598 
599                 if (LOG.isDebugEnabled()) {
600                     LOG.debug("Selection changed: " + e);
601                 }
602 
603                 // need this for the first modification when no selection,
604                 // otherwise monitor is set after the first alter, so won't be
605                 // save directly...
606 
607                 // get selection interval
608                 int minIndex = source.getMinSelectionIndex();
609                 int maxIndex = source.getMaxSelectionIndex();
610 
611                 if (source.isSelectionEmpty()) {
612                     minIndex = -1;
613                     maxIndex = -1;
614                 }
615                 if (minIndex == lastMinIndex && maxIndex == lastMaxIndex) {
616                     return;
617                 }
618                 lastMinIndex = minIndex;
619                 lastMaxIndex = maxIndex;
620 
621                 // before all, desactivate rowMonitor to avoid PROPERTY_SELECTED event to fire useless
622                 // also prevent recalculateRowSelection to execute too often
623                 rowMonitor.setBean(null);
624 
625                 // re-affect selected property when selection checkbox is NOT used
626                 if (!getTableModel().hasCheckTableColumn()) {
627                     // reset selected property
628                     for (R row : getModel().getSelectedRows()) {
629                         row.setSelected(false);
630                     }
631 
632                     // set new selected property
633                     for (int index = minIndex; index <= maxIndex; index++) {
634                         if (source.isSelectedIndex(index)) {
635                             int modelIndex = getTable().convertRowIndexToModel(index);
636                             R row = getTableModel().getEntry(modelIndex);
637                             row.setSelected(true);
638                         }
639                     }
640 //                    getModel().populateSelectedRows();
641                 }
642 
643                 R newRow;
644 
645                 // Add control to avoid ArrayIndexOfBounds when calling convertRowIndexToModel (Mantis #0027276)
646                 if (source.isSelectionEmpty() || source.getLeadSelectionIndex() < 0 || source.getLeadSelectionIndex() >= getTable().getRowCount()) {
647                     newRow = null;
648                 } else {
649                     int rowIndex = source.getLeadSelectionIndex();
650                     int modelIndex = getTable().convertRowIndexToModel(rowIndex);
651                     newRow = getTableModel().getEntry(modelIndex);
652                     if (LOG.isDebugEnabled()) {
653                         LOG.debug("Will monitor entry: " + rowIndex);
654                     }
655                 }
656 
657                 rowMonitor.setBean(newRow);
658                 getModel().setSingleSelectedRow(newRow);
659 
660                 // select row in table (for example if column order change
661                 if (newRow != null && source.getMinSelectionIndex() == source.getMaxSelectionIndex()) {
662                     selectRow(newRow);
663                 }
664 
665                 // recalculate row selection with new selection
666                 recalculateRowSelection(newRow);
667 
668             }
669         };
670 
671         if (LOG.isDebugEnabled()) {
672             LOG.debug("Install " + tableSelectionListener + " on tableModel " + getTableModel());
673         }
674 
675         getTable().getSelectionModel().addListSelectionListener(tableSelectionListener);
676     }
677 
678     /**
679      * <p>uninstallTableSelectionListener.</p>
680      */
681     protected void uninstallTableSelectionListener() {
682 
683         if (tableSelectionListener != null) {
684 
685             if (LOG.isDebugEnabled()) {
686                 LOG.debug("Uninstall " + tableSelectionListener);
687             }
688 
689             // there was a previous selection listener, remove it
690             getTable().getSelectionModel().removeListSelectionListener(tableSelectionListener);
691             tableSelectionListener = null;
692         }
693     }
694 
695     /**
696      * <p>installTableScrollSelectionListener.</p>
697      */
698     protected void installTableScrollSelectionListener() {
699         Preconditions.checkState(
700                 tableScrollSelectionListener == null,
701                 "There is already a tableScrollSelectionListener registered, remove it before invoking this method.");
702 
703         tableScrollSelectionListener = new ListSelectionListener() {
704 
705             @Override
706             public void valueChanged(ListSelectionEvent e) {
707                 if (!e.getValueIsAdjusting()) {
708                     int colIndex = getTable().getSelectedColumn();
709                     int rowIndex = getTable().getSelectedRow();
710                     if (rowIndex > -1 && colIndex > -1) {
711                         getTable().scrollCellToVisible(rowIndex, colIndex);
712                     }
713                 }
714             }
715         };
716 
717         getTable().getSelectionModel().addListSelectionListener(tableScrollSelectionListener);
718         getTable().getColumnModel().getSelectionModel().addListSelectionListener(tableScrollSelectionListener);
719     }
720 
721     /**
722      * <p>uninstallTableScrollSelectionListener.</p>
723      */
724     protected void uninstallTableScrollSelectionListener() {
725         if (tableScrollSelectionListener != null) {
726 
727             if (LOG.isDebugEnabled()) {
728                 LOG.debug("Uninstall " + tableScrollSelectionListener);
729             }
730 
731             // there was a previous selection listener, remove it
732             getTable().getSelectionModel().removeListSelectionListener(tableScrollSelectionListener);
733             getTable().getColumnModel().getSelectionModel().removeListSelectionListener(tableScrollSelectionListener);
734             tableScrollSelectionListener = null;
735         }
736     }
737 
738     // Add listener on table column model to save table state (see Mantis #35641)
739     protected void installSaveTableStateListener() {
740         Preconditions.checkState(
741                 columnModelListener == null,
742                 "There is already a columnModelListener registered, remove it before invoking this method.");
743         Preconditions.checkState(
744                 rowSorterListener == null,
745                 "There is already a rowSorterListener registered, remove it before invoking this method.");
746 
747         columnModelListener = new TableColumnModelListener() {
748             @Override
749             public void columnAdded(TableColumnModelEvent event) {
750                 if (!getModel().isLoading()) {
751                     if (LOG.isDebugEnabled()) LOG.debug(event);
752                     saveTableInSwingSession();
753                 }
754             }
755 
756             @Override
757             public void columnRemoved(TableColumnModelEvent event) {
758                 if (!getModel().isLoading()) {
759                     if (LOG.isDebugEnabled()) LOG.debug(event);
760                     saveTableInSwingSession();
761                 }
762             }
763 
764             @Override
765             public void columnMoved(TableColumnModelEvent event) {
766                 if (!getModel().isLoading() && event.getFromIndex() != event.getToIndex()) {
767                     if (LOG.isDebugEnabled()) LOG.debug(event);
768                     saveTableInSwingSession();
769                 }
770             }
771 
772             @Override
773             public void columnMarginChanged(ChangeEvent event) {
774                 if (!getModel().isLoading()) {
775                     if (LOG.isDebugEnabled()) LOG.debug(event);
776                     saveTableInSwingSession();
777                 }
778             }
779 
780             @Override
781             public void columnSelectionChanged(ListSelectionEvent event) {
782             }
783         };
784 
785         getTable().getColumnModel().addColumnModelListener(columnModelListener);
786 
787         // Add listener on row sorter
788         if (getTable().getRowSorter() != null) {
789             rowSorterListener = new RowSorterListener() {
790                 @Override
791                 public void sorterChanged(RowSorterEvent event) {
792                     if (!getModel().isLoading() && event.getType().equals(RowSorterEvent.Type.SORT_ORDER_CHANGED)) {
793                         if (LOG.isDebugEnabled()) LOG.debug(event);
794                         saveTableInSwingSession();
795                     }
796                 }
797             };
798 
799             getTable().getRowSorter().addRowSorterListener(rowSorterListener);
800         }
801     }
802 
803     protected void saveTableInSwingSession() {
804         getContext().saveComponentInSwingSession(getTable(), getTableModel().getStateContext());
805     }
806 
807     protected void uninstallSaveTableStateListener() {
808         if (columnModelListener != null) {
809             getTable().getColumnModel().removeColumnModelListener(columnModelListener);
810             columnModelListener = null;
811         }
812         if (rowSorterListener != null) {
813             if (getTable().getRowSorter() != null) {
814                 getTable().getRowSorter().removeRowSorterListener(rowSorterListener);
815             }
816             rowSorterListener = null;
817         }
818     }
819 
820     /**
821      * <p>cleanRowMonitor.</p>
822      */
823     protected void cleanRowMonitor() {
824         rowMonitor.clearModified();
825     }
826 
827     /**
828      * <p>recomputeRowValidState.</p>
829      *
830      * @param row a R object.
831      */
832     public final void recomputeRowValidState(R row) {
833 
834         // recompute row valid state
835         boolean valid = isRowValid(row);
836 
837         // apply it to row
838         row.setValid(valid);
839 
840         if (valid) {
841             getModel().removeRowInError(row);
842         } else {
843             getModel().addRowInError(row);
844         }
845 
846     }
847 
848     /**
849      * <p>recomputeRowsValidState.</p>
850      */
851     public void recomputeRowsValidState() {
852         recomputeRowsValidState(true);
853     }
854 
855     /**
856      * <p>recomputeRowsValidState.</p>
857      *
858      * @param validIfNoRow a boolean.
859      */
860     public void recomputeRowsValidState(boolean validIfNoRow) {
861         List<R> rows = getModel().getRows();
862         if (LOG.isDebugEnabled()) {
863             LOG.debug("Will valid " + (rows == null ? 0 : rows.size())
864                     + " rows on model.");
865         }
866 
867         if (CollectionUtils.isNotEmpty(rows)) {
868 
869             // compute valid state for each row
870             Set<R> invalidRows = Sets.newHashSet();
871             for (R row : rows) {
872 
873                 // recompute row valid state & apply it to row
874                 row.setValid(isRowValid(row));
875 
876                 // add the row to the correct list
877                 if (!row.isValid()) {
878                     invalidRows.add(row);
879                 }
880             }
881 
882             // apply invalid rows in one line to prevent property change listeners to flicker
883             getModel().setRowsInError(invalidRows);
884 //            getModel().setValid(invalidRows.isEmpty());
885         } else {
886             getModel().setValid(validIfNoRow);
887         }
888 
889     }
890 
891     /**
892      * <p>stopCellEditing.</p>
893      */
894     public void stopCellEditing() {
895 
896         if (getTable().isEditing()) {
897             getTable().getCellEditor().stopCellEditing();
898         }
899     }
900 
901     /**
902      * <p>addDatePickerColumnToModel.</p>
903      *
904      * @param model      a {@link TableColumnModel} object.
905      * @param identifier a {@link fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier} object.
906      * @return a {@link TableColumnExt} object.
907      */
908     protected TableColumnExt addDatePickerColumnToModel(TableColumnModel model, ColumnIdentifier<R> identifier, String dateFormat) {
909         return addDatePickerColumnToModel(model, identifier, dateFormat, true);
910     }
911 
912     /**
913      * <p>addDatePickerColumnToModel.</p>
914      *
915      * @param model      a {@link TableColumnModel} object.
916      * @param identifier a {@link fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier} object.
917      * @param isEditable a boolean.
918      * @return a {@link TableColumnExt} object.
919      */
920     protected TableColumnExt addDatePickerColumnToModel(TableColumnModel model, ColumnIdentifier<R> identifier, String dateFormat, boolean isEditable) {
921         return addColumnToModel(
922                 model,
923                 isEditable ? newDateCellEditor(dateFormat) : null,
924                 newDateCellRenderer(dateFormat),
925                 identifier);
926     }
927 
928     /**
929      * <p>newDateCellEditor.</p>
930      *
931      * @param pattern a {@link String} object.
932      * @return a {@link TableCellEditor} object.
933      */
934     protected TableCellEditor newDateCellEditor(String pattern) {
935         return new DatePickerCellEditor(pattern);
936     }
937 
938     /**
939      * <p>newDateCellEditor2.</p>
940      *
941      * @param pattern a {@link String} object.
942      * @return a {@link TableCellEditor} object.
943      */
944     protected TableCellEditor newDateCellEditor2(String pattern) {
945         return new DatePickerCellEditor2(pattern);
946     }
947 
948     /**
949      * <p>newDateCellRenderer.</p>
950      *
951      * @param pattern a {@link String} object.
952      * @return a {@link TableCellRenderer} object.
953      */
954     protected TableCellRenderer newDateCellRenderer(String pattern) {
955         return new DateCellRenderer(getTable().getDefaultRenderer(Date.class), pattern);
956     }
957 
958     /**
959      * <p>newTableCellRender.</p>
960      *
961      * @param identifier a {@link fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier} object.
962      * @return a {@link TableCellRenderer} object.
963      */
964     protected TableCellRenderer newTableCellRender(ColumnIdentifier<?> identifier) {
965         return newTableCellRender(identifier.getPropertyType(), identifier.getDecoratorName());
966     }
967 
968     /**
969      * {@inheritDoc}
970      */
971     @Override
972     protected <O> TableCellRenderer newTableCellRender(Class<O> type, String name) {
973         Decorator<O> decorator = getDecorator(type, name);
974         return new ExtendedDecoratorCellRenderer(decorator);
975     }
976 
977     protected <O> TableCellRenderer newTableCellRender(Class<O> type, String context, String tooltipContext) {
978         Decorator<O> decorator = getDecorator(type, context);
979         Decorator<O> tooltipDecorator = getDecorator(type, tooltipContext);
980         return new ExtendedDecoratorCellRenderer(decorator, tooltipDecorator);
981     }
982 
983     /**
984      * {@inheritDoc}
985      */
986     @Override
987     protected <O> TableCellRenderer newTableCellRender(Class<O> type) {
988         return this.newTableCellRender(type, null);
989     }
990 
991     /**
992      * <p>fixColumnWidth.</p>
993      *
994      * @param column a {@link TableColumn} object.
995      * @param width  a int.
996      */
997     protected void fixColumnWidth(TableColumn column, int width) {
998         column.setMinWidth(width);
999         column.setMaxWidth(width);
1000     }
1001 
1002     /**
1003      * Select a row
1004      *
1005      * @param row the row to select
1006      */
1007     public void selectRow(R row) {
1008         int rowViewIndex = getRowViewIndex(row);
1009         if (rowViewIndex == -1) {
1010             return;
1011         }
1012         if (getTable().getSelectionMode() != ListSelectionModel.SINGLE_SELECTION) {
1013             row.setSelected(true);
1014         }
1015         selectCell(rowViewIndex, null);
1016     }
1017 
1018     /**
1019      * Select a group of row
1020      *
1021      * @param rows rows to select
1022      */
1023     public void selectRows(List<R> rows) {
1024 
1025         for (R row : rows) {
1026             int rowViewIndex = getRowViewIndex(row);
1027             if (rowViewIndex != -1) {
1028                 row.setSelected(true);
1029                 getTable().addRowSelectionInterval(rowViewIndex, rowViewIndex);
1030 
1031                 if (rows.indexOf(row) == rows.size() - 1) {
1032                     getTable().scrollCellToVisible(rowViewIndex, 0);
1033                     recalculateRowSelection(row);
1034                 }
1035             }
1036         }
1037 
1038     }
1039 
1040     /**
1041      * Ajoute le focus sur la cellule dans la ligne et a la colonne
1042      *
1043      * @param row La ligne
1044      */
1045     public void setFocusOnCell(final R row) {
1046 
1047         // set current selected
1048         getModel().setSingleSelectedRow(row);
1049         recomputeRowValidState(row);
1050 
1051         SwingUtilities.invokeLater(new Runnable() {
1052             @Override
1053             public void run() {
1054 
1055                 // L'index de la ligne (recuperation de l index dans la table en prenant en compte les tris)
1056                 int rowViewIndex = getRowViewIndex(row);
1057                 if (rowViewIndex == -1) {
1058                     return;
1059                 }
1060 
1061                 // L'index de la colonne
1062                 final ColumnIdentifier<?> columnIdentifier = getTableModel().getFirstColumnEditing();
1063                 if (columnIdentifier != null) {
1064                     int indexColonne = getTable().getColumnExt(columnIdentifier).getModelIndex();
1065                     int columnViewIndex = getTable().convertColumnIndexToView(indexColonne);
1066 
1067                     while (columnViewIndex < getTable().getColumnCount(false) && !getTable().isCellEditable(rowViewIndex, columnViewIndex)) {
1068                         columnViewIndex++;
1069                     }
1070 
1071                     // abort if column is out out of bounds
1072                     if (columnViewIndex >= getTable().getColumnCount(false)) {
1073                         return;
1074                     }
1075 
1076                     // Selection de la ligne
1077                     selectCell(rowViewIndex, columnViewIndex);
1078 
1079                     // Edition de la cellule recherchee
1080                     getTable().editCellAt(rowViewIndex, columnViewIndex);
1081                 }
1082             }
1083         });
1084     }
1085 
1086     /**
1087      * <p>selectCell.</p>
1088      *
1089      * @param rowIndex a {@link Integer} object.
1090      * @param colIndex a {@link Integer} object.
1091      */
1092     public void selectCell(Integer rowIndex, Integer colIndex) {
1093 
1094         int row = rowIndex != null ? rowIndex : getTable().getSelectedRow();
1095         row = row != -1 ? row : 0;
1096         int col = colIndex != null ? colIndex : getTable().getSelectedColumn();
1097         col = col != -1 ? col : 0;
1098 
1099         if (row >= 0) {
1100             getTable().setRowSelectionInterval(row, row);
1101         }
1102         if (col >= 0) {
1103             getTable().setColumnSelectionInterval(col, col);
1104         }
1105         getTable().scrollCellToVisible(row, col);
1106     }
1107 
1108     /**
1109      * get the row view index of a row
1110      *
1111      * @param row the row
1112      * @return row view index of this row
1113      */
1114     private int getRowViewIndex(R row) {
1115         if (row == null) {
1116             return -1;
1117         }
1118         int rowIndex = getTableModel().getRowIndex(row);
1119         if (rowIndex == -1) {
1120             return -1;
1121         }
1122         return getTable().convertRowIndexToView(rowIndex);
1123     }
1124 
1125     /**
1126      * <p>newTableColumnModel.</p>
1127      *
1128      * @return a {@link fr.ifremer.quadrige2.ui.swing.common.table.SwingTableColumnModel} object.
1129      */
1130     protected SwingTableColumnModel newTableColumnModel() {
1131         return new SwingTableColumnModel();
1132     }
1133 
1134     // TABLE SORTING CONTROLLER
1135 
1136     /**
1137      * <p>uninstallSortController.</p>
1138      */
1139     protected void uninstallSortController() {
1140 
1141         // tell the table to not auto create row sorter
1142         getTable().setAutoCreateRowSorter(false);
1143 
1144         // remove actual row sorter
1145         getTable().setRowSorter(null);
1146     }
1147 
1148     /**
1149      * <p>installSortController.</p>
1150      */
1151     @SuppressWarnings("unchecked")
1152     protected void installSortController() {
1153 
1154         // uninstall first
1155         uninstallSortController();
1156 
1157         SwingTableSortController controller = new SwingTableSortController(getTableModel());
1158         // affect now to let table do initialization
1159         getTable().setRowSorter(controller);
1160 
1161         // create comparator for each column in column model
1162         DecoratorService decoratorService = ClientServiceLocator.instance().getDecoratorService();
1163         for (int i = 0; i < getTable().getColumnModel().getColumnCount(); i++) {
1164 
1165             Comparator comparator = null;
1166             TableColumnExt column = getTable().getColumnExt(i);
1167 
1168             if (!column.isSortable()) {
1169                 controller.setSortable(column.getModelIndex(), false);
1170                 continue;
1171             }
1172 
1173             ColumnIdentifier identifier = (ColumnIdentifier) column.getIdentifier();
1174 
1175             if (decoratorService != null) {
1176                 Decorator decorator = decoratorService.getDecoratorByType(identifier.getPropertyType(), identifier.getDecoratorName());
1177                 if (decorator != null) {
1178                     // create a new decorator comparator
1179                     comparator = new DecoratorComparator(decorator);
1180                 }
1181             }
1182 
1183             if (comparator == null) {
1184                 Class<?> columnClass = identifier.getPropertyType();
1185                 if (columnClass == String.class) {
1186                     // specific comparator for String
1187                     comparator = Collator.getInstance();
1188                 } else if (Comparable.class.isAssignableFrom(columnClass)) {
1189                     // default comparator for Comparable objects
1190                     comparator = new ComparableComparator();
1191                 }
1192             }
1193 
1194             // set this comparator to the controller
1195             controller.setComparator(column.getModelIndex(), comparator);
1196         }
1197     }
1198 
1199     /**
1200      * <p>getNextComponentToFocus.</p>
1201      *
1202      * @return a {@link Component} object.
1203      */
1204     public Component getNextComponentToFocus() {
1205         return null;
1206     }
1207 
1208     /**
1209      * <p>getPreviousComponentToFocus.</p>
1210      *
1211      * @return a {@link Component} object.
1212      */
1213     public Component getPreviousComponentToFocus() {
1214         return null;
1215     }
1216 }