Dépôt officiel du code source de l'ERP OpenConcerto
Rev 156 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.ui.state;
import org.openconcerto.ui.Log;
import org.openconcerto.ui.TM;
import org.openconcerto.ui.table.XTableColumnModel;
import org.openconcerto.utils.ExceptionHandler;
import org.openconcerto.utils.TableSorter;
import org.openconcerto.utils.TableSorter.Directive;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import javax.swing.JTable;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Save the width and order of columns in a JTable.
*
* @author Sylvain
*/
public class JTableStateManager extends ListenerXMLStateManager<JTable, AncestorListener> {
private static final String VERSION = "20100810";
private static final String IDENTIFIER_ATTR = "identifier";
private static final String MODEL_INDEX_ATTR = "modelIndex";
private static final String SORT_ATTR = "sort";
public JTableStateManager(JTable table) {
this(table, null);
}
public JTableStateManager(JTable table, File f) {
this(table, f, f != null);
}
public JTableStateManager(JTable table, File f, boolean autosave) {
super(table, f, autosave);
}
@Override
protected AncestorListener createListener() {
return new AncestorListener() {
public void ancestorAdded(AncestorEvent event) {
// nothing
}
public void ancestorMoved(AncestorEvent event) {
// nothing
}
public void ancestorRemoved(AncestorEvent event) {
try {
saveState();
} catch (IOException e) {
ExceptionHandler.handle(getSrc(), TM.tr("saveColumnsWidth"), e);
}
}
};
}
@Override
protected void addListener(AncestorListener l) {
this.getSrc().addAncestorListener(l);
}
@Override
protected void rmListener(AncestorListener l) {
this.getSrc().removeAncestorListener(l);
}
@Override
protected void writeState(File out) throws IOException {
final DocumentBuilder builder;
try {
// about 1ms
builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new IOException("Couldn't create builder", e);
}
final Document doc = builder.newDocument();
final Element elem = doc.createElement("liste");
elem.setAttribute("version", VERSION);
doc.appendChild(elem);
final TableColumnModel model = this.getSrc().getColumnModel();
final XTableColumnModel visibilityModel = model instanceof XTableColumnModel ? (XTableColumnModel) model : null;
final TableModel tModel = this.getSrc().getModel();
if (visibilityModel != null) {
for (final TableColumn col : visibilityModel.getColumns(false)) {
writeCol(elem, col, tModel).setAttribute("visible", String.valueOf(visibilityModel.isColumnVisible(col)));
}
} else {
final int nCol = this.getSrc().getColumnCount();
for (int i = 0; i < nCol; i++) {
final TableColumn col = model.getColumn(i);
writeCol(elem, col, tModel);
}
}
if (tModel instanceof TableSorter) {
TableSorter sorter = (TableSorter) tModel;
final Element sortingColsElem = doc.createElement("sortingColumns");
elem.appendChild(sortingColsElem);
for (final Directive d : sorter.getSortingColumns()) {
final Element colElem = doc.createElement("sortCol");
sortingColsElem.appendChild(colElem);
final TableColumn col;
if (visibilityModel != null) {
col = visibilityModel.getColumnByModelIndex(d.getColumn());
if (col == null) {
Log.get().warning("null column in visibilityModel column : " + d.getColumn());
}
} else {
col = model.getColumn(getSrc().convertColumnIndexToView(d.getColumn()));
if (col == null) {
Log.get().warning("null column in model for : " + getSrc().convertColumnIndexToView(d.getColumn()));
}
}
if (col != null) {
colElem.setAttribute(IDENTIFIER_ATTR, String.valueOf(col.getIdentifier()));
final int status = d.getDirection();
setSortAttribute(colElem, status);
}
}
}
// Use a Transformer for output
final TransformerFactory tFactory = TransformerFactory.newInstance();
try {
tFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
final Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(new DOMSource(elem), new StreamResult(out));
} catch (TransformerException e) {
throw new IOException("Couldn't output " + doc, e);
}
}
private Element writeCol(final Element elem, final TableColumn col, TableModel tModel) {
final Element res = elem.getOwnerDocument().createElement("col");
elem.appendChild(res);
int min = col.getMinWidth();
int max = col.getMaxWidth();
int width = col.getWidth();
res.setAttribute("min", String.valueOf(min));
res.setAttribute("max", String.valueOf(max));
res.setAttribute("width", String.valueOf(width));
res.setAttribute(IDENTIFIER_ATTR, String.valueOf(col.getIdentifier()));
res.setAttribute(MODEL_INDEX_ATTR, String.valueOf(col.getModelIndex()));
return res;
}
protected final void setSortAttribute(final Element res, final int status) {
if (status == TableSorter.ASCENDING) {
res.setAttribute(SORT_ATTR, "ascending");
} else if (status == TableSorter.DESCENDING) {
res.setAttribute(SORT_ATTR, "descending");
}
}
protected final int getSortAttribute(final NamedNodeMap attrs) {
final Node sortNode = attrs.getNamedItem(SORT_ATTR);
if (sortNode != null) {
final String sort = sortNode.getNodeValue();
if (sort.equals("ascending")) {
return TableSorter.ASCENDING;
} else if (sort.equals("descending")) {
return TableSorter.DESCENDING;
} else {
Log.get().log(Level.INFO, "ignore unknown sort value : {0}", sort);
}
}
return TableSorter.NOT_SORTED;
}
/**
* Met les colonnes comme spécifier dans <code>file</code>. Ne fait rien si <code>file</code>
* n'existe pas.
*
* @param file le fichier à charger.
*/
@Override
protected boolean readState(Document doc) {
NodeList listOfCol = doc.getElementsByTagName("col");
final TableColumnModel model = this.getSrc().getColumnModel();
final XTableColumnModel visibilityModel = model instanceof XTableColumnModel ? (XTableColumnModel) model : null;
// some columns might already be invisible so use the total number of columns
final List<TableColumn> uiCols = visibilityModel != null ? visibilityModel.getColumns(false) : Collections.list(model.getColumns());
final int modelColsCount = uiCols.size();
final int colsCount = listOfCol.getLength();
final String docVersion = doc.getDocumentElement().getAttribute("version");
if (!VERSION.equals(docVersion)) {
Log.get().info("wrong version :" + docVersion + " != " + VERSION);
} else if (modelColsCount != colsCount) {
// MAYBE store modelCol.getIdentifier(), to be able to find the width
// (ie width = stored width * (sum of all stored cols)/(sum of existing cols))
Log.get().info("saved cols :" + colsCount + " != actual cols: " + modelColsCount);
} else if (!checkIdentifiers(listOfCol, uiCols)) {
Log.get().info("column identifiers differ");
} else {
// for JTable.convertColumnIndexToView() to work, all columns must be visible
// ok since the visible attribute will take care of the visibility
// MAYBE implement convertColumnIndexToView() ourselves and forgo setVisible(true)
// before and setVisible(false) after
if (visibilityModel != null) {
for (int i = 0; i < colsCount; i++) {
visibilityModel.setColumnVisible(visibilityModel.getColumn(i, false), true);
}
}
final TableModel tModel = this.getSrc().getModel();
final List<TableColumn> invisibleCols = new ArrayList<>();
for (int i = 0; i < colsCount; i++) {
final NamedNodeMap attrs = listOfCol.item(i).getAttributes();
// index
final int modelIndex = Integer.parseInt(attrs.getNamedItem(MODEL_INDEX_ATTR).getNodeValue());
// move from the current index to the final view index
model.moveColumn(this.getSrc().convertColumnIndexToView(modelIndex), i);
final TableColumn modelCol = model.getColumn(i);
// Taille min
String smin = (attrs.getNamedItem("min").getNodeValue());
int min = Integer.parseInt(smin);
if (min < 10) {
min = 10;
}
modelCol.setMinWidth(min);
// Taille max
String smax = (attrs.getNamedItem("max").getNodeValue());
int max = Integer.parseInt(smax);
modelCol.setMaxWidth(max);
// Taille voulue
String ssize = (attrs.getNamedItem("width").getNodeValue());
int size = Integer.parseInt(ssize);
if (size < 10) {
size = 15;
}
modelCol.setWidth(size);
modelCol.setPreferredWidth(size);
// Visibility
final Node visible = attrs.getNamedItem("visible");
// don't call setColumnVisible() now since it removes the column and this offsets
// indexes, only deal will invisible since by now all columns are visible
if (visible != null && !Boolean.parseBoolean(visible.getNodeValue()))
invisibleCols.add(modelCol);
// Better not to restore sorting at all, than to restore wrong sorting. So remove
// the code that was here since it didn't restore the order of the sorting columns
// (e.g. first sort by country, then by age).
}
// Sorting
final NodeList listOfSortCol = doc.getElementsByTagName("sortCol");
final int sortColCount = listOfSortCol.getLength();
if (tModel instanceof TableSorter && sortColCount > 0) {
final List<Directive> sortingCols = new ArrayList<>();
for (int i = 0; i < sortColCount; i++) {
final NamedNodeMap attrs = listOfSortCol.item(i).getAttributes();
final String colID = attrs.getNamedItem(IDENTIFIER_ATTR).getNodeValue();
final int colIndex;
try {
colIndex = model.getColumnIndex(colID);
} catch (Exception e) {
Log.get().log(Level.INFO, "ignore unknown identifier : " + colID, e);
continue;
}
final int modelIndex = model.getColumn(colIndex).getModelIndex();
final int direction = getSortAttribute(attrs);
if (direction == TableSorter.NOT_SORTED) {
Log.get().info("ignore sort value for column " + colID);
continue;
}
sortingCols.add(new Directive(modelIndex, direction));
}
((TableSorter) tModel).setSortingColumns(sortingCols);
}
if (visibilityModel != null) {
for (final TableColumn toRm : invisibleCols) {
visibilityModel.setColumnVisible(toRm, false);
}
}
return true;
}
return false;
}
// check ID to avoid : initial state columns A,B,C,D : count = 4
// but just after add E and hide C : count also = 4, so if the second state is recorded it will
// be wrongfully restored in the first step.
private boolean checkIdentifiers(NodeList listOfCol, List<TableColumn> uiCols) {
final int colsCount = listOfCol.getLength();
for (int i = 0; i < colsCount; i++) {
final NamedNodeMap attrs = listOfCol.item(i).getAttributes();
final int modelIndex = Integer.parseInt(attrs.getNamedItem(MODEL_INDEX_ATTR).getNodeValue());
final String xmlID = attrs.getNamedItem(IDENTIFIER_ATTR).getNodeValue();
final String uiID = String.valueOf(uiCols.get(modelIndex).getIdentifier());
if (!uiID.equals(xmlID))
return false;
}
return true;
}
}