OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 93 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 93 Rev 156
1
/*
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 * 
3
 * 
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 * 
5
 * 
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
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
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
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.
9
 * language governing permissions and limitations under the License.
10
 * 
10
 * 
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.openoffice.spreadsheet;
14
 package org.openconcerto.openoffice.spreadsheet;
15
 
15
 
16
import org.openconcerto.openoffice.Length;
16
import org.openconcerto.openoffice.Length;
17
import org.openconcerto.openoffice.LengthUnit;
17
import org.openconcerto.openoffice.LengthUnit;
18
import org.openconcerto.openoffice.ODDocument;
18
import org.openconcerto.openoffice.ODDocument;
19
import org.openconcerto.openoffice.Style;
19
import org.openconcerto.openoffice.Style;
20
import org.openconcerto.openoffice.StyleDesc;
20
import org.openconcerto.openoffice.StyleDesc;
21
import org.openconcerto.openoffice.StyleStyleDesc;
21
import org.openconcerto.openoffice.StyleStyleDesc;
22
import org.openconcerto.openoffice.StyledNode;
22
import org.openconcerto.openoffice.StyledNode;
23
import org.openconcerto.openoffice.XMLVersion;
23
import org.openconcerto.openoffice.XMLVersion;
24
import org.openconcerto.openoffice.spreadsheet.CellStyle.StyleTableCellProperties;
24
import org.openconcerto.openoffice.spreadsheet.CellStyle.StyleTableCellProperties;
25
import org.openconcerto.openoffice.spreadsheet.SheetTableModel.MutableTableModel;
25
import org.openconcerto.openoffice.spreadsheet.SheetTableModel.MutableTableModel;
26
import org.openconcerto.utils.CollectionUtils;
26
import org.openconcerto.utils.CollectionUtils;
27
import org.openconcerto.utils.Tuple2;
27
import org.openconcerto.utils.Tuple2;
28
import org.openconcerto.xml.JDOMUtils;
28
import org.openconcerto.xml.JDOMUtils;
29
import org.openconcerto.xml.SimpleXMLPath;
29
import org.openconcerto.xml.SimpleXMLPath;
30
 
30
 
31
import java.awt.Point;
31
import java.awt.Point;
32
import java.util.ArrayList;
32
import java.util.ArrayList;
33
import java.util.HashMap;
33
import java.util.HashMap;
34
import java.util.HashSet;
34
import java.util.HashSet;
35
import java.util.List;
35
import java.util.List;
36
import java.util.Map;
36
import java.util.Map;
37
import java.util.Set;
37
import java.util.Set;
38
import java.util.regex.Matcher;
38
import java.util.regex.Matcher;
39
 
39
 
40
import javax.swing.table.TableModel;
40
import javax.swing.table.TableModel;
41
 
41
 
42
import org.jdom.Attribute;
42
import org.jdom.Attribute;
43
import org.jdom.Element;
43
import org.jdom.Element;
44
 
44
 
45
/**
45
/**
46
 * A single sheet in a spreadsheet.
46
 * A single sheet in a spreadsheet.
47
 * 
47
 * 
48
 * @author Sylvain
48
 * @author Sylvain
49
 * @param <D> type of table parent
49
 * @param <D> type of table parent
50
 */
50
 */
51
public class Table<D extends ODDocument> extends TableCalcNode<TableStyle, D> {
51
public class Table<D extends ODDocument> extends TableCalcNode<TableStyle, D> {
52
 
52
 
53
    static Element createEmpty(XMLVersion ns) {
53
    static Element createEmpty(XMLVersion ns) {
54
        return createEmpty(ns, 1, 1);
54
        return createEmpty(ns, 1, 1);
55
    }
55
    }
56
 
56
 
57
    static Element createEmpty(final XMLVersion ns, final int colCount, final int rowCount) {
57
    static Element createEmpty(final XMLVersion ns, final int colCount, final int rowCount) {
58
        return createEmpty(ns, colCount, rowCount, null);
58
        return createEmpty(ns, colCount, rowCount, null);
59
    }
59
    }
60
 
60
 
61
    // pass styleName to avoid creating possibly expensive Table instance (readCols/readRows) in
61
    // pass styleName to avoid creating possibly expensive Table instance (readCols/readRows) in
62
    // order to use setStyleName()
62
    // order to use setStyleName()
63
    static Element createEmpty(final XMLVersion ns, final int colCount, final int rowCount, final String styleName) {
63
    static Element createEmpty(final XMLVersion ns, final int colCount, final int rowCount, final String styleName) {
64
        // from the relaxNG
64
        // from the relaxNG
65
        if (colCount < 1 || rowCount < 1)
65
        if (colCount < 1 || rowCount < 1)
66
            throw new IllegalArgumentException("a table must have at least one cell");
66
            throw new IllegalArgumentException("a table must have at least one cell");
67
        final Element col = Column.createEmpty(ns, null);
67
        final Element col = Column.createEmpty(ns, null);
68
        Axis.COLUMN.setRepeated(col, colCount);
68
        Axis.COLUMN.setRepeated(col, colCount);
69
        final Element row = Row.createEmpty(ns).addContent(Cell.createEmpty(ns, colCount));
69
        final Element row = Row.createEmpty(ns).addContent(Cell.createEmpty(ns, colCount));
70
        Axis.ROW.setRepeated(row, rowCount);
70
        Axis.ROW.setRepeated(row, rowCount);
71
        final Element res = new Element("table", ns.getTABLE()).addContent(col).addContent(row);
71
        final Element res = new Element("table", ns.getTABLE()).addContent(col).addContent(row);
72
        StyledNode.setStyleName(res, styleName);
72
        StyledNode.setStyleName(res, styleName);
73
        return res;
73
        return res;
74
    }
74
    }
75
 
75
 
76
    static final String getName(final Element elem) {
76
    static final String getName(final Element elem) {
77
        return elem.getAttributeValue("name", elem.getNamespace("table"));
77
        return elem.getAttributeValue("name", elem.getNamespace("table"));
78
    }
78
    }
79
 
79
 
80
    // ATTN Row have their index as attribute
80
    // ATTN Row have their index as attribute
81
    private final ArrayList<Row<D>> rows;
81
    private final ArrayList<Row<D>> rows;
82
    private TableGroup rowGroup;
82
    private TableGroup rowGroup;
83
    private final ArrayList<Column<D>> cols;
83
    private final ArrayList<Column<D>> cols;
84
    private TableGroup columnGroup;
84
    private TableGroup columnGroup;
85
 
85
 
86
    public Table(D parent, Element local) {
86
    public Table(D parent, Element local) {
87
        super(parent, local, TableStyle.class);
87
        super(parent, local, TableStyle.class);
88
 
88
 
89
        this.rows = new ArrayList<Row<D>>(64);
89
        this.rows = new ArrayList<Row<D>>(64);
90
        this.cols = new ArrayList<Column<D>>(32);
90
        this.cols = new ArrayList<Column<D>>(32);
91
 
91
 
92
        // read columns first since Row constructor needs it
92
        // read columns first since Row constructor needs it
93
        this.readColumns();
93
        this.readColumns();
94
        this.readRows();
94
        this.readRows();
95
    }
95
    }
96
 
96
 
97
    private void readColumns() {
97
    private void readColumns() {
98
        this.read(Axis.COLUMN);
98
        this.read(Axis.COLUMN);
99
    }
99
    }
100
 
100
 
101
    private final void readRows() {
101
    private final void readRows() {
102
        this.read(Axis.ROW);
102
        this.read(Axis.ROW);
103
    }
103
    }
104
 
104
 
105
    private final void read(final Axis axis) {
105
    private final void read(final Axis axis) {
106
        final boolean col = axis == Axis.COLUMN;
106
        final boolean col = axis == Axis.COLUMN;
107
        final Tuple2<TableGroup, List<Element>> r = TableGroup.createRoot(this, axis);
107
        final Tuple2<TableGroup, List<Element>> r = TableGroup.createRoot(this, axis);
108
        final ArrayList<?> l = col ? this.cols : this.rows;
108
        final ArrayList<?> l = col ? this.cols : this.rows;
109
        final int oldSize = l.size();
109
        final int oldSize = l.size();
110
        l.clear();
110
        l.clear();
111
        final int newSize = r.get0().getSize();
111
        final int newSize = r.get0().getSize();
112
        l.ensureCapacity(newSize);
112
        l.ensureCapacity(newSize);
113
        if (col) {
113
        if (col) {
114
            final StyleStyleDesc<ColumnStyle> colStyleDesc = getColumnStyleDesc();
114
            final StyleStyleDesc<ColumnStyle> colStyleDesc = getColumnStyleDesc();
115
            for (final Element clone : r.get1())
115
            for (final Element clone : r.get1())
116
                this.addCol(clone, colStyleDesc);
116
                this.addCol(clone, colStyleDesc);
117
            this.columnGroup = r.get0();
117
            this.columnGroup = r.get0();
118
        } else {
118
        } else {
119
            final StyleStyleDesc<RowStyle> rowStyleDesc = getRowStyleDesc();
119
            final StyleStyleDesc<RowStyle> rowStyleDesc = getRowStyleDesc();
120
            final StyleStyleDesc<CellStyle> cellStyleDesc = getCellStyleDesc();
120
            final StyleStyleDesc<CellStyle> cellStyleDesc = getCellStyleDesc();
121
            for (final Element clone : r.get1())
121
            for (final Element clone : r.get1())
122
                this.addRow(clone, rowStyleDesc, cellStyleDesc);
122
                this.addRow(clone, rowStyleDesc, cellStyleDesc);
123
            this.rowGroup = r.get0();
123
            this.rowGroup = r.get0();
124
        }
124
        }
125
        // this always copy the array, so make sure we reclaim enough memory (~ 64k)
125
        // this always copy the array, so make sure we reclaim enough memory (~ 64k)
126
        if (oldSize - newSize > 8192) {
126
        if (oldSize - newSize > 8192) {
127
            l.trimToSize();
127
            l.trimToSize();
128
        }
128
        }
129
        assert newSize == (col ? this.getColumnCount() : this.getRowCount());
129
        assert newSize == (col ? this.getColumnCount() : this.getRowCount());
130
    }
130
    }
131
 
131
 
132
    private final void addCol(Element clone, StyleStyleDesc<ColumnStyle> colStyleDesc) {
132
    private final void addCol(Element clone, StyleStyleDesc<ColumnStyle> colStyleDesc) {
133
        this.cols.add(new Column<D>(this, clone, colStyleDesc));
133
        this.cols.add(new Column<D>(this, clone, colStyleDesc));
134
    }
134
    }
135
 
135
 
136
    static final int flattenChildren(final List<Element> res, final Element elem, final Axis axis) {
136
    static final int flattenChildren(final List<Element> res, final Element elem, final Axis axis) {
137
        int count = 0;
137
        int count = 0;
138
        // array so that flatten1() can modify an int
138
        // array so that flatten1() can modify an int
139
        int[] index = new int[] { 0 };
139
        int[] index = new int[] { 0 };
140
        // copy since we will change our children (don't use List.listIterator(int) since it
140
        // copy since we will change our children (don't use List.listIterator(int) since it
141
        // re-filters all content)
141
        // re-filters all content)
142
        @SuppressWarnings("unchecked")
142
        @SuppressWarnings("unchecked")
143
        final List<Element> children = new ArrayList<Element>(elem.getChildren(axis.getElemName(), elem.getNamespace()));
143
        final List<Element> children = new ArrayList<Element>(elem.getChildren(axis.getElemName(), elem.getNamespace()));
144
        final int stop = children.size();
144
        final int stop = children.size();
145
        for (int i = 0; i < stop; i++) {
145
        for (int i = 0; i < stop; i++) {
146
            final Element row = children.get(i);
146
            final Element row = children.get(i);
147
            count += flatten1(res, row, axis, index);
147
            count += flatten1(res, row, axis, index);
148
        }
148
        }
149
        return count;
149
        return count;
150
    }
150
    }
151
 
151
 
152
    static int flatten1(final List<Element> res, final Element row, final Axis axis) {
152
    static int flatten1(final List<Element> res, final Element row, final Axis axis) {
153
        return flatten1(res, row, axis, null);
153
        return flatten1(res, row, axis, null);
154
    }
154
    }
155
 
155
 
156
    // add XML elements to res and return the logical count
156
    // add XML elements to res and return the logical count
157
    private static int flatten1(final List<Element> res, final Element row, final Axis axis, final int[] parentIndex) {
157
    private static int flatten1(final List<Element> res, final Element row, final Axis axis, final int[] parentIndex) {
158
        final int resSize = res.size();
158
        final int resSize = res.size();
159
        final Attribute repeatedAttr = axis.getRepeatedAttr(row);
159
        final Attribute repeatedAttr = axis.getRepeatedAttr(row);
160
        final int repeated = repeatedAttr == null ? 1 : Integer.parseInt(repeatedAttr.getValue());
160
        final int repeated = repeatedAttr == null ? 1 : Integer.parseInt(repeatedAttr.getValue());
161
        if (axis == Axis.COLUMN && repeated > 1) {
161
        if (axis == Axis.COLUMN && repeated > 1) {
162
            row.removeAttribute(repeatedAttr);
162
            row.removeAttribute(repeatedAttr);
163
            final Element parent = row.getParentElement();
163
            final Element parent = row.getParentElement();
164
            final int index = (parentIndex == null ? parent.indexOf(row) : parentIndex[0]) + 1;
164
            final int index = (parentIndex == null ? parent.indexOf(row) : parentIndex[0]) + 1;
165
            res.add(row);
165
            res.add(row);
166
            // -1 : we keep the original row
166
            // -1 : we keep the original row
167
            for (int i = 0; i < repeated - 1; i++) {
167
            for (int i = 0; i < repeated - 1; i++) {
168
                final Element clone = (Element) row.clone();
168
                final Element clone = (Element) row.clone();
169
                res.add(clone);
169
                res.add(clone);
170
                parent.addContent(index + i, clone);
170
                parent.addContent(index + i, clone);
171
            }
171
            }
172
        } else {
172
        } else {
173
            res.add(row);
173
            res.add(row);
174
        }
174
        }
175
        if (parentIndex != null)
175
        if (parentIndex != null)
176
            parentIndex[0] += res.size() - resSize;
176
            parentIndex[0] += res.size() - resSize;
177
        return repeated;
177
        return repeated;
178
    }
178
    }
179
 
179
 
180
    public final String getName() {
180
    public final String getName() {
181
        return getName(this.getElement());
181
        return getName(this.getElement());
182
    }
182
    }
183
 
183
 
184
    public final void setName(String name) {
184
    public final void setName(String name) {
185
        this.getElement().setAttribute("name", name, this.getODDocument().getVersion().getTABLE());
185
        this.getElement().setAttribute("name", name, this.getODDocument().getVersion().getTABLE());
186
    }
186
    }
187
 
187
 
188
    public void detach() {
188
    public void detach() {
189
        this.getElement().detach();
189
        this.getElement().detach();
190
    }
190
    }
191
 
191
 
192
    public final Object getPrintRanges() {
192
    public final Object getPrintRanges() {
193
        return this.getElement().getAttributeValue("print-ranges", this.getTABLE());
193
        return this.getElement().getAttributeValue("print-ranges", this.getTABLE());
194
    }
194
    }
195
 
195
 
196
    public final void setPrintRanges(String s) {
196
    public final void setPrintRanges(String s) {
197
        this.getElement().setAttribute("print-ranges", s, this.getTABLE());
197
        this.getElement().setAttribute("print-ranges", s, this.getTABLE());
198
    }
198
    }
199
 
199
 
200
    public final void removePrintRanges() {
200
    public final void removePrintRanges() {
201
        this.getElement().removeAttribute("print-ranges", this.getTABLE());
201
        this.getElement().removeAttribute("print-ranges", this.getTABLE());
202
    }
202
    }
203
 
203
 
204
    public final synchronized void duplicateFirstRows(int nbFirstRows, int nbDuplicate) {
204
    public final synchronized void duplicateFirstRows(int nbFirstRows, int nbDuplicate) {
205
        this.duplicateRows(0, nbFirstRows, nbDuplicate);
205
        this.duplicateRows(0, nbFirstRows, nbDuplicate);
206
    }
206
    }
207
 
207
 
208
    public final synchronized void insertDuplicatedRows(int rowDuplicated, int nbDuplicate) {
208
    public final synchronized void insertDuplicatedRows(int rowDuplicated, int nbDuplicate) {
209
        this.duplicateRows(rowDuplicated, 1, nbDuplicate);
209
        this.duplicateRows(rowDuplicated, 1, nbDuplicate);
210
    }
210
    }
211
 
211
 
212
    /**
212
    /**
213
     * Clone a range of rows. Eg if you want to copy once rows 2 through 5, you call
213
     * Clone a range of rows. Eg if you want to copy once rows 2 through 5, you call
214
     * <code>duplicateRows(2, 4, 1)</code>.
214
     * <code>duplicateRows(2, 4, 1)</code>.
215
     * 
215
     * 
216
     * @param start the first row to clone.
216
     * @param start the first row to clone.
217
     * @param count the number of rows after <code>start</code> to clone, i.e. 1 means clone one
217
     * @param count the number of rows after <code>start</code> to clone, i.e. 1 means clone one
218
     *        row, must be greater or equal to 0.
218
     *        row, must be greater or equal to 0.
219
     * @param copies the number of copies of the range to make, must be greater or equal to 0.
219
     * @param copies the number of copies of the range to make, must be greater or equal to 0.
220
     * @throws IllegalArgumentException if arguments are not valid.
220
     * @throws IllegalArgumentException if arguments are not valid.
221
     */
221
     */
222
    public final void duplicateRows(final int start, final int count, final int copies) throws IllegalArgumentException {
222
    public final void duplicateRows(final int start, final int count, final int copies) throws IllegalArgumentException {
223
        this.duplicateRows(start, count, copies, true);
223
        this.duplicateRows(start, count, copies, true);
224
    }
224
    }
225
 
225
 
226
    public final synchronized void duplicateRows(final int start, final int count, final int copies, final boolean updateCellAddresses) throws IllegalArgumentException {
226
    public final synchronized void duplicateRows(final int start, final int count, final int copies, final boolean updateCellAddresses) throws IllegalArgumentException {
227
        if (start < 0)
227
        if (start < 0)
228
            throw new IllegalArgumentException("Negative start index : " + start);
228
            throw new IllegalArgumentException("Negative start index : " + start);
229
        if (count < 0)
229
        if (count < 0)
230
            throw new IllegalArgumentException("Negative count : " + count);
230
            throw new IllegalArgumentException("Negative count : " + count);
231
        else if (count == 0)
231
        else if (count == 0)
232
            return;
232
            return;
233
        if (copies < 0)
233
        if (copies < 0)
234
            throw new IllegalArgumentException("Negative copies : " + copies);
234
            throw new IllegalArgumentException("Negative copies : " + copies);
235
        else if (copies == 0)
235
        else if (copies == 0)
236
            return;
236
            return;
237
        // stop is excluded
237
        // stop is excluded
238
        final int stop = start + count;
238
        final int stop = start + count;
239
        if (stop > this.getRowCount())
239
        if (stop > this.getRowCount())
240
            throw new IllegalArgumentException("Last row to duplicate (" + (stop - 1) + ") doesn't exist, there's only : " + getRowCount());
240
            throw new IllegalArgumentException("Last row to duplicate (" + (stop - 1) + ") doesn't exist, there's only : " + getRowCount());
241
 
241
 
242
        // should not change merged status
242
        // should not change merged status
243
        final Map<Point, Integer> coverOrigins = new HashMap<Point, Integer>();
243
        final Map<Point, Integer> coverOrigins = new HashMap<Point, Integer>();
244
        final List<Point> coverOriginsToUpdate = new ArrayList<Point>();
244
        final List<Point> coverOriginsToUpdate = new ArrayList<Point>();
245
        final int colCount = this.getColumnCount();
245
        final int colCount = this.getColumnCount();
246
        for (int x = 0; x < colCount;) {
246
        for (int x = 0; x < colCount;) {
247
            int y = start;
247
            int y = start;
248
            while (y < stop) {
248
            while (y < stop) {
249
                final Point coverOrigin = this.getCoverOrigin(x, y);
249
                final Point coverOrigin = this.getCoverOrigin(x, y);
250
                if (coverOrigin == null) {
250
                if (coverOrigin == null) {
251
                    y++;
251
                    y++;
252
                } else {
252
                } else {
253
                    final int lastCoveredCellRow;
253
                    final int lastCoveredCellRow;
254
                    // if we have already encountered this merged cell, skip it
254
                    // if we have already encountered this merged cell, skip it
255
                    if (coverOrigins.containsKey(coverOrigin)) {
255
                    if (coverOrigins.containsKey(coverOrigin)) {
256
                        lastCoveredCellRow = coverOrigins.get(coverOrigin);
256
                        lastCoveredCellRow = coverOrigins.get(coverOrigin);
257
                    } else {
257
                    } else {
258
                        final Cell<D> covering = this.getImmutableCellAt(coverOrigin.x, coverOrigin.y);
258
                        final Cell<D> covering = this.getImmutableCellAt(coverOrigin.x, coverOrigin.y);
259
                        lastCoveredCellRow = coverOrigin.y + covering.getRowsSpanned() - 1;
259
                        lastCoveredCellRow = coverOrigin.y + covering.getRowsSpanned() - 1;
260
                        if (coverOrigin.y < start) {
260
                        if (coverOrigin.y < start) {
261
                            if (lastCoveredCellRow < stop - 1)
261
                            if (lastCoveredCellRow < stop - 1)
262
                                throw new IllegalArgumentException("Span starts before the duplicated rows and doesn't extend past the end of duplicated rows at " + getAddress(coverOrigin));
262
                                throw new IllegalArgumentException("Span starts before the duplicated rows and doesn't extend past the end of duplicated rows at " + getAddress(coverOrigin));
263
                        } else {
263
                        } else {
264
                            if (lastCoveredCellRow > stop - 1)
264
                            if (lastCoveredCellRow > stop - 1)
265
                                throw new IllegalArgumentException("Span starts in the duplicated rows and extend past the end of duplicated rows at " + getAddress(coverOrigin));
265
                                throw new IllegalArgumentException("Span starts in the duplicated rows and extend past the end of duplicated rows at " + getAddress(coverOrigin));
266
                        }
266
                        }
267
 
267
 
268
                        coverOrigins.put(coverOrigin, lastCoveredCellRow);
268
                        coverOrigins.put(coverOrigin, lastCoveredCellRow);
269
                        // merged cells inside the duplicated rows don't need to be updated
269
                        // merged cells inside the duplicated rows don't need to be updated
270
                        if (coverOrigin.y < start || lastCoveredCellRow > stop - 1)
270
                        if (coverOrigin.y < start || lastCoveredCellRow > stop - 1)
271
                            coverOriginsToUpdate.add(coverOrigin);
271
                            coverOriginsToUpdate.add(coverOrigin);
272
                    }
272
                    }
273
                    y = lastCoveredCellRow + 1;
273
                    y = lastCoveredCellRow + 1;
274
                }
274
                }
275
            }
275
            }
276
            x++;
276
            x++;
277
        }
277
        }
278
 
278
 
279
        // clone xml elements and add them to our tree
279
        // clone xml elements and add them to our tree
280
        final List<Element> clones = new ArrayList<Element>(count * copies);
280
        final List<Element> clones = new ArrayList<Element>(count * copies);
281
        for (int l = start; l < stop;) {
281
        for (int l = start; l < stop;) {
282
            final Row<D> immutableRow = this.getRow(l);
282
            final Row<D> immutableRow = this.getRow(l);
283
            final Row<D> toClone;
283
            final Row<D> toClone;
284
            // MAYBE use something else than getMutableRow() since we don't need a single row.
284
            // MAYBE use something else than getMutableRow() since we don't need a single row.
285
            // the repeated row starts before the copied range, split it at the beginning
285
            // the repeated row starts before the copied range, split it at the beginning
286
            if (immutableRow.getY() < l) {
286
            if (immutableRow.getY() < l) {
287
                toClone = this.getMutableRow(l);
287
                toClone = this.getMutableRow(l);
288
            } else {
288
            } else {
289
                assert immutableRow.getY() == l;
289
                assert immutableRow.getY() == l;
290
                if (immutableRow.getLastY() >= stop) {
290
                if (immutableRow.getLastY() >= stop) {
291
                    // the repeated row goes beyond the copied range, split it at the end
291
                    // the repeated row goes beyond the copied range, split it at the end
292
                    assert this.getRow(stop) == immutableRow;
292
                    assert this.getRow(stop) == immutableRow;
293
                    this.getMutableRow(stop);
293
                    this.getMutableRow(stop);
294
                    toClone = this.getRow(l);
294
                    toClone = this.getRow(l);
295
                } else {
295
                } else {
296
                    toClone = immutableRow;
296
                    toClone = immutableRow;
297
                }
297
                }
298
            }
298
            }
299
            assert toClone.getY() == l;
299
            assert toClone.getY() == l;
300
            assert toClone.getLastY() < stop : "Row goes to far";
300
            assert toClone.getLastY() < stop : "Row goes to far";
301
            l += toClone.getRepeated();
301
            l += toClone.getRepeated();
302
            clones.add((Element) toClone.getElement().clone());
302
            clones.add((Element) toClone.getElement().clone());
303
        }
303
        }
304
        final int clonesSize = clones.size();
304
        final int clonesSize = clones.size();
305
        for (int i = 1; i < copies; i++) {
305
        for (int i = 1; i < copies; i++) {
306
            for (int j = 0; j < clonesSize; j++) {
306
            for (int j = 0; j < clonesSize; j++) {
307
                clones.add((Element) clones.get(j).clone());
307
                clones.add((Element) clones.get(j).clone());
308
            }
308
            }
309
        }
309
        }
310
        // works anywhere its XML element is
310
        // works anywhere its XML element is
311
        assert this.getRow(stop - 1).getLastY() == stop - 1 : "Adding XML element too far";
311
        assert this.getRow(stop - 1).getLastY() == stop - 1 : "Adding XML element too far";
312
        JDOMUtils.insertAfter(this.getRow(stop - 1).getElement(), clones);
312
        JDOMUtils.insertAfter(this.getRow(stop - 1).getElement(), clones);
313
 
313
 
314
        for (final Point coverOrigin : coverOriginsToUpdate) {
314
        for (final Point coverOrigin : coverOriginsToUpdate) {
315
            final MutableCell<D> coveringCell = getCellAt(coverOrigin);
315
            final MutableCell<D> coveringCell = getCellAt(coverOrigin);
316
            coveringCell.setRowsSpanned(coveringCell.getRowsSpanned() + count * copies);
316
            coveringCell.setRowsSpanned(coveringCell.getRowsSpanned() + count * copies);
317
        }
317
        }
318
 
318
 
319
        // synchronize our rows with our new tree (rows' index have changed)
319
        // synchronize our rows with our new tree (rows' index have changed)
320
        this.readRows();
320
        this.readRows();
321
 
321
 
322
        // 19.627 in OpenDocument-v1.2-cs01-part1 : The table:end-cell-address attribute specifies
322
        // 19.627 in OpenDocument-v1.2-cs01-part1 : The table:end-cell-address attribute specifies
323
        // end position of the shape if it is included in a spreadsheet document.
323
        // end position of the shape if it is included in a spreadsheet document.
324
        if (updateCellAddresses && getODDocument() instanceof SpreadSheet) {
324
        if (updateCellAddresses && getODDocument() instanceof SpreadSheet) {
325
            final SpreadSheet ssheet = (SpreadSheet) getODDocument();
325
            final SpreadSheet ssheet = (SpreadSheet) getODDocument();
326
            final SimpleXMLPath<Attribute> descAttrs = SimpleXMLPath.allAttributes("end-cell-address", "table");
326
            final SimpleXMLPath<Attribute> descAttrs = SimpleXMLPath.allAttributes("end-cell-address", "table");
327
            for (final Attribute endCellAttr : descAttrs.selectNodes(getElement())) {
327
            for (final Attribute endCellAttr : descAttrs.selectNodes(getElement())) {
328
                final Tuple2<Sheet, Point> resolved = ssheet.resolve(endCellAttr.getValue());
328
                final Tuple2<Sheet, Point> resolved = ssheet.resolve(endCellAttr.getValue());
329
                final Sheet endCellSheet = resolved.get0();
329
                final Sheet endCellSheet = resolved.get0();
330
                if (endCellSheet != this)
330
                if (endCellSheet != this)
331
                    throw new UnsupportedOperationException("End sheet is not this : " + endCellSheet);
331
                    throw new UnsupportedOperationException("End sheet is not this : " + endCellSheet);
332
                final Point endCellPoint = resolved.get1();
332
                final Point endCellPoint = resolved.get1();
333
                // if the end point is before the copied rows, nothing to do
333
                // if the end point is before the copied rows, nothing to do
334
                if (endCellPoint.y >= start) {
334
                if (endCellPoint.y >= start) {
335
                    final Element endCellParentElem = endCellAttr.getParent();
335
                    final Element endCellParentElem = endCellAttr.getParent();
336
 
336
 
337
                    // find row index of the shape
337
                    // find row index of the shape
338
                    final Element rowElem = JDOMUtils.getAncestor(endCellParentElem, "table-row", getTABLE());
338
                    final Element rowElem = JDOMUtils.getAncestor(endCellParentElem, "table-row", getTABLE());
339
                    if (rowElem == null)
339
                    if (rowElem == null)
340
                        throw new IllegalStateException("Not in a row : " + JDOMUtils.output(endCellParentElem));
340
                        throw new IllegalStateException("Not in a row : " + JDOMUtils.output(endCellParentElem));
341
                    int startRowIndex = -1;
341
                    int startRowIndex = -1;
342
                    final int rowCount = getRowCount();
342
                    final int rowCount = getRowCount();
343
                    for (int i = 0; i < rowCount; i++) {
343
                    for (int i = 0; i < rowCount; i++) {
344
                        if (getRow(i).getElement() == rowElem) {
344
                        if (getRow(i).getElement() == rowElem) {
345
                            startRowIndex = i;
345
                            startRowIndex = i;
346
                            break;
346
                            break;
347
                        }
347
                        }
348
                    }
348
                    }
349
                    if (startRowIndex < 0)
349
                    if (startRowIndex < 0)
350
                        throw new IllegalStateException("Row not found for " + JDOMUtils.output(endCellParentElem));
350
                        throw new IllegalStateException("Row not found for " + JDOMUtils.output(endCellParentElem));
351
                    final int newEndY;
351
                    final int newEndY;
352
                    if (startRowIndex >= start + (copies + 1) * count) {
352
                    if (startRowIndex >= start + (copies + 1) * count) {
353
                        // if the shape doesn't span over the copied rows, only need to offset
353
                        // if the shape doesn't span over the copied rows, only need to offset
354
                        // end-cell-address
354
                        // end-cell-address
355
                        newEndY = endCellPoint.y + copies * count;
355
                        newEndY = endCellPoint.y + copies * count;
356
                    } else if (startRowIndex >= start + count && endCellPoint.y < start + count) {
356
                    } else if (startRowIndex >= start + count && endCellPoint.y < start + count) {
357
                        // if the shape was copied and its end cell too, translate it
357
                        // if the shape was copied and its end cell too, translate it
358
                        // find in which copy the shape is in, ATTN the truncation is important
358
                        // find in which copy the shape is in, ATTN the truncation is important
359
                        // since the shape might not be in the first copied row
359
                        // since the shape might not be in the first copied row
360
                        final int nth = (startRowIndex - start) / count;
360
                        final int nth = (startRowIndex - start) / count;
361
                        newEndY = endCellPoint.y + nth * count;
361
                        newEndY = endCellPoint.y + nth * count;
362
                    } else {
362
                    } else {
363
                        // we must use height to compute new values for end-cell-address and end-y
363
                        // we must use height to compute new values for end-cell-address and end-y
364
 
364
 
365
                        // find the height of the shape
365
                        // find the height of the shape
366
                        final Length[] coordinates = getODDocument().getFormatVersion().getXML().getCoordinates(endCellParentElem, false, true);
366
                        final Length[] coordinates = getODDocument().getFormatVersion().getXML().getCoordinates(endCellParentElem, false, true);
367
                        if (coordinates == null)
367
                        if (coordinates == null)
368
                            throw new IllegalStateException("Couldn't find the height of the shape : " + JDOMUtils.output(endCellParentElem));
368
                            throw new IllegalStateException("Couldn't find the height of the shape : " + JDOMUtils.output(endCellParentElem));
369
                        final Length endYFromAnchor = coordinates[3];
369
                        final Length endYFromAnchor = coordinates[3];
370
                        assert endYFromAnchor != null : "getCoordinates() should never return null BigDecimal (unless requested by horizontal/vertical)";
370
                        assert endYFromAnchor != null : "getCoordinates() should never return null BigDecimal (unless requested by horizontal/vertical)";
371
                        // find the end row
371
                        // find the end row
372
                        int rowIndex = startRowIndex;
372
                        int rowIndex = startRowIndex;
373
                        Length cellEndYFromAnchor = getRow(rowIndex).getStyle().getTableRowProperties().getHeight();
373
                        Length cellEndYFromAnchor = getRow(rowIndex).getStyle().getTableRowProperties().getHeight();
374
                        while (endYFromAnchor.compareTo(cellEndYFromAnchor) > 0) {
374
                        while (endYFromAnchor.compareTo(cellEndYFromAnchor) > 0) {
375
                            rowIndex++;
375
                            rowIndex++;
376
                            cellEndYFromAnchor = cellEndYFromAnchor.add(getRow(rowIndex).getStyle().getTableRowProperties().getHeight());
376
                            cellEndYFromAnchor = cellEndYFromAnchor.add(getRow(rowIndex).getStyle().getTableRowProperties().getHeight());
377
                        }
377
                        }
378
                        // find the end-y
378
                        // find the end-y
379
                        final Length cellStartYFromAnchor = cellEndYFromAnchor.subtract(getRow(rowIndex).getStyle().getTableRowProperties().getHeight());
379
                        final Length cellStartYFromAnchor = cellEndYFromAnchor.subtract(getRow(rowIndex).getStyle().getTableRowProperties().getHeight());
380
                        final Length endY = endYFromAnchor.subtract(cellStartYFromAnchor);
380
                        final Length endY = endYFromAnchor.subtract(cellStartYFromAnchor);
381
                        assert endY.signum() >= 0;
381
                        assert endY.signum() >= 0;
382
 
382
 
383
                        newEndY = rowIndex;
383
                        newEndY = rowIndex;
384
                        endCellParentElem.setAttribute("end-y", endY.format(), getTABLE());
384
                        endCellParentElem.setAttribute("end-y", endY.format(), getTABLE());
385
                    }
385
                    }
386
                    endCellAttr.setValue(SpreadSheet.formatSheetName(endCellSheet.getName()) + "." + Table.getAddress(new Point(endCellPoint.x, newEndY)));
386
                    endCellAttr.setValue(SpreadSheet.formatSheetName(endCellSheet.getName()) + "." + Table.getAddress(new Point(endCellPoint.x, newEndY)));
387
                }
387
                }
388
            }
388
            }
389
        }
389
        }
390
    }
390
    }
391
 
391
 
392
    private synchronized void addRow(Element child, StyleDesc<RowStyle> styleDesc, StyleDesc<CellStyle> cellStyleDesc) {
392
    private synchronized void addRow(Element child, StyleDesc<RowStyle> styleDesc, StyleDesc<CellStyle> cellStyleDesc) {
393
        final Row<D> row = new Row<D>(this, child, this.rows.size(), styleDesc, cellStyleDesc);
393
        final Row<D> row = new Row<D>(this, child, this.rows.size(), styleDesc, cellStyleDesc);
394
        final int toRepeat = row.getRepeated();
394
        final int toRepeat = row.getRepeated();
395
        for (int i = 0; i < toRepeat; i++) {
395
        for (int i = 0; i < toRepeat; i++) {
396
            this.rows.add(row);
396
            this.rows.add(row);
397
        }
397
        }
398
    }
398
    }
399
 
399
 
400
    public final Point resolveHint(String ref) {
400
    public final Point resolveHint(String ref) {
401
        final Point res = resolve(ref);
401
        final Point res = resolve(ref);
402
        if (res != null) {
402
        if (res != null) {
403
            return res;
403
            return res;
404
        } else
404
        } else
405
            throw new IllegalArgumentException(ref + " is not a cell ref, if it's a named range, you must use it on a SpreadSheet.");
405
            throw new IllegalArgumentException(ref + " is not a cell ref, if it's a named range, you must use it on a SpreadSheet.");
406
    }
406
    }
407
 
407
 
408
    // *** set cell
408
    // *** set cell
409
 
409
 
410
    public final boolean isCellValid(int x, int y) {
410
    public final boolean isCellValid(int x, int y) {
411
        if (x > this.getColumnCount())
411
        if (x > this.getColumnCount())
412
            return false;
412
            return false;
413
        else if (y > this.getRowCount())
413
        else if (y > this.getRowCount())
414
            return false;
414
            return false;
415
        else
415
        else
416
            return this.getImmutableCellAt(x, y).isValid();
416
            return this.getImmutableCellAt(x, y).isValid();
417
    }
417
    }
418
 
418
 
419
    /**
419
    /**
420
     * Return a modifiable cell at the passed coordinates. This is slower than
420
     * Return a modifiable cell at the passed coordinates. This is slower than
421
     * {@link #getImmutableCellAt(int, int)} since this method may modify the underlying XML (e.g.
421
     * {@link #getImmutableCellAt(int, int)} since this method may modify the underlying XML (e.g.
422
     * break up repeated cells to allow for modification of only the returned cell).
422
     * break up repeated cells to allow for modification of only the returned cell).
423
     * 
423
     * 
424
     * @param x the column.
424
     * @param x the column.
425
     * @param y the row.
425
     * @param y the row.
426
     * @return the cell.
426
     * @return the cell.
427
     * @see #getImmutableCellAt(int, int)
427
     * @see #getImmutableCellAt(int, int)
428
     */
428
     */
429
    public final MutableCell<D> getCellAt(int x, int y) {
429
    public final MutableCell<D> getCellAt(int x, int y) {
-
 
430
        final Row<D> r = this.getMutableRow(y);
-
 
431
        try {
430
        return this.getMutableRow(y).getMutableCellAt(x);
432
            return r.getMutableCellAt(x);
-
 
433
        } catch (Exception e) {
-
 
434
            throw new IllegalArgumentException("Couldn't get mutable cell at " + getAddress(x, y), e);
-
 
435
        }
431
    }
436
    }
432
 
437
 
433
    public final MutableCell<D> getCellAt(String ref) {
438
    public final MutableCell<D> getCellAt(String ref) {
434
        return this.getCellAt(resolveHint(ref));
439
        return this.getCellAt(resolveHint(ref));
435
    }
440
    }
436
 
441
 
437
    final MutableCell<D> getCellAt(Point p) {
442
    final MutableCell<D> getCellAt(Point p) {
438
        return this.getCellAt(p.x, p.y);
443
        return this.getCellAt(p.x, p.y);
439
    }
444
    }
440
 
445
 
441
    /**
446
    /**
442
     * Sets the value at the specified coordinates.
447
     * Sets the value at the specified coordinates.
443
     * 
448
     * 
444
     * @param val the new value, <code>null</code> will be treated as "".
449
     * @param val the new value, <code>null</code> will be treated as "".
445
     * @param x the column.
450
     * @param x the column.
446
     * @param y the row.
451
     * @param y the row.
447
     */
452
     */
448
    public final void setValueAt(Object val, int x, int y) {
453
    public final void setValueAt(Object val, int x, int y) {
449
        if (val == null)
454
        if (val == null)
450
            val = "";
455
            val = "";
451
        // ne pas casser les repeated pour rien
456
        // ne pas casser les repeated pour rien
452
        if (!val.equals(this.getValueAt(x, y)))
457
        if (!val.equals(this.getValueAt(x, y)))
453
            this.getCellAt(x, y).setValue(val);
458
            this.getCellAt(x, y).setValue(val);
454
    }
459
    }
455
 
460
 
456
    // *** get cell
461
    // *** get cell
457
 
462
 
458
    /**
463
    /**
459
     * Return a non modifiable cell at the passed coordinates. This is faster than
464
     * Return a non modifiable cell at the passed coordinates. This is faster than
460
     * {@link #getCellAt(int, int)} since this method never modifies the underlying XML.
465
     * {@link #getCellAt(int, int)} since this method never modifies the underlying XML.
461
     * 
466
     * 
462
     * @param x the column.
467
     * @param x the column.
463
     * @param y the row.
468
     * @param y the row.
464
     * @return the cell.
469
     * @return the cell.
465
     * @see #getCellAt(int, int)
470
     * @see #getCellAt(int, int)
466
     */
471
     */
467
    public final Cell<D> getImmutableCellAt(int x, int y) {
472
    public final Cell<D> getImmutableCellAt(int x, int y) {
468
        return this.getRow(y).getCellAt(x);
473
        return this.getRow(y).getCellAt(x);
469
    }
474
    }
470
 
475
 
471
    public final Cell<D> getImmutableCellAt(String ref) {
476
    public final Cell<D> getImmutableCellAt(String ref) {
472
        final Point p = resolveHint(ref);
477
        final Point p = resolveHint(ref);
473
        return this.getImmutableCellAt(p.x, p.y);
478
        return this.getImmutableCellAt(p.x, p.y);
474
    }
479
    }
475
 
480
 
476
    /**
481
    /**
477
     * Return the origin of a merged cell.
482
     * Return the origin of a merged cell.
478
     * 
483
     * 
479
     * @param x the column.
484
     * @param x the column.
480
     * @param y the row.
485
     * @param y the row.
481
     * @return the point of origin, <code>null</code> if there's no merged cell at the passed
486
     * @return the point of origin, <code>null</code> if there's no merged cell at the passed
482
     *         coordinates.
487
     *         coordinates.
483
     */
488
     */
484
    public final Point getCoverOrigin(final int x, final int y) {
489
    public final Point getCoverOrigin(final int x, final int y) {
485
        // can't return a Cell, since it has no x
490
        // can't return a Cell, since it has no x
486
        // don't return a MutableCell since it is costly
491
        // don't return a MutableCell since it is costly
487
 
492
 
488
        final Cell<D> c = this.getImmutableCellAt(x, y);
493
        final Cell<D> c = this.getImmutableCellAt(x, y);
489
        if (c.coversOtherCells()) {
494
        if (c.coversOtherCells()) {
490
            return new Point(x, y);
495
            return new Point(x, y);
491
        } else if (!c.isCovered()) {
496
        } else if (!c.isCovered()) {
492
            return null;
497
            return null;
493
        } else {
498
        } else {
494
            final Row<D> row = this.getRow(y);
499
            final Row<D> row = this.getRow(y);
495
            Cell<D> currentCell = c;
500
            Cell<D> currentCell = c;
496
            int currentX = x;
501
            int currentX = x;
497
            while (currentX > 0 && currentCell.isCovered()) {
502
            while (currentX > 0 && currentCell.isCovered()) {
498
                currentX--;
503
                currentX--;
499
                currentCell = row.getCellAt(currentX);
504
                currentCell = row.getCellAt(currentX);
500
            }
505
            }
501
            if (currentCell.coversOtherCells())
506
            if (currentCell.coversOtherCells())
502
                return new Point(currentX, y);
507
                return new Point(currentX, y);
503
 
508
 
504
            if (!currentCell.isCovered()) {
509
            if (!currentCell.isCovered()) {
505
                currentX++;
510
                currentX++;
506
                currentCell = row.getCellAt(currentX);
511
                currentCell = row.getCellAt(currentX);
507
            }
512
            }
508
            assert currentCell.isCovered();
513
            assert currentCell.isCovered();
509
 
514
 
510
            int currentY = y;
515
            int currentY = y;
511
            while (!currentCell.coversOtherCells()) {
516
            while (!currentCell.coversOtherCells()) {
512
                currentY--;
517
                currentY--;
513
                currentCell = this.getImmutableCellAt(currentX, currentY);
518
                currentCell = this.getImmutableCellAt(currentX, currentY);
514
            }
519
            }
515
            return new Point(currentX, currentY);
520
            return new Point(currentX, currentY);
516
        }
521
        }
517
    }
522
    }
518
 
523
 
519
    /**
524
    /**
520
     * The value of the cell at the passed coordinates.
525
     * The value of the cell at the passed coordinates.
521
     * 
526
     * 
522
     * @param row row index from <code>0</code> to <code>{@link #getRowCount() row count}-1</code>
527
     * @param row row index from <code>0</code> to <code>{@link #getRowCount() row count}-1</code>
523
     * @param column column index from <code>0</code> to
528
     * @param column column index from <code>0</code> to
524
     *        <code>{@link #getColumnCount() column count}-1</code>
529
     *        <code>{@link #getColumnCount() column count}-1</code>
525
     * @return the value at the passed coordinates.
530
     * @return the value at the passed coordinates.
526
     * @see #getValueAt(String)
531
     * @see #getValueAt(String)
527
     * @see Cell#getValue()
532
     * @see Cell#getValue()
528
     */
533
     */
529
    public final Object getValueAt(int column, int row) {
534
    public final Object getValueAt(int column, int row) {
530
        return this.getImmutableCellAt(column, row).getValue();
535
        return this.getImmutableCellAt(column, row).getValue();
531
    }
536
    }
532
 
537
 
533
    /**
538
    /**
534
     * Find the style name for the specified cell.
539
     * Find the style name for the specified cell.
535
     * 
540
     * 
536
     * @param column column index.
541
     * @param column column index.
537
     * @param row row index.
542
     * @param row row index.
538
     * @return the style name, can be <code>null</code>.
543
     * @return the style name, can be <code>null</code>.
539
     */
544
     */
540
    public final String getStyleNameAt(int column, int row) {
545
    public final String getStyleNameAt(int column, int row) {
541
        // first the cell
546
        // first the cell
542
        String cellStyle = this.getImmutableCellAt(column, row).getStyleAttr();
547
        String cellStyle = this.getImmutableCellAt(column, row).getStyleAttr();
543
        if (cellStyle != null)
548
        if (cellStyle != null)
544
            return cellStyle;
549
            return cellStyle;
545
        // then the row (as specified in §2 of section 8.1)
550
        // then the row (as specified in §2 of section 8.1)
546
        // in reality LO never generates table-row/@default-cell-style-name and its behavior is
551
        // in reality LO never generates table-row/@default-cell-style-name and its behavior is
547
        // really unpredictable if it opens files with it
552
        // really unpredictable if it opens files with it
548
        if (Style.isStandardStyleResolution()) {
553
        if (Style.isStandardStyleResolution()) {
549
            cellStyle = this.getRow(row).getElement().getAttributeValue("default-cell-style-name", getTABLE());
554
            cellStyle = this.getRow(row).getElement().getAttributeValue("default-cell-style-name", getTABLE());
550
            if (cellStyle != null)
555
            if (cellStyle != null)
551
                return cellStyle;
556
                return cellStyle;
552
        }
557
        }
553
        // and finally the column
558
        // and finally the column
554
        return this.getColumn(column).getElement().getAttributeValue("default-cell-style-name", getTABLE());
559
        return this.getColumn(column).getElement().getAttributeValue("default-cell-style-name", getTABLE());
555
    }
560
    }
556
 
561
 
557
    public final CellStyle getStyleAt(int column, int row) {
562
    public final CellStyle getStyleAt(int column, int row) {
558
        return getCellStyleDesc().findStyleForNode(this.getImmutableCellAt(column, row), this.getStyleNameAt(column, row));
563
        return getCellStyleDesc().findStyleForNode(this.getImmutableCellAt(column, row), this.getStyleNameAt(column, row));
559
    }
564
    }
560
 
565
 
561
    public final StyleTableCellProperties getTableCellPropertiesAt(int column, int row) {
566
    public final StyleTableCellProperties getTableCellPropertiesAt(int column, int row) {
562
        final CellStyle styleAt = this.getStyleAt(column, row);
567
        final CellStyle styleAt = this.getStyleAt(column, row);
563
        return styleAt == null ? null : styleAt.getTableCellProperties(this.getImmutableCellAt(column, row));
568
        return styleAt == null ? null : styleAt.getTableCellProperties(this.getImmutableCellAt(column, row));
564
    }
569
    }
565
 
570
 
566
    protected StyleStyleDesc<CellStyle> getCellStyleDesc() {
571
    protected StyleStyleDesc<CellStyle> getCellStyleDesc() {
567
        return Style.getStyleStyleDesc(CellStyle.class, getODDocument().getVersion());
572
        return Style.getStyleStyleDesc(CellStyle.class, getODDocument().getVersion());
568
    }
573
    }
569
 
574
 
570
    public final CellStyle getDefaultCellStyle() {
575
    public final CellStyle getDefaultCellStyle() {
571
        return getCellStyleDesc().findDefaultStyle(this.getODDocument().getPackage());
576
        return getCellStyleDesc().findDefaultStyle(this.getODDocument().getPackage());
572
    }
577
    }
573
 
578
 
574
    /**
579
    /**
575
     * Return the coordinates of cells using the passed style.
580
     * Return the coordinates of cells using the passed style.
576
     * 
581
     * 
577
     * @param cellStyleName a style name.
582
     * @param cellStyleName a style name.
578
     * @return the cells using <code>cellStyleName</code>.
583
     * @return the cells using <code>cellStyleName</code>.
579
     */
584
     */
580
    public final List<Tuple2<Integer, Integer>> getStyleReferences(final String cellStyleName) {
585
    public final List<Tuple2<Integer, Integer>> getStyleReferences(final String cellStyleName) {
581
        final List<Tuple2<Integer, Integer>> res = new ArrayList<Tuple2<Integer, Integer>>();
586
        final List<Tuple2<Integer, Integer>> res = new ArrayList<Tuple2<Integer, Integer>>();
582
        final Set<Integer> cols = new HashSet<Integer>();
587
        final Set<Integer> cols = new HashSet<Integer>();
583
        final int columnCount = getColumnCount();
588
        final int columnCount = getColumnCount();
584
        for (int i = 0; i < columnCount; i++) {
589
        for (int i = 0; i < columnCount; i++) {
585
            if (cellStyleName.equals(this.getColumn(i).getElement().getAttributeValue("default-cell-style-name", getTABLE())))
590
            if (cellStyleName.equals(this.getColumn(i).getElement().getAttributeValue("default-cell-style-name", getTABLE())))
586
                cols.add(i);
591
                cols.add(i);
587
        }
592
        }
588
        final int rowCount = getRowCount();
593
        final int rowCount = getRowCount();
589
        for (int y = 0; y < rowCount; y++) {
594
        for (int y = 0; y < rowCount; y++) {
590
            final Row<D> row = this.getRow(y);
595
            final Row<D> row = this.getRow(y);
591
            final String rowStyle = row.getElement().getAttributeValue("default-cell-style-name", getTABLE());
596
            final String rowStyle = row.getElement().getAttributeValue("default-cell-style-name", getTABLE());
592
            for (int x = 0; x < columnCount; x++) {
597
            for (int x = 0; x < columnCount; x++) {
593
                final String cellStyle = row.getCellAt(x).getStyleAttr();
598
                final String cellStyle = row.getCellAt(x).getStyleAttr();
594
                final boolean match;
599
                final boolean match;
595
                // first the cell
600
                // first the cell
596
                if (cellStyle != null)
601
                if (cellStyle != null)
597
                    match = cellStyleName.equals(cellStyle);
602
                    match = cellStyleName.equals(cellStyle);
598
                // then the row (as specified in §2 of section 8.1)
603
                // then the row (as specified in §2 of section 8.1)
599
                else if (rowStyle != null)
604
                else if (rowStyle != null)
600
                    match = cellStyleName.equals(rowStyle);
605
                    match = cellStyleName.equals(rowStyle);
601
                // and finally the column
606
                // and finally the column
602
                else
607
                else
603
                    match = cols.contains(x);
608
                    match = cols.contains(x);
604
 
609
 
605
                if (match)
610
                if (match)
606
                    res.add(Tuple2.create(x, y));
611
                    res.add(Tuple2.create(x, y));
607
            }
612
            }
608
        }
613
        }
609
        return res;
614
        return res;
610
    }
615
    }
611
 
616
 
612
    protected final StyleStyleDesc<ColumnStyle> getColumnStyleDesc() {
617
    protected final StyleStyleDesc<ColumnStyle> getColumnStyleDesc() {
613
        return Style.getStyleStyleDesc(ColumnStyle.class, XMLVersion.getVersion(getElement()));
618
        return Style.getStyleStyleDesc(ColumnStyle.class, XMLVersion.getVersion(getElement()));
614
    }
619
    }
615
 
620
 
616
    protected final StyleStyleDesc<RowStyle> getRowStyleDesc() {
621
    protected final StyleStyleDesc<RowStyle> getRowStyleDesc() {
617
        return Style.getStyleStyleDesc(RowStyle.class, XMLVersion.getVersion(getElement()));
622
        return Style.getStyleStyleDesc(RowStyle.class, XMLVersion.getVersion(getElement()));
618
    }
623
    }
619
 
624
 
620
    /**
625
    /**
621
     * The value of the cell at the passed coordinates.
626
     * The value of the cell at the passed coordinates.
622
     * 
627
     * 
623
     * @param ref a reference, e.g. "A3".
628
     * @param ref a reference, e.g. "A3".
624
     * @return the value at the passed coordinates.
629
     * @return the value at the passed coordinates.
625
     * @see #getValueAt(int, int)
630
     * @see #getValueAt(int, int)
626
     * @see Cell#getValue()
631
     * @see Cell#getValue()
627
     */
632
     */
628
    public final Object getValueAt(String ref) {
633
    public final Object getValueAt(String ref) {
629
        return this.getImmutableCellAt(ref).getValue();
634
        return this.getImmutableCellAt(ref).getValue();
630
    }
635
    }
631
 
636
 
632
    // *** get count
637
    // *** get count
633
 
638
 
634
    // not public since Row represent the physical (XML) row, i.e. it can represent many logical
639
    // not public since Row represent the physical (XML) row, i.e. it can represent many logical
635
    // rows. Ideally two classes should be created (as Cell/MutableCell), but this would only be
640
    // rows. Ideally two classes should be created (as Cell/MutableCell), but this would only be
636
    // useful for changing style (e.g. setHeight()).
641
    // useful for changing style (e.g. setHeight()).
637
 
642
 
638
    final Row<D> getRow(int index) {
643
    final Row<D> getRow(int index) {
639
        return this.rows.get(index);
644
        return this.rows.get(index);
640
    }
645
    }
641
 
646
 
642
    final Row<D> getMutableRow(int y) {
647
    final Row<D> getMutableRow(int y) {
643
        final Row<D> c = this.getRow(y);
648
        final Row<D> c = this.getRow(y);
644
        if (c.getRepeated() > 1) {
649
        if (c.getRepeated() > 1) {
645
            RepeatedBreaker.<D> getRowBreaker().breakRepeated(this, this.rows, y);
650
            RepeatedBreaker.<D> getRowBreaker().breakRepeated(this, this.rows, y);
646
            return this.getRow(y);
651
            return this.getRow(y);
647
        } else {
652
        } else {
648
            return c;
653
            return c;
649
        }
654
        }
650
    }
655
    }
651
 
656
 
652
    // OK to use immutable row since for now RowStyle has no setter (except for raw methods in super
657
    // OK to use immutable row since for now RowStyle has no setter (except for raw methods in super
653
    // classes)
658
    // classes)
654
    public final RowStyle getRowStyle(int index) {
659
    public final RowStyle getRowStyle(int index) {
655
        return this.getRow(index).getStyle();
660
        return this.getRow(index).getStyle();
656
    }
661
    }
657
 
662
 
658
    public final Column<D> getColumn(int i) {
663
    public final Column<D> getColumn(int i) {
659
        return this.cols.get(i);
664
        return this.cols.get(i);
660
    }
665
    }
661
 
666
 
662
    public final ColumnStyle getColumnStyle(int index) {
667
    public final ColumnStyle getColumnStyle(int index) {
663
        return this.getColumn(index).getStyle();
668
        return this.getColumn(index).getStyle();
664
    }
669
    }
665
 
670
 
666
    public final int getRowCount() {
671
    public final int getRowCount() {
667
        return this.rows.size();
672
        return this.rows.size();
668
    }
673
    }
669
 
674
 
670
    public final TableGroup getRowGroup() {
675
    public final TableGroup getRowGroup() {
671
        return this.rowGroup;
676
        return this.rowGroup;
672
    }
677
    }
673
 
678
 
674
    /**
679
    /**
675
     * Return the deepest group at the passed row.
680
     * Return the deepest group at the passed row.
676
     * 
681
     * 
677
     * @param y a row index.
682
     * @param y a row index.
678
     * @return the group at the index, never <code>null</code>.
683
     * @return the group at the index, never <code>null</code>.
679
     */
684
     */
680
    public final TableGroup getRowGroupAt(final int y) {
685
    public final TableGroup getRowGroupAt(final int y) {
681
        return this.getRowGroup().getDescendentOrSelfContaining(y);
686
        return this.getRowGroup().getDescendentOrSelfContaining(y);
682
    }
687
    }
683
 
688
 
684
    public final int getHeaderRowCount() {
689
    public final int getHeaderRowCount() {
685
        return this.getRowGroup().getFollowingHeaderCount();
690
        return this.getRowGroup().getFollowingHeaderCount();
686
    }
691
    }
687
 
692
 
688
    public final int getColumnCount() {
693
    public final int getColumnCount() {
689
        return this.cols.size();
694
        return this.cols.size();
690
    }
695
    }
691
 
696
 
692
    public final TableGroup getColumnGroup() {
697
    public final TableGroup getColumnGroup() {
693
        return this.columnGroup;
698
        return this.columnGroup;
694
    }
699
    }
695
 
700
 
696
    /**
701
    /**
697
     * Return the deepest group at the passed column.
702
     * Return the deepest group at the passed column.
698
     * 
703
     * 
699
     * @param x a column index.
704
     * @param x a column index.
700
     * @return the group at the index, never <code>null</code>.
705
     * @return the group at the index, never <code>null</code>.
701
     */
706
     */
702
    public final TableGroup getColumnGroupAt(final int x) {
707
    public final TableGroup getColumnGroupAt(final int x) {
703
        return this.getColumnGroup().getDescendentOrSelfContaining(x);
708
        return this.getColumnGroup().getDescendentOrSelfContaining(x);
704
    }
709
    }
705
 
710
 
706
    public final int getHeaderColumnCount() {
711
    public final int getHeaderColumnCount() {
707
        return this.getColumnGroup().getFollowingHeaderCount();
712
        return this.getColumnGroup().getFollowingHeaderCount();
708
    }
713
    }
709
 
714
 
710
    // *** set count
715
    // *** set count
711
 
716
 
712
    /**
717
    /**
713
     * Changes the column count without keeping the table width.
718
     * Changes the column count without keeping the table width.
714
     * 
719
     * 
715
     * @param newSize the new column count.
720
     * @param newSize the new column count.
716
     * @see #setColumnCount(int, int, boolean)
721
     * @see #setColumnCount(int, int, boolean)
717
     */
722
     */
718
    public final void setColumnCount(int newSize) {
723
    public final void setColumnCount(int newSize) {
719
        this.setColumnCount(newSize, -1, false);
724
        this.setColumnCount(newSize, -1, false);
720
    }
725
    }
721
 
726
 
722
    /**
727
    /**
723
     * Assure that this sheet has at least <code>newSize</code> columns.
728
     * Assure that this sheet has at least <code>newSize</code> columns.
724
     * 
729
     * 
725
     * @param newSize the minimum column count this table should have.
730
     * @param newSize the minimum column count this table should have.
726
     */
731
     */
727
    public final void ensureColumnCount(int newSize) {
732
    public final void ensureColumnCount(int newSize) {
728
        if (newSize > this.getColumnCount())
733
        if (newSize > this.getColumnCount())
729
            this.setColumnCount(newSize);
734
            this.setColumnCount(newSize);
730
    }
735
    }
731
 
736
 
732
    /**
737
    /**
733
     * Changes the column count. If <code>newSize</code> is less than {@link #getColumnCount()}
738
     * Changes the column count. If <code>newSize</code> is less than {@link #getColumnCount()}
734
     * extra cells will be chopped off. Otherwise empty cells will be created.
739
     * extra cells will be chopped off. Otherwise empty cells will be created.
735
     * 
740
     * 
736
     * @param newSize the new column count.
741
     * @param newSize the new column count.
737
     * @param colIndex the index of the column to be copied, -1 for empty column (and no style).
742
     * @param colIndex the index of the column to be copied, -1 for empty column (and no style).
738
     * @param keepTableWidth <code>true</code> if the table should be same width after the column
743
     * @param keepTableWidth <code>true</code> if the table should be same width after the column
739
     *        change.
744
     *        change.
740
     */
745
     */
741
    public final void setColumnCount(int newSize, int colIndex, final boolean keepTableWidth) {
746
    public final void setColumnCount(int newSize, int colIndex, final boolean keepTableWidth) {
742
        this.setColumnCount(newSize, colIndex, null, keepTableWidth);
747
        this.setColumnCount(newSize, colIndex, null, keepTableWidth);
743
    }
748
    }
744
 
749
 
745
    /**
750
    /**
746
     * Changes the column count. If <code>newSize</code> is less than {@link #getColumnCount()}
751
     * Changes the column count. If <code>newSize</code> is less than {@link #getColumnCount()}
747
     * extra cells will be chopped off. Otherwise empty cells will be created.
752
     * extra cells will be chopped off. Otherwise empty cells will be created.
748
     * 
753
     * 
749
     * @param newSize the new column count.
754
     * @param newSize the new column count.
750
     * @param colStyle the style of the new columns, <code>null</code> for no style (throws
755
     * @param colStyle the style of the new columns, <code>null</code> for no style (throws
751
     *        exception if the table has a non-null width).
756
     *        exception if the table has a non-null width).
752
     * @param keepTableWidth <code>true</code> if the table should be same width after the column
757
     * @param keepTableWidth <code>true</code> if the table should be same width after the column
753
     *        change.
758
     *        change.
754
     * @see #createColumnStyle(Number, LengthUnit)
759
     * @see #createColumnStyle(Number, LengthUnit)
755
     */
760
     */
756
    public final void setColumnCount(int newSize, ColumnStyle colStyle, final boolean keepTableWidth) {
761
    public final void setColumnCount(int newSize, ColumnStyle colStyle, final boolean keepTableWidth) {
757
        this.setColumnCount(newSize, -1, colStyle, keepTableWidth);
762
        this.setColumnCount(newSize, -1, colStyle, keepTableWidth);
758
    }
763
    }
759
 
764
 
760
    private final void setColumnCount(int newSize, int colIndex, ColumnStyle colStyle, final boolean keepTableWidth) {
765
    private final void setColumnCount(int newSize, int colIndex, ColumnStyle colStyle, final boolean keepTableWidth) {
761
        final int toGrow = newSize - this.getColumnCount();
766
        final int toGrow = newSize - this.getColumnCount();
762
        if (toGrow < 0) {
767
        if (toGrow < 0) {
763
            this.removeColumn(newSize, this.getColumnCount(), keepTableWidth);
768
            this.removeColumn(newSize, this.getColumnCount(), keepTableWidth);
764
        } else if (toGrow > 0) {
769
        } else if (toGrow > 0) {
765
            // the list of columns cannot be mixed with other elements
770
            // the list of columns cannot be mixed with other elements
766
            // so just keep adding after the last one
771
            // so just keep adding after the last one
767
            final int indexOfLastCol;
772
            final int indexOfLastCol;
768
            if (this.getColumnCount() == 0)
773
            if (this.getColumnCount() == 0)
769
                // from section 8.1.1 the only possible elements after cols are rows
774
                // from section 8.1.1 the only possible elements after cols are rows
770
                // but there can't be rows w/o columns, so just add to the end
775
                // but there can't be rows w/o columns, so just add to the end
771
                indexOfLastCol = this.getElement().getContentSize() - 1;
776
                indexOfLastCol = this.getElement().getContentSize() - 1;
772
            else
777
            else
773
                indexOfLastCol = this.getElement().getContent().indexOf(this.getColumn(this.getColumnCount() - 1).getElement());
778
                indexOfLastCol = this.getElement().getContent().indexOf(this.getColumn(this.getColumnCount() - 1).getElement());
774
 
779
 
775
            final Element elemToClone;
780
            final Element elemToClone;
776
            if (colIndex < 0) {
781
            if (colIndex < 0) {
777
                elemToClone = Column.createEmpty(getODDocument().getVersion(), colStyle);
782
                elemToClone = Column.createEmpty(getODDocument().getVersion(), colStyle);
778
            } else {
783
            } else {
779
                elemToClone = getColumn(colIndex).getElement();
784
                elemToClone = getColumn(colIndex).getElement();
780
            }
785
            }
781
            final StyleStyleDesc<ColumnStyle> columnStyleDesc = getColumnStyleDesc();
786
            final StyleStyleDesc<ColumnStyle> columnStyleDesc = getColumnStyleDesc();
782
            for (int i = 0; i < toGrow; i++) {
787
            for (int i = 0; i < toGrow; i++) {
783
                final Element newElem = (Element) elemToClone.clone();
788
                final Element newElem = (Element) elemToClone.clone();
784
                this.getElement().addContent(indexOfLastCol + 1 + i, newElem);
789
                this.getElement().addContent(indexOfLastCol + 1 + i, newElem);
785
                this.cols.add(new Column<D>(this, newElem, columnStyleDesc));
790
                this.cols.add(new Column<D>(this, newElem, columnStyleDesc));
786
            }
791
            }
787
            // now update widths
792
            // now update widths
788
            updateWidth(keepTableWidth);
793
            updateWidth(keepTableWidth);
789
 
794
 
790
            // add needed cells
795
            // add needed cells
791
            final StyleStyleDesc<CellStyle> cellStyleDesc = this.getCellStyleDesc();
796
            final StyleStyleDesc<CellStyle> cellStyleDesc = this.getCellStyleDesc();
792
            final int rowCount = this.getRowCount();
797
            final int rowCount = this.getRowCount();
793
            for (int i = 0; i < rowCount;) {
798
            for (int i = 0; i < rowCount;) {
794
                final Row<D> r = this.getRow(i);
799
                final Row<D> r = this.getRow(i);
795
                r.columnCountChanged(cellStyleDesc);
800
                r.columnCountChanged(cellStyleDesc);
796
                i += r.getRepeated();
801
                i += r.getRepeated();
797
            }
802
            }
798
        }
803
        }
799
    }
804
    }
800
 
805
 
801
    public final void removeColumn(int colIndex, final boolean keepTableWidth) {
806
    public final void removeColumn(int colIndex, final boolean keepTableWidth) {
802
        this.removeColumn(colIndex, colIndex + 1, keepTableWidth);
807
        this.removeColumn(colIndex, colIndex + 1, keepTableWidth);
803
    }
808
    }
804
 
809
 
805
    /**
810
    /**
806
     * Remove columns from this. As with OpenOffice, no cell must be covered in the column to
811
     * Remove columns from this. As with OpenOffice, no cell must be covered in the column to
807
     * remove. ATTN <code>keepTableWidth</code> only works for tables in text document that are not
812
     * remove. ATTN <code>keepTableWidth</code> only works for tables in text document that are not
808
     * aligned automatically (ie fill the entire page). ATTN spreadsheet applications may hide from
813
     * aligned automatically (ie fill the entire page). ATTN spreadsheet applications may hide from
809
     * you the real width of sheets, eg display only columns A to AJ when in reality there's
814
     * you the real width of sheets, eg display only columns A to AJ when in reality there's
810
     * hundreds of blank columns beyond. Thus if you pass <code>true</code> to
815
     * hundreds of blank columns beyond. Thus if you pass <code>true</code> to
811
     * <code>keepTableWidth</code> you'll end up with huge widths.
816
     * <code>keepTableWidth</code> you'll end up with huge widths.
812
     * 
817
     * 
813
     * @param firstIndex the first column to remove.
818
     * @param firstIndex the first column to remove.
814
     * @param lastIndex the last column to remove, exclusive.
819
     * @param lastIndex the last column to remove, exclusive.
815
     * @param keepTableWidth <code>true</code> if the table should be same width after the column
820
     * @param keepTableWidth <code>true</code> if the table should be same width after the column
816
     *        change.
821
     *        change.
817
     */
822
     */
818
    public final void removeColumn(int firstIndex, int lastIndex, final boolean keepTableWidth) {
823
    public final void removeColumn(int firstIndex, int lastIndex, final boolean keepTableWidth) {
819
        // first check that removeCells() will succeed, so that we avoid an incoherent XML state
824
        // first check that removeCells() will succeed, so that we avoid an incoherent XML state
820
        final int rowCount = this.getRowCount();
825
        final int rowCount = this.getRowCount();
821
        for (int i = 0; i < rowCount;) {
826
        for (int i = 0; i < rowCount;) {
822
            final Row<D> r = this.getRow(i);
827
            final Row<D> r = this.getRow(i);
823
            r.checkRemove(firstIndex, lastIndex);
828
            r.checkRemove(firstIndex, lastIndex);
824
            i += r.getRepeated();
829
            i += r.getRepeated();
825
        }
830
        }
826
        // rm column element
831
        // rm column element
827
        remove(Axis.COLUMN, firstIndex, lastIndex - 1);
832
        remove(Axis.COLUMN, firstIndex, lastIndex - 1);
828
        // update widths
833
        // update widths
829
        updateWidth(keepTableWidth);
834
        updateWidth(keepTableWidth);
830
        // rm cells
835
        // rm cells
831
        for (int i = 0; i < rowCount;) {
836
        for (int i = 0; i < rowCount;) {
832
            final Row<D> r = this.getRow(i);
837
            final Row<D> r = this.getRow(i);
833
            r.removeCells(firstIndex, lastIndex);
838
            r.removeCells(firstIndex, lastIndex);
834
            i += r.getRepeated();
839
            i += r.getRepeated();
835
        }
840
        }
836
    }
841
    }
837
 
842
 
838
    void updateWidth(final boolean keepTableWidth) {
843
    void updateWidth(final boolean keepTableWidth) {
839
        // tables widths must have a lower precision than columns since they can't be the exact sum
844
        // tables widths must have a lower precision than columns since they can't be the exact sum
840
        // of them.
845
        // of them.
841
        final Length currentWidth = getWidth().roundDecimalAmount();
846
        final Length currentWidth = getWidth().roundDecimalAmount();
842
        Length newWidth = Length.ZERO;
847
        Length newWidth = Length.ZERO;
843
        Column<?> nullWidthCol = null;
848
        Column<?> nullWidthCol = null;
844
        // columns are flattened in ctor: no repeated
849
        // columns are flattened in ctor: no repeated
845
        for (final Column<?> col : this.cols) {
850
        for (final Column<?> col : this.cols) {
846
            final Length colWidth = col.getWidth();
851
            final Length colWidth = col.getWidth();
847
            if (colWidth.isDefined()) {
852
            if (colWidth.isDefined()) {
848
                assert colWidth.signum() >= 0;
853
                assert colWidth.signum() >= 0;
849
                newWidth = newWidth.add(colWidth);
854
                newWidth = newWidth.add(colWidth);
850
            } else {
855
            } else {
851
                // we cannot compute the newWidth
856
                // we cannot compute the newWidth
852
                newWidth = Length.getNone();
857
                newWidth = Length.getNone();
853
                nullWidthCol = col;
858
                nullWidthCol = col;
854
                break;
859
                break;
855
            }
860
            }
856
        }
861
        }
857
        // tables widths must have a lower precision than columns since they can't be the exact sum
862
        // tables widths must have a lower precision than columns since they can't be the exact sum
858
        // of them.
863
        // of them.
859
        newWidth = newWidth.roundDecimalAmount();
864
        newWidth = newWidth.roundDecimalAmount();
860
        // remove all rel-column-width, simpler and Spreadsheet doesn't use them
865
        // remove all rel-column-width, simpler and Spreadsheet doesn't use them
861
        // SpreadSheets have no table width
866
        // SpreadSheets have no table width
862
        if (keepTableWidth && currentWidth.isDefined()) {
867
        if (keepTableWidth && currentWidth.isDefined()) {
863
            if (nullWidthCol != null)
868
            if (nullWidthCol != null)
864
                throw new IllegalStateException("Cannot keep width since a column has no width : " + nullWidthCol);
869
                throw new IllegalStateException("Cannot keep width since a column has no width : " + nullWidthCol);
865
            // compute column-width from table width
870
            // compute column-width from table width
866
            final Number ratio = currentWidth.divide(newWidth);
871
            final Number ratio = currentWidth.divide(newWidth);
867
            // once per style not once per col, otherwise if multiple columns with same styles they
872
            // once per style not once per col, otherwise if multiple columns with same styles they
868
            // all will be affected multiple times
873
            // all will be affected multiple times
869
            final Set<ColumnStyle> colStyles = new HashSet<ColumnStyle>();
874
            final Set<ColumnStyle> colStyles = new HashSet<ColumnStyle>();
870
            for (final Column<?> col : this.cols) {
875
            for (final Column<?> col : this.cols) {
871
                colStyles.add(col.getStyle());
876
                colStyles.add(col.getStyle());
872
            }
877
            }
873
            for (final ColumnStyle colStyle : colStyles) {
878
            for (final ColumnStyle colStyle : colStyles) {
874
                colStyle.setWidth(colStyle.getWidth().multiply(ratio));
879
                colStyle.setWidth(colStyle.getWidth().multiply(ratio));
875
            }
880
            }
876
        } else {
881
        } else {
877
            // compute table width from column-width
882
            // compute table width from column-width
878
            final TableStyle style = this.getStyle();
883
            final TableStyle style = this.getStyle();
879
            if (style != null) {
884
            if (style != null) {
880
                if (nullWidthCol == null)
885
                if (nullWidthCol == null)
881
                    style.setWidth(newWidth);
886
                    style.setWidth(newWidth);
882
                else if (currentWidth.isDefined())
887
                else if (currentWidth.isDefined())
883
                    throw new IllegalStateException("Cannot update table width since a column has no width : " + nullWidthCol);
888
                    throw new IllegalStateException("Cannot update table width since a column has no width : " + nullWidthCol);
884
                // else currentWidth undefined, no inconsistency, nothing to update
889
                // else currentWidth undefined, no inconsistency, nothing to update
885
            }
890
            }
886
            // either there's no table width or it's positive and equal to the sum of its columns
891
            // either there's no table width or it's positive and equal to the sum of its columns
887
            assert style == null || style.getWidth().isNone() || (!newWidth.isNone() && newWidth.compareTo(style.getWidth()) == 0);
892
            assert style == null || style.getWidth().isNone() || (!newWidth.isNone() && newWidth.compareTo(style.getWidth()) == 0);
888
            for (final Column<?> col : this.cols) {
893
            for (final Column<?> col : this.cols) {
889
                final ColumnStyle colStyle = col.getStyle();
894
                final ColumnStyle colStyle = col.getStyle();
890
                // if no style, nothing to remove
895
                // if no style, nothing to remove
891
                if (colStyle != null)
896
                if (colStyle != null)
892
                    colStyle.rmRelWidth();
897
                    colStyle.rmRelWidth();
893
            }
898
            }
894
        }
899
        }
895
    }
900
    }
896
 
901
 
897
    /**
902
    /**
898
     * Table width.
903
     * Table width.
899
     * 
904
     * 
900
     * @return the table width, can be <code>null</code> (table has no style or style has no width,
905
     * @return the table width, can be <code>null</code> (table has no style or style has no width,
901
     *         eg in SpreadSheet).
906
     *         eg in SpreadSheet).
902
     */
907
     */
903
    public final Length getWidth() {
908
    public final Length getWidth() {
904
        return this.getStyle().getWidth();
909
        return this.getStyle().getWidth();
905
    }
910
    }
906
 
911
 
907
    /**
912
    /**
908
     * Create and add an automatic style.
913
     * Create and add an automatic style.
909
     * 
914
     * 
910
     * @param amount the column width.
915
     * @param amount the column width.
911
     * @param unit the unit of <code>amount</code>.
916
     * @param unit the unit of <code>amount</code>.
912
     * @return the newly added style.
917
     * @return the newly added style.
913
     */
918
     */
914
    public final ColumnStyle createColumnStyle(final Number amount, final LengthUnit unit) {
919
    public final ColumnStyle createColumnStyle(final Number amount, final LengthUnit unit) {
915
        return createColumnStyle(amount == null ? Length.getNone() : Length.create(amount, unit));
920
        return createColumnStyle(amount == null ? Length.getNone() : Length.create(amount, unit));
916
    }
921
    }
917
 
922
 
918
    public final ColumnStyle createColumnStyle(final Length l) {
923
    public final ColumnStyle createColumnStyle(final Length l) {
919
        final ColumnStyle colStyle = this.getStyleDesc(ColumnStyle.class).createAutoStyle(this.getODDocument().getPackage());
924
        final ColumnStyle colStyle = this.getStyleDesc(ColumnStyle.class).createAutoStyle(this.getODDocument().getPackage());
920
        colStyle.setWidth(l);
925
        colStyle.setWidth(l);
921
        return colStyle;
926
        return colStyle;
922
    }
927
    }
923
 
928
 
924
    private final void setCount(final Axis col, final int newSize) {
929
    private final void setCount(final Axis col, final int newSize) {
925
        this.remove(col, newSize, -1);
930
        this.remove(col, newSize, -1);
926
    }
931
    }
927
 
932
 
928
    private final void remove(final Axis col, final int fromIndex, final int toIndexIncl) {
933
    private final void remove(final Axis col, final int fromIndex, final int toIndexIncl) {
929
        this.remove(col, fromIndex, toIndexIncl, true);
934
        this.remove(col, fromIndex, toIndexIncl, true);
930
    }
935
    }
931
 
936
 
932
    // both inclusive
937
    // both inclusive
933
    private final void remove(final Axis col, final int fromIndex, final int toIndexIncl, final boolean updateList) {
938
    private final void remove(final Axis col, final int fromIndex, final int toIndexIncl, final boolean updateList) {
934
        assert col == Axis.COLUMN || !updateList || toIndexIncl < 0 : "Row index will be wrong";
939
        assert col == Axis.COLUMN || !updateList || toIndexIncl < 0 : "Row index will be wrong";
935
        final List<? extends TableCalcNode<?, ?>> l = col == Axis.COLUMN ? this.cols : this.rows;
940
        final List<? extends TableCalcNode<?, ?>> l = col == Axis.COLUMN ? this.cols : this.rows;
936
        final int toIndexValid = CollectionUtils.getValidIndex(l, toIndexIncl);
941
        final int toIndexValid = CollectionUtils.getValidIndex(l, toIndexIncl);
937
        final int toRemoveCount = toIndexValid - fromIndex + 1;
942
        final int toRemoveCount = toIndexValid - fromIndex + 1;
938
        int removedCount = 0;
943
        int removedCount = 0;
939
        while (removedCount < toRemoveCount) {
944
        while (removedCount < toRemoveCount) {
940
            // works backwards to keep y OK
945
            // works backwards to keep y OK
941
            final int i = toIndexValid - removedCount;
946
            final int i = toIndexValid - removedCount;
942
            final TableCalcNode<?, ?> removed = l.get(i);
947
            final TableCalcNode<?, ?> removed = l.get(i);
943
            if (removed instanceof Row) {
948
            if (removed instanceof Row) {
944
                final Row<?> r = (Row<?>) removed;
949
                final Row<?> r = (Row<?>) removed;
945
                final int removeFromRepeated = i - Math.max(fromIndex, r.getY()) + 1;
950
                final int removeFromRepeated = i - Math.max(fromIndex, r.getY()) + 1;
946
                // removedCount grows each iteration
951
                // removedCount grows each iteration
947
                assert removeFromRepeated > 0;
952
                assert removeFromRepeated > 0;
948
                final int newRepeated = r.getRepeated() - removeFromRepeated;
953
                final int newRepeated = r.getRepeated() - removeFromRepeated;
949
                if (newRepeated == 0)
954
                if (newRepeated == 0)
950
                    removed.getElement().detach();
955
                    removed.getElement().detach();
951
                else
956
                else
952
                    r.setRepeated(newRepeated);
957
                    r.setRepeated(newRepeated);
953
                removedCount += removeFromRepeated;
958
                removedCount += removeFromRepeated;
954
            } else {
959
            } else {
955
                // Columns are always flattened
960
                // Columns are always flattened
956
                removed.getElement().detach();
961
                removed.getElement().detach();
957
                removedCount++;
962
                removedCount++;
958
            }
963
            }
959
        }
964
        }
960
        // one remove to be efficient
965
        // one remove to be efficient
961
        if (updateList)
966
        if (updateList)
962
            l.subList(fromIndex, toIndexValid + 1).clear();
967
            l.subList(fromIndex, toIndexValid + 1).clear();
963
    }
968
    }
964
 
969
 
965
    public final void ensureRowCount(int newSize) {
970
    public final void ensureRowCount(int newSize) {
966
        if (newSize > this.getRowCount())
971
        if (newSize > this.getRowCount())
967
            this.setRowCount(newSize);
972
            this.setRowCount(newSize);
968
    }
973
    }
969
 
974
 
970
    public final void setRowCount(int newSize) {
975
    public final void setRowCount(int newSize) {
971
        this.setRowCount(newSize, -1);
976
        this.setRowCount(newSize, -1);
972
    }
977
    }
973
 
978
 
974
    /**
979
    /**
975
     * Changes the row count. If <code>newSize</code> is less than {@link #getRowCount()} extra rows
980
     * Changes the row count. If <code>newSize</code> is less than {@link #getRowCount()} extra rows
976
     * will be chopped off. Otherwise empty cells will be created.
981
     * will be chopped off. Otherwise empty cells will be created.
977
     * 
982
     * 
978
     * @param newSize the new row count.
983
     * @param newSize the new row count.
979
     * @param rowIndex the index of the row to be copied, -1 for empty row (i.e. default style).
984
     * @param rowIndex the index of the row to be copied, -1 for empty row (i.e. default style).
980
     */
985
     */
981
    public final void setRowCount(int newSize, int rowIndex) {
986
    public final void setRowCount(int newSize, int rowIndex) {
982
        final int toGrow = newSize - this.getRowCount();
987
        final int toGrow = newSize - this.getRowCount();
983
        if (toGrow < 0) {
988
        if (toGrow < 0) {
984
            setCount(Axis.ROW, newSize);
989
            setCount(Axis.ROW, newSize);
985
        } else if (toGrow > 0) {
990
        } else if (toGrow > 0) {
986
            final Element elemToClone;
991
            final Element elemToClone;
987
            if (rowIndex < 0) {
992
            if (rowIndex < 0) {
988
                elemToClone = Row.createEmpty(this.getODDocument().getVersion());
993
                elemToClone = Row.createEmpty(this.getODDocument().getVersion());
989
                // each row MUST have the same number of columns
994
                // each row MUST have the same number of columns
990
                elemToClone.addContent(Cell.createEmpty(this.getODDocument().getVersion(), this.getColumnCount()));
995
                elemToClone.addContent(Cell.createEmpty(this.getODDocument().getVersion(), this.getColumnCount()));
991
            } else {
996
            } else {
992
                elemToClone = (Element) getRow(rowIndex).getElement().clone();
997
                elemToClone = (Element) getRow(rowIndex).getElement().clone();
993
            }
998
            }
994
            Axis.ROW.setRepeated(elemToClone, toGrow);
999
            Axis.ROW.setRepeated(elemToClone, toGrow);
995
            // as per section 8.1.1 rows are the last elements inside a table
1000
            // as per section 8.1.1 rows are the last elements inside a table
996
            this.getElement().addContent(elemToClone);
1001
            this.getElement().addContent(elemToClone);
997
            addRow(elemToClone, getRowStyleDesc(), getCellStyleDesc());
1002
            addRow(elemToClone, getRowStyleDesc(), getCellStyleDesc());
998
        }
1003
        }
999
    }
1004
    }
1000
 
1005
 
1001
    public final void removeRow(int index) {
1006
    public final void removeRow(int index) {
1002
        this.removeRows(index, index + 1);
1007
        this.removeRows(index, index + 1);
1003
    }
1008
    }
1004
 
1009
 
1005
    /**
1010
    /**
1006
     * Remove rows between the two passed indexes. If the origin of a merged cell is contained in
1011
     * Remove rows between the two passed indexes. If the origin of a merged cell is contained in
1007
     * the rows to remove, then it will be unmerged, otherwise (the origin is before the rows) the
1012
     * the rows to remove, then it will be unmerged, otherwise (the origin is before the rows) the
1008
     * merged cell will just be shrunk.
1013
     * merged cell will just be shrunk.
1009
     * 
1014
     * 
1010
     * @param firstIndex the start index, inclusive.
1015
     * @param firstIndex the start index, inclusive.
1011
     * @param lastIndex the stop index, exclusive.
1016
     * @param lastIndex the stop index, exclusive.
1012
     */
1017
     */
1013
    public final void removeRows(int firstIndex, int lastIndex) {
1018
    public final void removeRows(int firstIndex, int lastIndex) {
1014
        if (firstIndex < 0)
1019
        if (firstIndex < 0)
1015
            throw new IllegalArgumentException("Negative index " + firstIndex);
1020
            throw new IllegalArgumentException("Negative index " + firstIndex);
1016
        if (firstIndex == lastIndex)
1021
        if (firstIndex == lastIndex)
1017
            return;
1022
            return;
1018
        if (firstIndex > lastIndex)
1023
        if (firstIndex > lastIndex)
1019
            throw new IllegalArgumentException("First index after last index " + firstIndex + " > " + lastIndex);
1024
            throw new IllegalArgumentException("First index after last index " + firstIndex + " > " + lastIndex);
1020
 
1025
 
1021
        // remove row elements, minding merged cells
1026
        // remove row elements, minding merged cells
1022
        final int columnCount = getColumnCount();
1027
        final int columnCount = getColumnCount();
1023
        final Set<Point> coverOrigins = new HashSet<Point>();
1028
        final Set<Point> coverOrigins = new HashSet<Point>();
1024
        for (int y = firstIndex; y < lastIndex; y++) {
1029
        for (int y = firstIndex; y < lastIndex; y++) {
1025
            for (int x = 0; x < columnCount; x++) {
1030
            for (int x = 0; x < columnCount; x++) {
1026
                final Cell<D> cell = this.getImmutableCellAt(x, y);
1031
                final Cell<D> cell = this.getImmutableCellAt(x, y);
1027
                if (cell.isCovered()) {
1032
                if (cell.isCovered()) {
1028
                    coverOrigins.add(this.getCoverOrigin(x, y));
1033
                    coverOrigins.add(this.getCoverOrigin(x, y));
1029
                    // cells spanning inside the removed rows don't need to be unmerged
1034
                    // cells spanning inside the removed rows don't need to be unmerged
1030
                } else if (cell.coversOtherCells() && y + cell.getRowsSpanned() > lastIndex) {
1035
                } else if (cell.coversOtherCells() && y + cell.getRowsSpanned() > lastIndex) {
1031
                    this.getCellAt(x, y).unmerge();
1036
                    this.getCellAt(x, y).unmerge();
1032
                }
1037
                }
1033
            }
1038
            }
1034
        }
1039
        }
1035
 
1040
 
1036
        // don't update this.rows as we need them below and indexes would be wrong anyway
1041
        // don't update this.rows as we need them below and indexes would be wrong anyway
1037
        this.remove(Axis.ROW, firstIndex, lastIndex - 1, false);
1042
        this.remove(Axis.ROW, firstIndex, lastIndex - 1, false);
1038
 
1043
 
1039
        // adjust rows spanned
1044
        // adjust rows spanned
1040
        // we haven't yet reset this.rows, so we can use getImmutableCellAt()
1045
        // we haven't yet reset this.rows, so we can use getImmutableCellAt()
1041
        for (final Point coverOrigin : coverOrigins) {
1046
        for (final Point coverOrigin : coverOrigins) {
1042
            // if the origin is inside the removed rows, then the whole merged cell was removed
1047
            // if the origin is inside the removed rows, then the whole merged cell was removed
1043
            // (if the merged cell extended past lastIndex, it got unmerged above and wasn't added
1048
            // (if the merged cell extended past lastIndex, it got unmerged above and wasn't added
1044
            // to the set)
1049
            // to the set)
1045
            if (coverOrigin.y >= firstIndex) {
1050
            if (coverOrigin.y >= firstIndex) {
1046
                assert this.getImmutableCellAt(coverOrigin.x, coverOrigin.y).getElement().getDocument() == null;
1051
                assert this.getImmutableCellAt(coverOrigin.x, coverOrigin.y).getElement().getDocument() == null;
1047
                continue;
1052
                continue;
1048
            }
1053
            }
1049
            final Cell<D> coverOriginCell = this.getImmutableCellAt(coverOrigin.x, coverOrigin.y);
1054
            final Cell<D> coverOriginCell = this.getImmutableCellAt(coverOrigin.x, coverOrigin.y);
1050
            final int rowsSpanned = coverOriginCell.getRowsSpanned();
1055
            final int rowsSpanned = coverOriginCell.getRowsSpanned();
1051
            // otherwise the cover origin is between firstIndex and lastIndex and should have been
1056
            // otherwise the cover origin is between firstIndex and lastIndex and should have been
1052
            // unmerged
1057
            // unmerged
1053
            assert rowsSpanned > 1;
1058
            assert rowsSpanned > 1;
1054
            final int stopY = coverOrigin.y + rowsSpanned;
1059
            final int stopY = coverOrigin.y + rowsSpanned;
1055
            final int toRemove = Math.min(lastIndex, stopY) - firstIndex;
1060
            final int toRemove = Math.min(lastIndex, stopY) - firstIndex;
1056
            assert toRemove > 0 && toRemove < rowsSpanned;
1061
            assert toRemove > 0 && toRemove < rowsSpanned;
1057
            coverOriginCell.getElement().setAttribute("number-rows-spanned", (rowsSpanned - toRemove) + "", getTABLE());
1062
            coverOriginCell.getElement().setAttribute("number-rows-spanned", (rowsSpanned - toRemove) + "", getTABLE());
1058
        }
1063
        }
1059
 
1064
 
1060
        // remove empty table-row-group
1065
        // remove empty table-row-group
1061
        // since indexes are stored in TableGroup, we can do it after having removed rows
1066
        // since indexes are stored in TableGroup, we can do it after having removed rows
1062
        final Set<TableGroup> groups = new HashSet<TableGroup>();
1067
        final Set<TableGroup> groups = new HashSet<TableGroup>();
1063
        for (int y = firstIndex; y < lastIndex; y++) {
1068
        for (int y = firstIndex; y < lastIndex; y++) {
1064
            groups.add(this.getRowGroupAt(y));
1069
            groups.add(this.getRowGroupAt(y));
1065
        }
1070
        }
1066
        for (final TableGroup group : groups) {
1071
        for (final TableGroup group : groups) {
1067
            TableGroup g = group;
1072
            TableGroup g = group;
1068
            Element elem = g.getElement();
1073
            Element elem = g.getElement();
1069
            while (g != null && elem.getParent() != null && elem.getChildren().size() == 0) {
1074
            while (g != null && elem.getParent() != null && elem.getChildren().size() == 0) {
1070
                elem.detach();
1075
                elem.detach();
1071
                g = g.getParent();
1076
                g = g.getParent();
1072
                elem = g == null ? null : g.getElement();
1077
                elem = g == null ? null : g.getElement();
1073
            }
1078
            }
1074
        }
1079
        }
1075
 
1080
 
1076
        // recreate list from XML
1081
        // recreate list from XML
1077
        this.readRows();
1082
        this.readRows();
1078
    }
1083
    }
1079
 
1084
 
1080
    // *** table models
1085
    // *** table models
1081
 
1086
 
1082
    public final SheetTableModel<D> getTableModel(final int column, final int row) {
1087
    public final SheetTableModel<D> getTableModel(final int column, final int row) {
1083
        return new SheetTableModel<D>(this, row, column);
1088
        return new SheetTableModel<D>(this, row, column);
1084
    }
1089
    }
1085
 
1090
 
1086
    public final SheetTableModel<D> getTableModel(final int column, final int row, final int lastCol, final int lastRow) {
1091
    public final SheetTableModel<D> getTableModel(final int column, final int row, final int lastCol, final int lastRow) {
1087
        return new SheetTableModel<D>(this, row, column, lastRow, lastCol);
1092
        return new SheetTableModel<D>(this, row, column, lastRow, lastCol);
1088
    }
1093
    }
1089
 
1094
 
1090
    public final MutableTableModel<D> getMutableTableModel(final int column, final int row) {
1095
    public final MutableTableModel<D> getMutableTableModel(final int column, final int row) {
1091
        return new MutableTableModel<D>(this, row, column);
1096
        return new MutableTableModel<D>(this, row, column);
1092
    }
1097
    }
1093
 
1098
 
1094
    /**
1099
    /**
1095
     * Return the table from <code>start</code> to <code>end</code> inclusive.
1100
     * Return the table from <code>start</code> to <code>end</code> inclusive.
1096
     * 
1101
     * 
1097
     * @param start the first cell of the result.
1102
     * @param start the first cell of the result.
1098
     * @param end the last cell of the result.
1103
     * @param end the last cell of the result.
1099
     * @return the table.
1104
     * @return the table.
1100
     */
1105
     */
1101
    public final MutableTableModel<D> getMutableTableModel(final Point start, final Point end) {
1106
    public final MutableTableModel<D> getMutableTableModel(final Point start, final Point end) {
1102
        // +1 since exclusive
1107
        // +1 since exclusive
1103
        return new MutableTableModel<D>(this, start.y, start.x, end.y + 1, end.x + 1);
1108
        return new MutableTableModel<D>(this, start.y, start.x, end.y + 1, end.x + 1);
1104
    }
1109
    }
1105
 
1110
 
1106
    public final void merge(TableModel t, final int column, final int row) {
1111
    public final void merge(TableModel t, final int column, final int row) {
1107
        this.merge(t, column, row, false);
1112
        this.merge(t, column, row, false);
1108
    }
1113
    }
1109
 
1114
 
1110
    /**
1115
    /**
1111
     * Merges t into this sheet at the specified point.
1116
     * Merges t into this sheet at the specified point.
1112
     * 
1117
     * 
1113
     * @param t the data to be merged.
1118
     * @param t the data to be merged.
1114
     * @param column the column t will be merged at.
1119
     * @param column the column t will be merged at.
1115
     * @param row the row t will be merged at.
1120
     * @param row the row t will be merged at.
1116
     * @param includeColNames if <code>true</code> the column names of t will also be merged.
1121
     * @param includeColNames if <code>true</code> the column names of t will also be merged.
1117
     */
1122
     */
1118
    public final void merge(TableModel t, final int column, final int row, final boolean includeColNames) {
1123
    public final void merge(TableModel t, final int column, final int row, final boolean includeColNames) {
1119
        final int offset = (includeColNames ? 1 : 0);
1124
        final int offset = (includeColNames ? 1 : 0);
1120
        // the columns must be first, see section 8.1.1 of v1.1
1125
        // the columns must be first, see section 8.1.1 of v1.1
1121
        this.ensureColumnCount(column + t.getColumnCount());
1126
        this.ensureColumnCount(column + t.getColumnCount());
1122
        this.ensureRowCount(row + t.getRowCount() + offset);
1127
        this.ensureRowCount(row + t.getRowCount() + offset);
1123
        final TableModel thisModel = this.getMutableTableModel(column, row);
1128
        final TableModel thisModel = this.getMutableTableModel(column, row);
1124
        if (includeColNames) {
1129
        if (includeColNames) {
1125
            for (int x = 0; x < t.getColumnCount(); x++) {
1130
            for (int x = 0; x < t.getColumnCount(); x++) {
1126
                thisModel.setValueAt(t.getColumnName(x), 0, x);
1131
                thisModel.setValueAt(t.getColumnName(x), 0, x);
1127
            }
1132
            }
1128
        }
1133
        }
1129
        for (int y = 0; y < t.getRowCount(); y++) {
1134
        for (int y = 0; y < t.getRowCount(); y++) {
1130
            for (int x = 0; x < t.getColumnCount(); x++) {
1135
            for (int x = 0; x < t.getColumnCount(); x++) {
1131
                final Object value = t.getValueAt(y, x);
1136
                final Object value = t.getValueAt(y, x);
1132
                thisModel.setValueAt(value, y + offset, x);
1137
                thisModel.setValueAt(value, y + offset, x);
1133
            }
1138
            }
1134
        }
1139
        }
1135
    }
1140
    }
1136
 
1141
 
1137
    /**
1142
    /**
1138
     * All ranges defined in this table.
1143
     * All ranges defined in this table.
1139
     * 
1144
     * 
1140
     * @return the names.
1145
     * @return the names.
1141
     * @see SpreadSheet#getRangesNames()
1146
     * @see SpreadSheet#getRangesNames()
1142
     */
1147
     */
1143
    public final Set<String> getRangesNames() {
1148
    public final Set<String> getRangesNames() {
1144
        return SpreadSheet.getRangesNames(getElement(), getTABLE());
1149
        return SpreadSheet.getRangesNames(getElement(), getTABLE());
1145
    }
1150
    }
1146
 
1151
 
1147
    /**
1152
    /**
1148
     * Get a named range in this table.
1153
     * Get a named range in this table.
1149
     * 
1154
     * 
1150
     * @param name the name of the range.
1155
     * @param name the name of the range.
1151
     * @return a named range, or <code>null</code> if the passed name doesn't exist.
1156
     * @return a named range, or <code>null</code> if the passed name doesn't exist.
1152
     * @see #getRangesNames()
1157
     * @see #getRangesNames()
1153
     * @see SpreadSheet#getRange(String)
1158
     * @see SpreadSheet#getRange(String)
1154
     */
1159
     */
1155
    public final Range getRange(String name) {
1160
    public final Range getRange(String name) {
1156
        return SpreadSheet.getRange(getElement(), getTABLE(), name);
1161
        return SpreadSheet.getRange(getElement(), getTABLE(), name);
1157
    }
1162
    }
1158
 
1163
 
1159
    public final MutableTableModel<D> getTableModel(String name) {
1164
    public final MutableTableModel<D> getTableModel(String name) {
1160
        final Range points = getRange(name);
1165
        final Range points = getRange(name);
1161
        if (points == null)
1166
        if (points == null)
1162
            return null;
1167
            return null;
1163
        if (points.spanSheets())
1168
        if (points.spanSheets())
1164
            throw new IllegalStateException("different sheet names: " + points.getStartSheet() + " != " + points.getEndSheet());
1169
            throw new IllegalStateException("different sheet names: " + points.getStartSheet() + " != " + points.getEndSheet());
1165
        return this.getMutableTableModel(points.getStartPoint(), points.getEndPoint());
1170
        return this.getMutableTableModel(points.getStartPoint(), points.getEndPoint());
1166
    }
1171
    }
1167
 
1172
 
1168
    // * UsedRange & CurrentRegion
1173
    // * UsedRange & CurrentRegion
1169
 
1174
 
1170
    /**
1175
    /**
1171
     * The range that covers all used cells.
1176
     * The range that covers all used cells.
1172
     * 
1177
     * 
1173
     * @return the range that covers all used cells, <code>null</code> if the table is completely
1178
     * @return the range that covers all used cells, <code>null</code> if the table is completely
1174
     *         empty.
1179
     *         empty.
1175
     */
1180
     */
1176
    public final Range getUsedRange() {
1181
    public final Range getUsedRange() {
1177
        return this.getUsedRange(false);
1182
        return this.getUsedRange(false);
1178
    }
1183
    }
1179
 
1184
 
1180
    /**
1185
    /**
1181
     * The range that covers all used cells.
1186
     * The range that covers all used cells.
1182
     * 
1187
     * 
1183
     * @param checkStyle <code>true</code> to check the background and borders in addition to the
1188
     * @param checkStyle <code>true</code> to check the background and borders in addition to the
1184
     *        content.
1189
     *        content.
1185
     * @return the range that covers all used cells, <code>null</code> if the table is completely
1190
     * @return the range that covers all used cells, <code>null</code> if the table is completely
1186
     *         blank.
1191
     *         blank.
1187
     */
1192
     */
1188
    public final Range getUsedRange(boolean checkStyle) {
1193
    public final Range getUsedRange(boolean checkStyle) {
1189
        int minX = -1, minY = -1, maxX = -1, maxY = -1;
1194
        int minX = -1, minY = -1, maxX = -1, maxY = -1;
1190
        final int colCount = this.getColumnCount();
1195
        final int colCount = this.getColumnCount();
1191
        final int rowCount = this.getRowCount();
1196
        final int rowCount = this.getRowCount();
1192
        for (int x = 0; x < colCount; x++) {
1197
        for (int x = 0; x < colCount; x++) {
1193
            for (int y = 0; y < rowCount; y++) {
1198
            for (int y = 0; y < rowCount; y++) {
1194
                if (!this.isCellBlank(x, y, checkStyle)) {
1199
                if (!this.isCellBlank(x, y, checkStyle)) {
1195
                    if (minX < 0 || x < minX)
1200
                    if (minX < 0 || x < minX)
1196
                        minX = x;
1201
                        minX = x;
1197
                    if (minY < 0 || y < minY)
1202
                    if (minY < 0 || y < minY)
1198
                        minY = y;
1203
                        minY = y;
1199
 
1204
 
1200
                    if (maxX < 0 || x > maxX)
1205
                    if (maxX < 0 || x > maxX)
1201
                        maxX = x;
1206
                        maxX = x;
1202
                    if (maxY < 0 || y > maxY)
1207
                    if (maxY < 0 || y > maxY)
1203
                        maxY = y;
1208
                        maxY = y;
1204
                }
1209
                }
1205
            }
1210
            }
1206
        }
1211
        }
1207
        return minX < 0 ? null : new Range(getName(), new Point(minX, minY), new Point(maxX, maxY));
1212
        return minX < 0 ? null : new Range(getName(), new Point(minX, minY), new Point(maxX, maxY));
1208
    }
1213
    }
1209
 
1214
 
1210
    protected final boolean isCellBlank(final int x, int y, boolean checkStyle) {
1215
    protected final boolean isCellBlank(final int x, int y, boolean checkStyle) {
1211
        if (!getImmutableCellAt(x, y).isEmpty())
1216
        if (!getImmutableCellAt(x, y).isEmpty())
1212
            return false;
1217
            return false;
1213
 
1218
 
1214
        if (checkStyle) {
1219
        if (checkStyle) {
1215
            final StyleTableCellProperties tableCellProperties = this.getTableCellPropertiesAt(x, y);
1220
            final StyleTableCellProperties tableCellProperties = this.getTableCellPropertiesAt(x, y);
1216
            return tableCellProperties == null || (tableCellProperties.getBackgroundColor() == null && tableCellProperties.getBorders().isEmpty());
1221
            return tableCellProperties == null || (tableCellProperties.getBackgroundColor() == null && tableCellProperties.getBorders().isEmpty());
1217
        } else {
1222
        } else {
1218
            return true;
1223
            return true;
1219
        }
1224
        }
1220
    }
1225
    }
1221
 
1226
 
1222
    private class RegionExplorer {
1227
    private class RegionExplorer {
1223
 
1228
 
1224
        private final boolean checkStyle;
1229
        private final boolean checkStyle;
1225
        private final int rowCount, colCount;
1230
        private final int rowCount, colCount;
1226
        protected int minX, minY, maxX, maxY;
1231
        protected int minX, minY, maxX, maxY;
1227
 
1232
 
1228
        public RegionExplorer(final int startX, final int startY, final boolean checkStyle) {
1233
        public RegionExplorer(final int startX, final int startY, final boolean checkStyle) {
1229
            this.rowCount = getRowCount();
1234
            this.rowCount = getRowCount();
1230
            this.colCount = getColumnCount();
1235
            this.colCount = getColumnCount();
1231
            this.minX = this.maxX = startX;
1236
            this.minX = this.maxX = startX;
1232
            this.minY = this.maxY = startY;
1237
            this.minY = this.maxY = startY;
1233
            this.checkStyle = checkStyle;
1238
            this.checkStyle = checkStyle;
1234
        }
1239
        }
1235
 
1240
 
1236
        public boolean canXDecrement() {
1241
        public boolean canXDecrement() {
1237
            return this.minX > 0;
1242
            return this.minX > 0;
1238
        }
1243
        }
1239
 
1244
 
1240
        public boolean canYDecrement() {
1245
        public boolean canYDecrement() {
1241
            return this.minY > 0;
1246
            return this.minY > 0;
1242
        }
1247
        }
1243
 
1248
 
1244
        public boolean canXIncrement() {
1249
        public boolean canXIncrement() {
1245
            return this.maxX < this.colCount - 1;
1250
            return this.maxX < this.colCount - 1;
1246
        }
1251
        }
1247
 
1252
 
1248
        public boolean canYIncrement() {
1253
        public boolean canYIncrement() {
1249
            return this.maxY < this.rowCount - 1;
1254
            return this.maxY < this.rowCount - 1;
1250
        }
1255
        }
1251
 
1256
 
1252
        private boolean checkRow(final boolean upper) {
1257
        private boolean checkRow(final boolean upper) {
1253
            if (upper && this.canYDecrement() || !upper && this.canYIncrement()) {
1258
            if (upper && this.canYDecrement() || !upper && this.canYIncrement()) {
1254
                final int y = upper ? this.minY - 1 : this.maxY + 1;
1259
                final int y = upper ? this.minY - 1 : this.maxY + 1;
1255
                final int start = this.canXDecrement() ? this.minX - 1 : this.minX;
1260
                final int start = this.canXDecrement() ? this.minX - 1 : this.minX;
1256
                final int stop = this.canXIncrement() ? this.maxX + 1 : this.maxX;
1261
                final int stop = this.canXIncrement() ? this.maxX + 1 : this.maxX;
1257
                for (int x = start; x <= stop; x++) {
1262
                for (int x = start; x <= stop; x++) {
1258
                    if (!isCellBlank(x, y, this.checkStyle)) {
1263
                    if (!isCellBlank(x, y, this.checkStyle)) {
1259
                        if (upper)
1264
                        if (upper)
1260
                            this.minY = y;
1265
                            this.minY = y;
1261
                        else
1266
                        else
1262
                            this.maxY = y;
1267
                            this.maxY = y;
1263
                        if (x < this.minX)
1268
                        if (x < this.minX)
1264
                            this.minX = x;
1269
                            this.minX = x;
1265
                        if (x > this.maxX)
1270
                        if (x > this.maxX)
1266
                            this.maxX = x;
1271
                            this.maxX = x;
1267
                        return true;
1272
                        return true;
1268
                    }
1273
                    }
1269
                }
1274
                }
1270
            }
1275
            }
1271
            return false;
1276
            return false;
1272
        }
1277
        }
1273
 
1278
 
1274
        // sans corners (checked by checkRow())
1279
        // sans corners (checked by checkRow())
1275
        private boolean checkCol(final boolean left) {
1280
        private boolean checkCol(final boolean left) {
1276
            if (left && this.canXDecrement() || !left && this.canXIncrement()) {
1281
            if (left && this.canXDecrement() || !left && this.canXIncrement()) {
1277
                final int x = left ? this.minX - 1 : this.maxX + 1;
1282
                final int x = left ? this.minX - 1 : this.maxX + 1;
1278
                for (int y = this.minY; y <= this.maxY; y++) {
1283
                for (int y = this.minY; y <= this.maxY; y++) {
1279
                    if (!isCellBlank(x, y, this.checkStyle)) {
1284
                    if (!isCellBlank(x, y, this.checkStyle)) {
1280
                        if (left)
1285
                        if (left)
1281
                            this.minX = x;
1286
                            this.minX = x;
1282
                        else
1287
                        else
1283
                            this.maxX = x;
1288
                            this.maxX = x;
1284
                        return true;
1289
                        return true;
1285
                    }
1290
                    }
1286
                }
1291
                }
1287
            }
1292
            }
1288
            return false;
1293
            return false;
1289
        }
1294
        }
1290
 
1295
 
1291
        private final boolean checkFrame() {
1296
        private final boolean checkFrame() {
1292
            return this.checkRow(true) || this.checkRow(false) || this.checkCol(true) || this.checkCol(false);
1297
            return this.checkRow(true) || this.checkRow(false) || this.checkCol(true) || this.checkCol(false);
1293
        }
1298
        }
1294
 
1299
 
1295
        public final Range getCurrentRegion() {
1300
        public final Range getCurrentRegion() {
1296
            while (this.checkFrame())
1301
            while (this.checkFrame())
1297
                ;// bounded by table size
1302
                ;// bounded by table size
1298
            return new Range(getName(), new Point(this.minX, this.minY), new Point(this.maxX, this.maxY));
1303
            return new Range(getName(), new Point(this.minX, this.minY), new Point(this.maxX, this.maxY));
1299
        }
1304
        }
1300
    }
1305
    }
1301
 
1306
 
1302
    public final Range getCurrentRegion(String ref) {
1307
    public final Range getCurrentRegion(String ref) {
1303
        return this.getCurrentRegion(ref, false);
1308
        return this.getCurrentRegion(ref, false);
1304
    }
1309
    }
1305
 
1310
 
1306
    public final Range getCurrentRegion(String ref, boolean checkStyle) {
1311
    public final Range getCurrentRegion(String ref, boolean checkStyle) {
1307
        final Point p = resolveHint(ref);
1312
        final Point p = resolveHint(ref);
1308
        return this.getCurrentRegion(p.x, p.y, checkStyle);
1313
        return this.getCurrentRegion(p.x, p.y, checkStyle);
1309
    }
1314
    }
1310
 
1315
 
1311
    /**
1316
    /**
1312
     * The smallest range containing the passed cell completely surrounded by empty rows and
1317
     * The smallest range containing the passed cell completely surrounded by empty rows and
1313
     * columns.
1318
     * columns.
1314
     * 
1319
     * 
1315
     * @param startX x coordinate.
1320
     * @param startX x coordinate.
1316
     * @param startY y coordinate.
1321
     * @param startY y coordinate.
1317
     * @return the smallest range containing the passed cell.
1322
     * @return the smallest range containing the passed cell.
1318
     * @see <a href="http://msdn.microsoft.com/library/aa214248(v=office.11).aspx">CurrentRegion
1323
     * @see <a href="http://msdn.microsoft.com/library/aa214248(v=office.11).aspx">CurrentRegion
1319
     *      Property</a>
1324
     *      Property</a>
1320
     */
1325
     */
1321
    public final Range getCurrentRegion(final int startX, final int startY) {
1326
    public final Range getCurrentRegion(final int startX, final int startY) {
1322
        return this.getCurrentRegion(startX, startY, false);
1327
        return this.getCurrentRegion(startX, startY, false);
1323
    }
1328
    }
1324
 
1329
 
1325
    public final Range getCurrentRegion(final int startX, final int startY, final boolean checkStyle) {
1330
    public final Range getCurrentRegion(final int startX, final int startY, final boolean checkStyle) {
1326
        return new RegionExplorer(startX, startY, checkStyle).getCurrentRegion();
1331
        return new RegionExplorer(startX, startY, checkStyle).getCurrentRegion();
1327
    }
1332
    }
1328
 
1333
 
1329
    // *** static
1334
    // *** static
1330
 
1335
 
1331
    /**
1336
    /**
1332
     * Convert string coordinates into numeric ones.
1337
     * Convert string coordinates into numeric ones.
1333
     * 
1338
     * 
1334
     * @param ref the string address, eg "$AA$34" or "AA34".
1339
     * @param ref the string address, eg "$AA$34" or "AA34".
1335
     * @return the numeric coordinates or <code>null</code> if <code>ref</code> is not valid, eg
1340
     * @return the numeric coordinates or <code>null</code> if <code>ref</code> is not valid, eg
1336
     *         {26, 33}.
1341
     *         {26, 33}.
1337
     */
1342
     */
1338
    static final Point resolve(String ref) {
1343
    static final Point resolve(String ref) {
1339
        final Matcher matcher = SpreadSheet.minCellPattern.matcher(ref);
1344
        final Matcher matcher = SpreadSheet.minCellPattern.matcher(ref);
1340
        if (!matcher.matches())
1345
        if (!matcher.matches())
1341
            return null;
1346
            return null;
1342
        return resolve(matcher.group(1), matcher.group(2));
1347
        return resolve(matcher.group(1), matcher.group(2));
1343
    }
1348
    }
1344
 
1349
 
1345
    /**
1350
    /**
1346
     * Convert string coordinates into numeric ones. ATTN this method does no checks.
1351
     * Convert string coordinates into numeric ones. ATTN this method does no checks.
1347
     * 
1352
     * 
1348
     * @param letters the column, eg "AA".
1353
     * @param letters the column, eg "AA".
1349
     * @param digits the row, eg "34".
1354
     * @param digits the row, eg "34".
1350
     * @return the numeric coordinates, eg {26, 33}.
1355
     * @return the numeric coordinates, eg {26, 33}.
1351
     */
1356
     */
1352
    static final Point resolve(final String letters, final String digits) {
1357
    static final Point resolve(final String letters, final String digits) {
1353
        return new Point(toInt(letters), Integer.parseInt(digits) - 1);
1358
        return new Point(toInt(letters), Integer.parseInt(digits) - 1);
1354
    }
1359
    }
1355
 
1360
 
1356
    // "AA" => 26
1361
    // "AA" => 26
1357
    static final int toInt(String col) {
1362
    static final int toInt(String col) {
1358
        if (col.length() < 1)
1363
        if (col.length() < 1)
1359
            throw new IllegalArgumentException("x cannot be empty");
1364
            throw new IllegalArgumentException("x cannot be empty");
1360
        col = col.toUpperCase();
1365
        col = col.toUpperCase();
1361
 
1366
 
1362
        int x = 0;
1367
        int x = 0;
1363
        for (int i = 0; i < col.length(); i++) {
1368
        for (int i = 0; i < col.length(); i++) {
1364
            x = x * 26 + (col.charAt(i) - 'A' + 1);
1369
            x = x * 26 + (col.charAt(i) - 'A' + 1);
1365
        }
1370
        }
1366
 
1371
 
1367
        // zero based
1372
        // zero based
1368
        return x - 1;
1373
        return x - 1;
1369
    }
1374
    }
1370
 
1375
 
1371
    public static final String toStr(int col) {
1376
    public static final String toStr(int col) {
1372
        if (col < 0)
1377
        if (col < 0)
1373
            throw new IllegalArgumentException("negative column : " + col);
1378
            throw new IllegalArgumentException("negative column : " + col);
1374
        // one based (i.e. 0 is A)
1379
        // one based (i.e. 0 is A)
1375
        col++;
1380
        col++;
1376
 
1381
 
1377
        final int radix = 26;
1382
        final int radix = 26;
1378
        final StringBuilder chars = new StringBuilder(4);
1383
        final StringBuilder chars = new StringBuilder(4);
1379
        while (col > 0) {
1384
        while (col > 0) {
1380
            chars.append((char) ('A' + ((col - 1) % radix)));
1385
            chars.append((char) ('A' + ((col - 1) % radix)));
1381
            col = (col - 1) / radix;
1386
            col = (col - 1) / radix;
1382
        }
1387
        }
1383
 
1388
 
1384
        return chars.reverse().toString();
1389
        return chars.reverse().toString();
1385
    }
1390
    }
1386
 
1391
 
1387
    /**
1392
    /**
1388
     * Convert numeric coordinates into string ones.
1393
     * Convert numeric coordinates into string ones.
1389
     * 
1394
     * 
1390
     * @param p the numeric coordinates, e.g. {26, 33}.
1395
     * @param p the numeric coordinates, e.g. {26, 33}.
1391
     * @return the string address, e.g. "AA34".
1396
     * @return the string address, e.g. "AA34".
1392
     */
1397
     */
1393
    static final String getAddress(Point p) {
1398
    static final String getAddress(Point p) {
-
 
1399
        return getAddress(p.x, p.y);
-
 
1400
    }
-
 
1401
 
-
 
1402
    static final String getAddress(final int x, final int y) {
1394
        if (p.x < 0 || p.y < 0)
1403
        if (x < 0 || y < 0)
1395
            throw new IllegalArgumentException("negative coordinates : " + p);
1404
            throw new IllegalArgumentException("negative coordinates : " + x + ":" + y);
1396
        return toStr(p.x) + (p.y + 1);
1405
        return toStr(x) + (y + 1);
1397
    }
1406
    }
1398
}
1407
}