OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.ui.state;
15
 
16
import org.openconcerto.ui.Log;
73 ilm 17
import org.openconcerto.ui.TM;
17 ilm 18
import org.openconcerto.ui.table.XTableColumnModel;
19
import org.openconcerto.utils.ExceptionHandler;
73 ilm 20
import org.openconcerto.utils.TableSorter;
156 ilm 21
import org.openconcerto.utils.TableSorter.Directive;
17 ilm 22
 
23
import java.io.File;
24
import java.io.IOException;
25
import java.util.ArrayList;
26
import java.util.Collections;
27
import java.util.List;
156 ilm 28
import java.util.logging.Level;
17 ilm 29
 
30
import javax.swing.JTable;
31
import javax.swing.event.AncestorEvent;
32
import javax.swing.event.AncestorListener;
33
import javax.swing.table.TableColumn;
34
import javax.swing.table.TableColumnModel;
73 ilm 35
import javax.swing.table.TableModel;
156 ilm 36
import javax.xml.XMLConstants;
17 ilm 37
import javax.xml.parsers.DocumentBuilder;
38
import javax.xml.parsers.DocumentBuilderFactory;
39
import javax.xml.parsers.ParserConfigurationException;
156 ilm 40
import javax.xml.transform.OutputKeys;
17 ilm 41
import javax.xml.transform.Transformer;
42
import javax.xml.transform.TransformerException;
43
import javax.xml.transform.TransformerFactory;
44
import javax.xml.transform.dom.DOMSource;
45
import javax.xml.transform.stream.StreamResult;
46
 
47
import org.w3c.dom.Document;
48
import org.w3c.dom.Element;
49
import org.w3c.dom.NamedNodeMap;
50
import org.w3c.dom.Node;
51
import org.w3c.dom.NodeList;
52
 
65 ilm 53
/**
54
 * Save the width and order of columns in a JTable.
55
 *
56
 * @author Sylvain
57
 */
17 ilm 58
public class JTableStateManager extends ListenerXMLStateManager<JTable, AncestorListener> {
59
 
60
    private static final String VERSION = "20100810";
156 ilm 61
    private static final String IDENTIFIER_ATTR = "identifier";
62
    private static final String MODEL_INDEX_ATTR = "modelIndex";
63
    private static final String SORT_ATTR = "sort";
17 ilm 64
 
65
    public JTableStateManager(JTable table) {
66
        this(table, null);
67
    }
68
 
69
    public JTableStateManager(JTable table, File f) {
70
        this(table, f, f != null);
71
    }
72
 
73
    public JTableStateManager(JTable table, File f, boolean autosave) {
74
        super(table, f, autosave);
75
    }
76
 
77
    @Override
78
    protected AncestorListener createListener() {
79
        return new AncestorListener() {
80
 
81
            public void ancestorAdded(AncestorEvent event) {
156 ilm 82
                // nothing
17 ilm 83
            }
84
 
85
            public void ancestorMoved(AncestorEvent event) {
156 ilm 86
                // nothing
17 ilm 87
            }
88
 
89
            public void ancestorRemoved(AncestorEvent event) {
90
                try {
91
                    saveState();
92
                } catch (IOException e) {
73 ilm 93
                    ExceptionHandler.handle(getSrc(), TM.tr("saveColumnsWidth"), e);
17 ilm 94
                }
95
            }
96
        };
97
    }
98
 
99
    @Override
100
    protected void addListener(AncestorListener l) {
101
        this.getSrc().addAncestorListener(l);
102
    }
103
 
104
    @Override
105
    protected void rmListener(AncestorListener l) {
106
        this.getSrc().removeAncestorListener(l);
107
    }
108
 
109
    @Override
67 ilm 110
    protected void writeState(File out) throws IOException {
17 ilm 111
        final DocumentBuilder builder;
112
        try {
113
            // about 1ms
114
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
115
        } catch (ParserConfigurationException e) {
116
            throw new IOException("Couldn't create builder", e);
117
        }
118
 
119
        final Document doc = builder.newDocument();
120
        final Element elem = doc.createElement("liste");
121
        elem.setAttribute("version", VERSION);
122
        doc.appendChild(elem);
123
 
124
        final TableColumnModel model = this.getSrc().getColumnModel();
156 ilm 125
        final XTableColumnModel visibilityModel = model instanceof XTableColumnModel ? (XTableColumnModel) model : null;
73 ilm 126
        final TableModel tModel = this.getSrc().getModel();
156 ilm 127
        if (visibilityModel != null) {
17 ilm 128
            for (final TableColumn col : visibilityModel.getColumns(false)) {
73 ilm 129
                writeCol(elem, col, tModel).setAttribute("visible", String.valueOf(visibilityModel.isColumnVisible(col)));
17 ilm 130
            }
131
        } else {
132
            final int nCol = this.getSrc().getColumnCount();
133
            for (int i = 0; i < nCol; i++) {
134
                final TableColumn col = model.getColumn(i);
73 ilm 135
                writeCol(elem, col, tModel);
17 ilm 136
            }
137
        }
138
 
156 ilm 139
        if (tModel instanceof TableSorter) {
140
            TableSorter sorter = (TableSorter) tModel;
141
            final Element sortingColsElem = doc.createElement("sortingColumns");
142
            elem.appendChild(sortingColsElem);
143
            for (final Directive d : sorter.getSortingColumns()) {
144
                final Element colElem = doc.createElement("sortCol");
182 ilm 145
 
156 ilm 146
                sortingColsElem.appendChild(colElem);
147
                final TableColumn col;
148
                if (visibilityModel != null) {
149
                    col = visibilityModel.getColumnByModelIndex(d.getColumn());
182 ilm 150
                    if (col == null) {
151
                        Log.get().warning("null column in visibilityModel column : " + d.getColumn());
152
                    }
156 ilm 153
                } else {
154
                    col = model.getColumn(getSrc().convertColumnIndexToView(d.getColumn()));
182 ilm 155
                    if (col == null) {
156
                        Log.get().warning("null column in model for : " + getSrc().convertColumnIndexToView(d.getColumn()));
157
                    }
156 ilm 158
                }
182 ilm 159
                if (col != null) {
160
                    colElem.setAttribute(IDENTIFIER_ATTR, String.valueOf(col.getIdentifier()));
161
                    final int status = d.getDirection();
162
                    setSortAttribute(colElem, status);
163
                }
156 ilm 164
            }
165
        }
166
 
17 ilm 167
        // Use a Transformer for output
168
        final TransformerFactory tFactory = TransformerFactory.newInstance();
169
        try {
156 ilm 170
            tFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
17 ilm 171
            final Transformer transformer = tFactory.newTransformer();
156 ilm 172
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
17 ilm 173
            transformer.transform(new DOMSource(elem), new StreamResult(out));
174
        } catch (TransformerException e) {
175
            throw new IOException("Couldn't output " + doc, e);
176
        }
177
    }
178
 
73 ilm 179
    private Element writeCol(final Element elem, final TableColumn col, TableModel tModel) {
17 ilm 180
        final Element res = elem.getOwnerDocument().createElement("col");
181
        elem.appendChild(res);
182
        int min = col.getMinWidth();
183
        int max = col.getMaxWidth();
184
        int width = col.getWidth();
185
        res.setAttribute("min", String.valueOf(min));
186
        res.setAttribute("max", String.valueOf(max));
187
        res.setAttribute("width", String.valueOf(width));
156 ilm 188
        res.setAttribute(IDENTIFIER_ATTR, String.valueOf(col.getIdentifier()));
189
        res.setAttribute(MODEL_INDEX_ATTR, String.valueOf(col.getModelIndex()));
190
        return res;
191
    }
192
 
193
    protected final void setSortAttribute(final Element res, final int status) {
194
        if (status == TableSorter.ASCENDING) {
195
            res.setAttribute(SORT_ATTR, "ascending");
196
        } else if (status == TableSorter.DESCENDING) {
197
            res.setAttribute(SORT_ATTR, "descending");
198
        }
199
    }
200
 
201
    protected final int getSortAttribute(final NamedNodeMap attrs) {
202
        final Node sortNode = attrs.getNamedItem(SORT_ATTR);
203
        if (sortNode != null) {
204
            final String sort = sortNode.getNodeValue();
205
            if (sort.equals("ascending")) {
206
                return TableSorter.ASCENDING;
207
            } else if (sort.equals("descending")) {
208
                return TableSorter.DESCENDING;
209
            } else {
210
                Log.get().log(Level.INFO, "ignore unknown sort value : {0}", sort);
73 ilm 211
            }
212
        }
156 ilm 213
        return TableSorter.NOT_SORTED;
17 ilm 214
    }
215
 
216
    /**
156 ilm 217
     * Met les colonnes comme spécifier dans <code>file</code>. Ne fait rien si <code>file</code>
17 ilm 218
     * n'existe pas.
219
     *
220
     * @param file le fichier à charger.
221
     */
222
    @Override
223
    protected boolean readState(Document doc) {
224
        NodeList listOfCol = doc.getElementsByTagName("col");
225
        final TableColumnModel model = this.getSrc().getColumnModel();
226
        final XTableColumnModel visibilityModel = model instanceof XTableColumnModel ? (XTableColumnModel) model : null;
227
        // some columns might already be invisible so use the total number of columns
228
        final List<TableColumn> uiCols = visibilityModel != null ? visibilityModel.getColumns(false) : Collections.list(model.getColumns());
229
        final int modelColsCount = uiCols.size();
230
        final int colsCount = listOfCol.getLength();
231
        final String docVersion = doc.getDocumentElement().getAttribute("version");
232
        if (!VERSION.equals(docVersion)) {
233
            Log.get().info("wrong version :" + docVersion + " != " + VERSION);
234
        } else if (modelColsCount != colsCount) {
235
            // MAYBE store modelCol.getIdentifier(), to be able to find the width
236
            // (ie width = stored width * (sum of all stored cols)/(sum of existing cols))
237
            Log.get().info("saved cols :" + colsCount + " != actual cols: " + modelColsCount);
238
        } else if (!checkIdentifiers(listOfCol, uiCols)) {
239
            Log.get().info("column identifiers differ");
240
        } else {
241
            // for JTable.convertColumnIndexToView() to work, all columns must be visible
242
            // ok since the visible attribute will take care of the visibility
243
            // MAYBE implement convertColumnIndexToView() ourselves and forgo setVisible(true)
244
            // before and setVisible(false) after
245
            if (visibilityModel != null) {
246
                for (int i = 0; i < colsCount; i++) {
247
                    visibilityModel.setColumnVisible(visibilityModel.getColumn(i, false), true);
248
                }
249
            }
73 ilm 250
            final TableModel tModel = this.getSrc().getModel();
156 ilm 251
            final List<TableColumn> invisibleCols = new ArrayList<>();
17 ilm 252
            for (int i = 0; i < colsCount; i++) {
253
                final NamedNodeMap attrs = listOfCol.item(i).getAttributes();
254
                // index
156 ilm 255
                final int modelIndex = Integer.parseInt(attrs.getNamedItem(MODEL_INDEX_ATTR).getNodeValue());
17 ilm 256
                // move from the current index to the final view index
257
                model.moveColumn(this.getSrc().convertColumnIndexToView(modelIndex), i);
258
 
259
                final TableColumn modelCol = model.getColumn(i);
260
 
261
                // Taille min
262
                String smin = (attrs.getNamedItem("min").getNodeValue());
263
                int min = Integer.parseInt(smin);
264
                if (min < 10) {
265
                    min = 10;
266
                }
267
                modelCol.setMinWidth(min);
268
 
269
                // Taille max
270
                String smax = (attrs.getNamedItem("max").getNodeValue());
271
                int max = Integer.parseInt(smax);
272
                modelCol.setMaxWidth(max);
273
 
274
                // Taille voulue
275
                String ssize = (attrs.getNamedItem("width").getNodeValue());
276
                int size = Integer.parseInt(ssize);
277
                if (size < 10) {
278
                    size = 15;
279
                }
280
                modelCol.setWidth(size);
281
                modelCol.setPreferredWidth(size);
282
 
73 ilm 283
                // Visibility
17 ilm 284
                final Node visible = attrs.getNamedItem("visible");
285
                // don't call setColumnVisible() now since it removes the column and this offsets
286
                // indexes, only deal will invisible since by now all columns are visible
287
                if (visible != null && !Boolean.parseBoolean(visible.getNodeValue()))
288
                    invisibleCols.add(modelCol);
73 ilm 289
 
156 ilm 290
                // Better not to restore sorting at all, than to restore wrong sorting. So remove
291
                // the code that was here since it didn't restore the order of the sorting columns
292
                // (e.g. first sort by country, then by age).
293
            }
73 ilm 294
 
156 ilm 295
            // Sorting
296
            final NodeList listOfSortCol = doc.getElementsByTagName("sortCol");
297
            final int sortColCount = listOfSortCol.getLength();
298
            if (tModel instanceof TableSorter && sortColCount > 0) {
299
                final List<Directive> sortingCols = new ArrayList<>();
300
                for (int i = 0; i < sortColCount; i++) {
301
                    final NamedNodeMap attrs = listOfSortCol.item(i).getAttributes();
302
 
303
                    final String colID = attrs.getNamedItem(IDENTIFIER_ATTR).getNodeValue();
304
                    final int colIndex;
305
                    try {
306
                        colIndex = model.getColumnIndex(colID);
307
                    } catch (Exception e) {
308
                        Log.get().log(Level.INFO, "ignore unknown identifier : " + colID, e);
309
                        continue;
73 ilm 310
                    }
156 ilm 311
                    final int modelIndex = model.getColumn(colIndex).getModelIndex();
312
                    final int direction = getSortAttribute(attrs);
313
                    if (direction == TableSorter.NOT_SORTED) {
314
                        Log.get().info("ignore sort value for column " + colID);
315
                        continue;
316
                    }
317
                    sortingCols.add(new Directive(modelIndex, direction));
73 ilm 318
                }
156 ilm 319
                ((TableSorter) tModel).setSortingColumns(sortingCols);
17 ilm 320
            }
321
            if (visibilityModel != null) {
322
                for (final TableColumn toRm : invisibleCols) {
323
                    visibilityModel.setColumnVisible(toRm, false);
324
                }
325
            }
326
            return true;
327
        }
328
        return false;
329
    }
330
 
331
    // check ID to avoid : initial state columns A,B,C,D : count = 4
332
    // but just after add E and hide C : count also = 4, so if the second state is recorded it will
333
    // be wrongfully restored in the first step.
334
    private boolean checkIdentifiers(NodeList listOfCol, List<TableColumn> uiCols) {
335
        final int colsCount = listOfCol.getLength();
336
        for (int i = 0; i < colsCount; i++) {
337
            final NamedNodeMap attrs = listOfCol.item(i).getAttributes();
156 ilm 338
            final int modelIndex = Integer.parseInt(attrs.getNamedItem(MODEL_INDEX_ATTR).getNodeValue());
339
            final String xmlID = attrs.getNamedItem(IDENTIFIER_ATTR).getNodeValue();
17 ilm 340
            final String uiID = String.valueOf(uiCols.get(modelIndex).getIdentifier());
341
            if (!uiID.equals(xmlID))
342
                return false;
343
        }
344
        return true;
345
    }
346
}