View Javadoc
1   package fr.ifremer.quadrige2.ui.swing.common.table.state;
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  
27  import com.google.common.collect.Lists;
28  import com.google.common.collect.Maps;
29  import fr.ifremer.quadrige2.core.dao.technical.Beans;
30  import fr.ifremer.quadrige2.ui.swing.common.table.AbstractTableModel;
31  import fr.ifremer.quadrige2.ui.swing.common.table.CheckTableColumn;
32  import fr.ifremer.quadrige2.ui.swing.common.table.ColumnIdentifier;
33  import jaxx.runtime.swing.session.State;
34  import org.apache.commons.lang3.StringUtils;
35  import org.jdesktop.swingx.JXTable;
36  import org.jdesktop.swingx.table.TableColumnExt;
37  
38  import javax.swing.RowSorter;
39  import javax.swing.SortOrder;
40  import java.util.*;
41  
42  /**
43   * State used for a JXTable with a AbstractTableModel
44   * <p/>
45   * Created by Ludovic on 05/05/2015.
46   */
47  public class SwingTableSessionState implements ExtendedState {
48  
49      protected static final String SEPARATOR = "|";
50      // session states map by context
51      private Map<String, SwingTableState> sessionStates;
52  
53      /**
54       * <p>Constructor for SwingTableSessionState.</p>
55       */
56      public SwingTableSessionState() {
57          sessionStates = Maps.newHashMap();
58      }
59  
60      /**
61       * <p>checkComponent.</p>
62       *
63       * @param o a {@link Object} object.
64       * @return a {@link JXTable} object.
65       */
66      protected JXTable checkComponent(Object o) {
67          if (o == null) {
68              throw new IllegalArgumentException("null component");
69          }
70          if (!(o instanceof JXTable)) {
71              throw new IllegalArgumentException("invalid component");
72          }
73          return (JXTable) o;
74      }
75  
76      /** {@inheritDoc} */
77      @Override
78      public State getState(Object o) {
79  
80          return getState(o, null, null);
81      }
82  
83      /** {@inheritDoc} */
84      @Override
85      @SuppressWarnings("unchecked")
86      public State getState(Object o, State previousState, String stateContextToSave) {
87  
88          JXTable table = checkComponent(o);
89          if (!(table.getModel() instanceof AbstractTableModel)) {
90              return null;
91          }
92          // Retrieve table model and its state context
93          AbstractTableModel tableModel = (AbstractTableModel) table.getModel();
94          String stateContext = stateContextToSave != null ? stateContextToSave : tableModel.getStateContext();
95  
96          // Reuse previous state or create new one
97          SwingTableSessionState tableSessionState;
98          if (previousState instanceof SwingTableSessionState) {
99              tableSessionState = (SwingTableSessionState) previousState;
100         } else {
101             tableSessionState = new SwingTableSessionState();
102         }
103         // Get or create the table state for the context
104         SwingTableState tableState = tableSessionState.getSessionStates().get(stateContext);
105         if (tableState == null) {
106             tableState = new SwingTableState();
107             tableSessionState.getSessionStates().put(stateContext, tableState);
108         }
109 
110         // get tableModel.getIdentifiers() in a new list to prevent ConcurrentModificationException
111         List<ColumnIdentifier> identifiers = Beans.getList(tableModel.getIdentifiers());
112         // Retrieve sort keys
113         Map<Integer, Integer> sortOrderPriority = Maps.newHashMap();
114         if (table.getRowSorter() != null && table.getRowSorter().getSortKeys() != null) {
115             for (int priority = 0; priority < table.getRowSorter().getSortKeys().size(); priority++) {
116                 RowSorter.SortKey sortKey = table.getRowSorter().getSortKeys().get(priority);
117                 sortOrderPriority.put(sortKey.getColumn(), priority);
118             }
119         }
120 
121         // Iterate over identifiers
122         for (ColumnIdentifier identifier : identifiers) {
123             TableColumnExt column = table.getColumnExt(identifier);
124             if (column == null || column instanceof CheckTableColumn) {
125                 // ignore the checkTableColumn
126                 continue;
127             }
128 
129             int columnViewIndex = table.convertColumnIndexToView(column.getModelIndex());
130 
131             // shift column view index if table model has checkTableColumn
132             if (tableModel.hasCheckTableColumn() && columnViewIndex > 0) {
133                 columnViewIndex--;
134             }
135 
136             // set the visibility property
137                 tableState.getVisibility().put(identifier.getPropertyName(), columnViewIndex);
138 
139             // set the width property
140             tableState.getWidth().put(identifier.getPropertyName(), column.getPreferredWidth());
141 
142             // set the sortOrder property
143             // test if the table has a rowSorter to prevent NullPointerException in table.getSortOrder(identifier) (see Mantis#29603)
144             if (table.getRowSorter() != null) {
145                 SortOrder sortOrder = table.getSortOrder(identifier);
146                 if (sortOrder != SortOrder.UNSORTED) {
147                     String order = sortOrder.name() + SEPARATOR + sortOrderPriority.get(column.getModelIndex());
148                     tableState.getSortOrder().put(identifier.getPropertyName(), order);
149                 }
150             }
151         }
152 
153         return tableSessionState;
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     @SuppressWarnings("unchecked")
159     public void setState(Object o, State state) {
160         if (state == null) {
161             return;
162         }
163         if (!(state instanceof SwingTableSessionState)) {
164             throw new IllegalArgumentException("invalid state");
165         }
166         JXTable table = checkComponent(o);
167         if (!(table.getModel() instanceof AbstractTableModel)) {
168             return;
169         }
170 
171         // Retrieve table model
172         AbstractTableModel tableModel = (AbstractTableModel) table.getModel();
173         SwingTableSessionState tableSessionState = (SwingTableSessionState) state;
174         SwingTableState tableState = tableSessionState.getSessionStates().get(tableModel.getStateContext());
175         if (tableState == null) {
176             // Ignore if no session state for this context
177             return;
178         }
179 
180         // get tableModel.getIdentifiers() in a new list to prevent ConcurrentModificationException
181         List<ColumnIdentifier> identifiers = Beans.getList(tableModel.getIdentifiers());
182         Map<Integer, SortOrder> sortOrderByModelIndex = Maps.newHashMap();
183         Map<Integer, Integer> sortPriority = Maps.newHashMap();
184         Map<Integer, Integer> deferredMove = Maps.newHashMap();
185 
186         for (ColumnIdentifier identifier : identifiers) {
187             TableColumnExt column = table.getColumnExt(identifier);
188             if (column == null || column instanceof CheckTableColumn) {
189                 // ignore the checkTableColumn
190                 continue;
191             }
192 
193             // Get the visibility property
194             Integer visibility = tableState.getVisibility().get(identifier.getPropertyName());
195             if (visibility != null && visibility > -1) {
196                 // set column visible
197                 column.setVisible(true);
198                 // but move it after this loop
199                 deferredMove.put(column.getModelIndex(), visibility);
200             } else if (column.isHideable()) {
201                 // set column invisible if allowed
202                 column.setVisible(false);
203             }
204 
205             // Get the width property
206             if (column.getResizable()) {
207                 Integer width = tableState.getWidth().get(identifier.getPropertyName());
208                 if (width != null) {
209                     column.setPreferredWidth(width);
210                 }
211             }
212 
213             // Get the sortOrder property
214             if (column.isSortable()) {
215                 String order = tableState.getSortOrder().get(identifier.getPropertyName());
216                 if (order != null) {
217                     // parse the sort order and priority
218                     String[] orderAndPriority = StringUtils.split(order, SEPARATOR);
219                     SortOrder sortOrder = SortOrder.valueOf(orderAndPriority[0]);
220                     Integer priority = Integer.parseInt(orderAndPriority[1]);
221                     // populate sort order and sort priority maps to apply to table later
222                     sortOrderByModelIndex.put(column.getModelIndex(), sortOrder);
223                     sortPriority.put(priority, column.getModelIndex());
224                 }
225             }
226         }
227 
228         // Apply columns moves
229         if (!deferredMove.isEmpty()) {
230             List<Integer> columnModelIndexes = Lists.newArrayList(deferredMove.keySet());
231             Collections.sort(columnModelIndexes);
232             for (Integer columnModelIndex : columnModelIndexes) {
233                 int columnViewIndex = table.convertColumnIndexToView(columnModelIndex);
234                 int newColumnViewIndex = deferredMove.get(columnModelIndex);
235 
236                 // shift new column view index if table model has checkTableColumn
237                 if (tableModel.hasCheckTableColumn() && newColumnViewIndex >= 0) {
238                     newColumnViewIndex++;
239                 }
240                 if (newColumnViewIndex >= table.getColumnCount()) {
241                     newColumnViewIndex = table.getColumnCount() - 1;
242                 }
243 
244                 // Move column to target position
245                 table.moveColumn(columnViewIndex, newColumnViewIndex);
246             }
247         }
248 
249         // Apply column sort
250         if (!sortOrderByModelIndex.isEmpty()) {
251             SortedSet<Integer> priorityOrder = new TreeSet<>(sortPriority.keySet());
252             List<RowSorter.SortKey> sortKeys = Lists.newArrayList();
253             for (Integer priority : priorityOrder) {
254                 Integer modelIndex = sortPriority.get(priority);
255                 SortOrder sortOrder = sortOrderByModelIndex.get(modelIndex);
256                 RowSorter.SortKey sortKey = new RowSorter.SortKey(modelIndex, sortOrder);
257                 sortKeys.add(sortKey);
258             }
259             table.getRowSorter().setSortKeys(sortKeys);
260         }
261 
262     }
263 
264     /**
265      * <p>Getter for the field <code>sessionStates</code>.</p>
266      *
267      * @return a {@link Map} object.
268      */
269     public Map<String, SwingTableState> getSessionStates() {
270         return sessionStates;
271     }
272 
273     /**
274      * <p>Setter for the field <code>sessionStates</code>.</p>
275      *
276      * @param sessionStates a {@link Map} object.
277      */
278     public void setSessionStates(Map<String, SwingTableState> sessionStates) {
279         this.sessionStates = sessionStates;
280     }
281 
282 }