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 |
}
|