OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 73 | Go to most recent revision | 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
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
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");
145
                sortingColsElem.appendChild(colElem);
146
                final TableColumn col;
147
                if (visibilityModel != null) {
148
                    col = visibilityModel.getColumnByModelIndex(d.getColumn());
149
                } else {
150
                    col = model.getColumn(getSrc().convertColumnIndexToView(d.getColumn()));
151
                }
152
                colElem.setAttribute(IDENTIFIER_ATTR, String.valueOf(col.getIdentifier()));
153
                final int status = d.getDirection();
154
                setSortAttribute(colElem, status);
155
            }
156
        }
157
 
17 ilm 158
        // Use a Transformer for output
159
        final TransformerFactory tFactory = TransformerFactory.newInstance();
160
        try {
156 ilm 161
            tFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
17 ilm 162
            final Transformer transformer = tFactory.newTransformer();
156 ilm 163
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
17 ilm 164
            transformer.transform(new DOMSource(elem), new StreamResult(out));
165
        } catch (TransformerException e) {
166
            throw new IOException("Couldn't output " + doc, e);
167
        }
168
    }
169
 
73 ilm 170
    private Element writeCol(final Element elem, final TableColumn col, TableModel tModel) {
17 ilm 171
        final Element res = elem.getOwnerDocument().createElement("col");
172
        elem.appendChild(res);
173
        int min = col.getMinWidth();
174
        int max = col.getMaxWidth();
175
        int width = col.getWidth();
176
        res.setAttribute("min", String.valueOf(min));
177
        res.setAttribute("max", String.valueOf(max));
178
        res.setAttribute("width", String.valueOf(width));
156 ilm 179
        res.setAttribute(IDENTIFIER_ATTR, String.valueOf(col.getIdentifier()));
180
        res.setAttribute(MODEL_INDEX_ATTR, String.valueOf(col.getModelIndex()));
181
        return res;
182
    }
183
 
184
    protected final void setSortAttribute(final Element res, final int status) {
185
        if (status == TableSorter.ASCENDING) {
186
            res.setAttribute(SORT_ATTR, "ascending");
187
        } else if (status == TableSorter.DESCENDING) {
188
            res.setAttribute(SORT_ATTR, "descending");
189
        }
190
    }
191
 
192
    protected final int getSortAttribute(final NamedNodeMap attrs) {
193
        final Node sortNode = attrs.getNamedItem(SORT_ATTR);
194
        if (sortNode != null) {
195
            final String sort = sortNode.getNodeValue();
196
            if (sort.equals("ascending")) {
197
                return TableSorter.ASCENDING;
198
            } else if (sort.equals("descending")) {
199
                return TableSorter.DESCENDING;
200
            } else {
201
                Log.get().log(Level.INFO, "ignore unknown sort value : {0}", sort);
73 ilm 202
            }
203
        }
156 ilm 204
        return TableSorter.NOT_SORTED;
17 ilm 205
    }
206
 
207
    /**
156 ilm 208
     * Met les colonnes comme spécifier dans <code>file</code>. Ne fait rien si <code>file</code>
17 ilm 209
     * n'existe pas.
210
     *
211
     * @param file le fichier à charger.
212
     */
213
    @Override
214
    protected boolean readState(Document doc) {
215
        NodeList listOfCol = doc.getElementsByTagName("col");
216
        final TableColumnModel model = this.getSrc().getColumnModel();
217
        final XTableColumnModel visibilityModel = model instanceof XTableColumnModel ? (XTableColumnModel) model : null;
218
        // some columns might already be invisible so use the total number of columns
219
        final List<TableColumn> uiCols = visibilityModel != null ? visibilityModel.getColumns(false) : Collections.list(model.getColumns());
220
        final int modelColsCount = uiCols.size();
221
        final int colsCount = listOfCol.getLength();
222
        final String docVersion = doc.getDocumentElement().getAttribute("version");
223
        if (!VERSION.equals(docVersion)) {
224
            Log.get().info("wrong version :" + docVersion + " != " + VERSION);
225
        } else if (modelColsCount != colsCount) {
226
            // MAYBE store modelCol.getIdentifier(), to be able to find the width
227
            // (ie width = stored width * (sum of all stored cols)/(sum of existing cols))
228
            Log.get().info("saved cols :" + colsCount + " != actual cols: " + modelColsCount);
229
        } else if (!checkIdentifiers(listOfCol, uiCols)) {
230
            Log.get().info("column identifiers differ");
231
        } else {
232
            // for JTable.convertColumnIndexToView() to work, all columns must be visible
233
            // ok since the visible attribute will take care of the visibility
234
            // MAYBE implement convertColumnIndexToView() ourselves and forgo setVisible(true)
235
            // before and setVisible(false) after
236
            if (visibilityModel != null) {
237
                for (int i = 0; i < colsCount; i++) {
238
                    visibilityModel.setColumnVisible(visibilityModel.getColumn(i, false), true);
239
                }
240
            }
73 ilm 241
            final TableModel tModel = this.getSrc().getModel();
156 ilm 242
            final List<TableColumn> invisibleCols = new ArrayList<>();
17 ilm 243
            for (int i = 0; i < colsCount; i++) {
244
                final NamedNodeMap attrs = listOfCol.item(i).getAttributes();
245
                // index
156 ilm 246
                final int modelIndex = Integer.parseInt(attrs.getNamedItem(MODEL_INDEX_ATTR).getNodeValue());
17 ilm 247
                // move from the current index to the final view index
248
                model.moveColumn(this.getSrc().convertColumnIndexToView(modelIndex), i);
249
 
250
                final TableColumn modelCol = model.getColumn(i);
251
 
252
                // Taille min
253
                String smin = (attrs.getNamedItem("min").getNodeValue());
254
                int min = Integer.parseInt(smin);
255
                if (min < 10) {
256
                    min = 10;
257
                }
258
                modelCol.setMinWidth(min);
259
 
260
                // Taille max
261
                String smax = (attrs.getNamedItem("max").getNodeValue());
262
                int max = Integer.parseInt(smax);
263
                modelCol.setMaxWidth(max);
264
 
265
                // Taille voulue
266
                String ssize = (attrs.getNamedItem("width").getNodeValue());
267
                int size = Integer.parseInt(ssize);
268
                if (size < 10) {
269
                    size = 15;
270
                }
271
                modelCol.setWidth(size);
272
                modelCol.setPreferredWidth(size);
273
 
73 ilm 274
                // Visibility
17 ilm 275
                final Node visible = attrs.getNamedItem("visible");
276
                // don't call setColumnVisible() now since it removes the column and this offsets
277
                // indexes, only deal will invisible since by now all columns are visible
278
                if (visible != null && !Boolean.parseBoolean(visible.getNodeValue()))
279
                    invisibleCols.add(modelCol);
73 ilm 280
 
156 ilm 281
                // Better not to restore sorting at all, than to restore wrong sorting. So remove
282
                // the code that was here since it didn't restore the order of the sorting columns
283
                // (e.g. first sort by country, then by age).
284
            }
73 ilm 285
 
156 ilm 286
            // Sorting
287
            final NodeList listOfSortCol = doc.getElementsByTagName("sortCol");
288
            final int sortColCount = listOfSortCol.getLength();
289
            if (tModel instanceof TableSorter && sortColCount > 0) {
290
                final List<Directive> sortingCols = new ArrayList<>();
291
                for (int i = 0; i < sortColCount; i++) {
292
                    final NamedNodeMap attrs = listOfSortCol.item(i).getAttributes();
293
 
294
                    final String colID = attrs.getNamedItem(IDENTIFIER_ATTR).getNodeValue();
295
                    final int colIndex;
296
                    try {
297
                        colIndex = model.getColumnIndex(colID);
298
                    } catch (Exception e) {
299
                        Log.get().log(Level.INFO, "ignore unknown identifier : " + colID, e);
300
                        continue;
73 ilm 301
                    }
156 ilm 302
                    final int modelIndex = model.getColumn(colIndex).getModelIndex();
303
                    final int direction = getSortAttribute(attrs);
304
                    if (direction == TableSorter.NOT_SORTED) {
305
                        Log.get().info("ignore sort value for column " + colID);
306
                        continue;
307
                    }
308
                    sortingCols.add(new Directive(modelIndex, direction));
73 ilm 309
                }
156 ilm 310
                ((TableSorter) tModel).setSortingColumns(sortingCols);
17 ilm 311
            }
312
            if (visibilityModel != null) {
313
                for (final TableColumn toRm : invisibleCols) {
314
                    visibilityModel.setColumnVisible(toRm, false);
315
                }
316
            }
317
            return true;
318
        }
319
        return false;
320
    }
321
 
322
    // check ID to avoid : initial state columns A,B,C,D : count = 4
323
    // but just after add E and hide C : count also = 4, so if the second state is recorded it will
324
    // be wrongfully restored in the first step.
325
    private boolean checkIdentifiers(NodeList listOfCol, List<TableColumn> uiCols) {
326
        final int colsCount = listOfCol.getLength();
327
        for (int i = 0; i < colsCount; i++) {
328
            final NamedNodeMap attrs = listOfCol.item(i).getAttributes();
156 ilm 329
            final int modelIndex = Integer.parseInt(attrs.getNamedItem(MODEL_INDEX_ATTR).getNodeValue());
330
            final String xmlID = attrs.getNamedItem(IDENTIFIER_ATTR).getNodeValue();
17 ilm 331
            final String uiID = String.valueOf(uiCols.get(modelIndex).getIdentifier());
332
            if (!uiID.equals(xmlID))
333
                return false;
334
        }
335
        return true;
336
    }
337
}