View Javadoc
1   package fr.ifremer.quadrige3.ui.swing.table.state;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 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 fr.ifremer.quadrige3.core.dao.technical.Assert;
27  import fr.ifremer.quadrige3.ui.swing.table.*;
28  import fr.ifremer.quadrige3.ui.swing.table.action.AdditionalTableActions;
29  import jaxx.runtime.swing.session.State;
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.jdesktop.swingx.action.AbstractActionExt;
34  import org.jdesktop.swingx.table.TableColumnExt;
35  
36  import javax.swing.Action;
37  import javax.swing.RowSorter;
38  import javax.swing.SortOrder;
39  import javax.swing.SwingUtilities;
40  import javax.swing.table.AbstractTableModel;
41  import java.util.*;
42  
43  /**
44   * State used for a SwingTable with a AbstractTableModel
45   * <p/>
46   * Created by Ludovic on 05/05/2015.
47   */
48  public class SwingTableSessionState implements ExtendedState {
49  
50      private static final Log LOG = LogFactory.getLog(SwingTableSessionState.class);
51  
52      protected static final String SEPARATOR = "|";
53  
54      // session states map by context
55      private Map<String, SwingTableState> sessionStates;
56  
57      // selected additional actions
58      private Set<String> selectedActions;
59  
60      /**
61       * <p>Constructor for SwingTableSessionState.</p>
62       */
63      public SwingTableSessionState() {
64          sessionStates = new HashMap<>();
65          selectedActions = new HashSet<>();
66      }
67  
68      /**
69       * <p>checkComponent.</p>
70       *
71       * @param object a {@link Object} object.
72       * @return a {@link SwingTable} object.
73       */
74      protected SwingTable checkComponent(Object object) {
75          Assert.notNull(object);
76          Assert.isInstanceOf(SwingTable.class, object);
77          Assert.isInstanceOf(AbstractTableModel.class, ((SwingTable) object).getModel());
78          return (SwingTable) object;
79      }
80  
81      /**
82       * {@inheritDoc}
83       */
84      @Override
85      public State getState(Object o) {
86  
87          return getState(o, null, null);
88      }
89  
90      /**
91       * {@inheritDoc}
92       */
93      @Override
94      @SuppressWarnings("unchecked")
95      public State getState(Object o, State previousState, String stateContextToSave) {
96  
97          SwingTable table = checkComponent(o);
98  
99  
100         // Retrieve table model and its state context
101         String stateContext = stateContextToSave != null ? stateContextToSave : table.getTableModel().getStateContext();
102 
103         // Reuse previous state or create new one
104         SwingTableSessionState tableSessionState;
105         if (previousState instanceof SwingTableSessionState) {
106             tableSessionState = (SwingTableSessionState) previousState;
107         } else {
108             tableSessionState = new SwingTableSessionState();
109         }
110         try {
111             // Get or create the table state for the context
112             SwingTableState tableState = tableSessionState.getSessionStates().computeIfAbsent(stateContext, k -> new SwingTableState());
113 
114             // get tableModel.getIdentifiers() in a new list to prevent ConcurrentModificationException
115             List<ColumnIdentifier> identifiers = new ArrayList<>(table.getTableModel().getIdentifiers());
116             // Retrieve sort keys
117             Map<Integer, Integer> sortOrderPriority = new HashMap<>();
118             if (table.getRowSorter() != null && table.getRowSorter().getSortKeys() != null) {
119                 for (int priority = 0; priority < table.getRowSorter().getSortKeys().size(); priority++) {
120                     RowSorter.SortKey sortKey = table.getRowSorter().getSortKeys().get(priority);
121                     sortOrderPriority.put(sortKey.getColumn(), priority);
122                 }
123             }
124 
125             // Iterate over identifiers
126             for (ColumnIdentifier identifier : identifiers) {
127                 TableColumnExt column = table.getColumnExt(identifier);
128                 if (column == null || column instanceof FixedColumn) {
129                     // ignore fixed column
130                     continue;
131                 }
132 
133                 int columnViewIndex = table.convertColumnIndexToView(column.getModelIndex());
134                 boolean isPmfmColumn = identifier instanceof PmfmColumnIdentifier;
135                 boolean isHidden = column instanceof HiddenColumn;
136 
137                 // shift column view index if table model has checkTableColumn
138 //            if (table.getColumnModel().hasCheckTableColumn() && columnViewIndex > 0) {
139 //                columnViewIndex--;
140 //            }
141 
142                 // set the visibility and width property
143                 if (isPmfmColumn) {
144                     // pmfm column
145                     if (isHidden) {
146                         tableState.getPmfmVisibility().remove(((PmfmColumnIdentifier) identifier).getPmfmId());
147                         tableState.getPmfmWidth().remove(((PmfmColumnIdentifier) identifier).getPmfmId());
148                     } else {
149                         tableState.getPmfmVisibility().put(((PmfmColumnIdentifier) identifier).getPmfmId(), columnViewIndex);
150                         tableState.getPmfmWidth().put(((PmfmColumnIdentifier) identifier).getPmfmId(), column.getPreferredWidth());
151                     }
152 
153                 } else {
154                     // normal column
155                     if (isHidden) {
156                         tableState.getVisibility().remove(identifier.getPropertyName());
157                         tableState.getWidth().remove(identifier.getPropertyName());
158                     } else {
159                         tableState.getVisibility().put(identifier.getPropertyName(), columnViewIndex);
160                         tableState.getWidth().put(identifier.getPropertyName(), column.getPreferredWidth());
161                     }
162                 }
163 
164                 // set the sortOrder property
165                 // test if the table has a rowSorter to prevent NullPointerException in table.getSortOrder(identifier) (see Mantis#29603)
166                 if (table.getRowSorter() != null) {
167                     SortOrder sortOrder = table.getSortOrder(identifier);
168                     if (sortOrder != SortOrder.UNSORTED) {
169                         String propertyName = identifier.getPropertyName() + (isPmfmColumn ? SEPARATOR + ((PmfmColumnIdentifier) identifier).getPmfmId() : "");
170                         String order = sortOrder.name() + SEPARATOR + sortOrderPriority.get(column.getModelIndex());
171                         tableState.getSortOrder().put(propertyName, order);
172                     }
173                 }
174             }
175 
176             // get additional actions
177             tableSessionState.getSelectedActions().clear();
178             AdditionalTableActions additionalTableActions = getAdditionalTableActions(table);
179             if (additionalTableActions != null) {
180                 additionalTableActions.getActions().stream()
181                     // Read the selected property (a boolean) from an AbstractActionExt with state action is enabled
182                     .filter(action -> (action instanceof AbstractActionExt) && ((AbstractActionExt) action).isStateAction() && ((AbstractActionExt) action).isSelected())
183                     .forEach(action -> tableSessionState.getSelectedActions().add(action.getClass().getName()));
184             }
185 
186         } catch (Exception e) {
187             LOG.error("Something goes wrong on getState", e);
188         }
189         return tableSessionState;
190     }
191 
192     /**
193      * {@inheritDoc}
194      */
195     @Override
196     @SuppressWarnings("unchecked")
197     public void setState(Object o, State state) {
198         if (state == null) {
199             return;
200         }
201         if (!(state instanceof SwingTableSessionState)) {
202             throw new IllegalArgumentException("invalid state");
203         }
204         SwingTable table = checkComponent(o);
205 
206         // Retrieve table model
207         SwingTableSessionState tableSessionState = (SwingTableSessionState) state;
208         SwingTableState tableState = tableSessionState.getSessionStates().get(table.getTableModel().getStateContext());
209         if (tableState == null) {
210             // Ignore if no session state for this context
211             return;
212         }
213 
214         try {
215             // get tableModel.getIdentifiers() in a new list to prevent ConcurrentModificationException
216             List<ColumnIdentifier> identifiers = new ArrayList<>(table.getTableModel().getIdentifiers());
217             Map<Integer, SortOrder> sortOrderByModelIndex = new HashMap<>();
218             Map<Integer, Integer> sortPriority = new HashMap<>();
219             Map<Integer, Integer> deferredMove = new HashMap<>();
220 
221             for (ColumnIdentifier identifier : identifiers) {
222                 TableColumnExt column = table.getColumnExt(identifier);
223                 if (column == null || column instanceof FixedColumn) {
224                     // ignore fixed column
225                     continue;
226                 }
227                 boolean isPmfmColumn = identifier instanceof PmfmColumnIdentifier;
228                 boolean isHidden = column instanceof HiddenColumn;
229 
230                 // Get the visibility property
231                 if (isHidden) {
232                     // Force hide column
233                     column.setVisible(false);
234                 } else {
235                     Integer visibility;
236                     if (isPmfmColumn) {
237                         // pmfm column
238                         visibility = tableState.getPmfmVisibility().get(((PmfmColumnIdentifier) identifier).getPmfmId());
239                     } else {
240                         // normal column
241                         visibility = tableState.getVisibility().get(identifier.getPropertyName());
242                     }
243                     if (visibility != null && visibility > -1) {
244                         // set column visible
245                         column.setVisible(true);
246                         // but move it after this loop
247                         deferredMove.put(visibility, column.getModelIndex());
248                     } else if (column.isHideable()) {
249                         // set column invisible if allowed
250                         column.setVisible(false);
251                     }
252                 }
253 
254                 // Get the width property
255                 if (column.getResizable() && !isHidden) {
256                     Integer width;
257                     if (isPmfmColumn) {
258                         // pmfm column
259                         width = tableState.getPmfmWidth().get(((PmfmColumnIdentifier) identifier).getPmfmId());
260 
261                     } else {
262                         // normal column
263                         width = tableState.getWidth().get(identifier.getPropertyName());
264                     }
265                     if (width != null) {
266                         column.setPreferredWidth(width);
267                     }
268                 }
269 
270                 // Get the sortOrder property
271                 if (column.isSortable()) {
272                     String propertyName = identifier.getPropertyName() + (isPmfmColumn ? SEPARATOR + ((PmfmColumnIdentifier) identifier).getPmfmId() : "");
273                     String order = tableState.getSortOrder().get(propertyName);
274                     if (order != null) {
275                         // parse the sort order and priority
276                         String[] orderAndPriority = StringUtils.split(order, SEPARATOR);
277                         SortOrder sortOrder = SortOrder.valueOf(orderAndPriority[0]);
278                         Integer priority = Integer.parseInt(orderAndPriority[1]);
279                         // populate sort order and sort priority maps to apply to table later
280                         sortOrderByModelIndex.put(column.getModelIndex(), sortOrder);
281                         sortPriority.put(priority, column.getModelIndex());
282                     }
283                 }
284             }
285 
286             // Apply columns moves
287             if (!deferredMove.isEmpty()) {
288                 // Process with the target position order instead of model order (Mantis #45506)
289                 for (Integer newColumnViewIndex : deferredMove.keySet()) {
290                     int columnViewIndex = table.convertColumnIndexToView(deferredMove.get(newColumnViewIndex));
291 
292                     // shift new column view index if table model has checkTableColumn
293 //                if (table.getColumnModel().hasCheckTableColumn() && newColumnViewIndex >= 0) {
294 //                    newColumnViewIndex++;
295 //                }
296                     if (newColumnViewIndex >= table.getColumnCount()) {
297                         newColumnViewIndex = table.getColumnCount() - 1;
298                     }
299 
300                     // Move column to target position
301                     table.moveColumn(columnViewIndex, newColumnViewIndex);
302                 }
303             }
304 
305             // Apply column sort
306             if (!sortOrderByModelIndex.isEmpty()) {
307                 SortedSet<Integer> priorityOrder = new TreeSet<>(sortPriority.keySet());
308                 List<RowSorter.SortKey> sortKeys = new ArrayList<>();
309                 for (Integer priority : priorityOrder) {
310                     Integer modelIndex = sortPriority.get(priority);
311                     SortOrder sortOrder = sortOrderByModelIndex.get(modelIndex);
312                     RowSorter.SortKey sortKey = new RowSorter.SortKey(modelIndex, sortOrder);
313                     sortKeys.add(sortKey);
314                 }
315                 table.getRowSorter().setSortKeys(sortKeys);
316             }
317 
318             // Apply additional actions
319             AdditionalTableActions additionalTableActions = getAdditionalTableActions(table);
320             if (additionalTableActions != null) {
321                 tableSessionState.getSelectedActions().forEach(selectedActionName -> additionalTableActions.getActions().stream()
322                     .filter(action -> (action instanceof AbstractActionExt) && ((AbstractActionExt) action).isStateAction())
323                     .filter(action -> action.getClass().getName().equals(selectedActionName))
324                     .findFirst()
325                     .ifPresent(action ->
326                         {
327                             AbstractActionExt stateAction = (AbstractActionExt) action;
328                             stateAction.setSelected(true);
329                             SwingUtilities.invokeLater(() -> stateAction.actionPerformed(null));
330                         }
331                     ));
332             }
333 
334         } catch (Exception e) {
335             LOG.error("Something goes wrong on setState", e);
336         }
337     }
338 
339     // <Dont' delete> these getters ans setters used by SwingSession
340 
341     public Map<String, SwingTableState> getSessionStates() {
342         return sessionStates;
343     }
344 
345     public void setSessionStates(Map<String, SwingTableState> sessionStates) {
346         this.sessionStates = sessionStates;
347     }
348 
349     public Set<String> getSelectedActions() {
350         return selectedActions;
351     }
352 
353     public void setSelectedActions(Set<String> selectedActions) {
354         this.selectedActions = selectedActions;
355     }
356 
357     // </Don't delete>
358 
359     private AdditionalTableActions getAdditionalTableActions(SwingTable table) {
360         Action additionalActions = table.getActionMap().get(AdditionalTableActions.ACTION_NAME);
361         return (additionalActions instanceof AdditionalTableActions) ? (AdditionalTableActions) additionalActions : null;
362     }
363 }