001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.poi.hssf.record.aggregates;
018:
019: import org.apache.poi.hssf.record.ColumnInfoRecord;
020: import org.apache.poi.hssf.record.Record;
021: import org.apache.poi.hssf.record.RecordInputStream;
022:
023: import java.util.ArrayList;
024: import java.util.Iterator;
025: import java.util.List;
026:
027: /**
028: * @author Glen Stampoultzis
029: * @version $Id: ColumnInfoRecordsAggregate.java 496526 2007-01-15 22:46:35Z markt $
030: */
031: public class ColumnInfoRecordsAggregate extends Record {
032: // int size = 0;
033: List records = null;
034:
035: public ColumnInfoRecordsAggregate() {
036: records = new ArrayList();
037: }
038:
039: /** You never fill an aggregate */
040: protected void fillFields(RecordInputStream in) {
041: }
042:
043: /** Not required by an aggregate */
044: protected void validateSid(short id) {
045: }
046:
047: /** It's an aggregate... just made something up */
048: public short getSid() {
049: return -1012;
050: }
051:
052: public int getRecordSize() {
053: int size = 0;
054: for (Iterator iterator = records.iterator(); iterator.hasNext();)
055: size += ((ColumnInfoRecord) iterator.next())
056: .getRecordSize();
057: return size;
058: }
059:
060: public Iterator getIterator() {
061: return records.iterator();
062: }
063:
064: /**
065: * Performs a deep clone of the record
066: */
067: public Object clone() {
068: ColumnInfoRecordsAggregate rec = new ColumnInfoRecordsAggregate();
069: for (int k = 0; k < records.size(); k++) {
070: ColumnInfoRecord ci = (ColumnInfoRecord) records.get(k);
071: ci = (ColumnInfoRecord) ci.clone();
072: rec.insertColumn(ci);
073: }
074: return rec;
075: }
076:
077: /**
078: * Inserts a column into the aggregate (at the end of the list).
079: */
080: public void insertColumn(ColumnInfoRecord col) {
081: records.add(col);
082: }
083:
084: /**
085: * Inserts a column into the aggregate (at the position specified
086: * by <code>idx</code>.
087: */
088: public void insertColumn(int idx, ColumnInfoRecord col) {
089: records.add(idx, col);
090: }
091:
092: public int getNumColumns() {
093: return records.size();
094: }
095:
096: /**
097: * called by the class that is responsible for writing this sucker.
098: * Subclasses should implement this so that their data is passed back in a
099: * byte array.
100: *
101: * @param offset offset to begin writing at
102: * @param data byte array containing instance data
103: * @return number of bytes written
104: */
105: public int serialize(int offset, byte[] data) {
106: Iterator itr = records.iterator();
107: int pos = offset;
108:
109: while (itr.hasNext()) {
110: pos += ((Record) itr.next()).serialize(pos, data);
111: }
112: return pos - offset;
113: }
114:
115: public int findStartOfColumnOutlineGroup(int idx) {
116: // Find the start of the group.
117: ColumnInfoRecord columnInfo = (ColumnInfoRecord) records
118: .get(idx);
119: int level = columnInfo.getOutlineLevel();
120: while (idx != 0) {
121: ColumnInfoRecord prevColumnInfo = (ColumnInfoRecord) records
122: .get(idx - 1);
123: if (columnInfo.getFirstColumn() - 1 == prevColumnInfo
124: .getLastColumn()) {
125: if (prevColumnInfo.getOutlineLevel() < level) {
126: break;
127: }
128: idx--;
129: columnInfo = prevColumnInfo;
130: } else {
131: break;
132: }
133: }
134:
135: return idx;
136: }
137:
138: public int findEndOfColumnOutlineGroup(int idx) {
139: // Find the end of the group.
140: ColumnInfoRecord columnInfo = (ColumnInfoRecord) records
141: .get(idx);
142: int level = columnInfo.getOutlineLevel();
143: while (idx < records.size() - 1) {
144: ColumnInfoRecord nextColumnInfo = (ColumnInfoRecord) records
145: .get(idx + 1);
146: if (columnInfo.getLastColumn() + 1 == nextColumnInfo
147: .getFirstColumn()) {
148: if (nextColumnInfo.getOutlineLevel() < level) {
149: break;
150: }
151: idx++;
152: columnInfo = nextColumnInfo;
153: } else {
154: break;
155: }
156: }
157:
158: return idx;
159: }
160:
161: public ColumnInfoRecord getColInfo(int idx) {
162: return (ColumnInfoRecord) records.get(idx);
163: }
164:
165: public ColumnInfoRecord writeHidden(ColumnInfoRecord columnInfo,
166: int idx, boolean hidden) {
167: int level = columnInfo.getOutlineLevel();
168: while (idx < records.size()) {
169: columnInfo.setHidden(hidden);
170: if (idx + 1 < records.size()) {
171: ColumnInfoRecord nextColumnInfo = (ColumnInfoRecord) records
172: .get(idx + 1);
173: if (columnInfo.getLastColumn() + 1 == nextColumnInfo
174: .getFirstColumn()) {
175: if (nextColumnInfo.getOutlineLevel() < level)
176: break;
177: columnInfo = nextColumnInfo;
178: } else {
179: break;
180: }
181: }
182: idx++;
183: }
184: return columnInfo;
185: }
186:
187: public boolean isColumnGroupCollapsed(int idx) {
188: int endOfOutlineGroupIdx = findEndOfColumnOutlineGroup(idx);
189: if (endOfOutlineGroupIdx >= records.size())
190: return false;
191: if (getColInfo(endOfOutlineGroupIdx).getLastColumn() + 1 != getColInfo(
192: endOfOutlineGroupIdx + 1).getFirstColumn())
193: return false;
194: else
195: return getColInfo(endOfOutlineGroupIdx + 1).getCollapsed();
196: }
197:
198: public boolean isColumnGroupHiddenByParent(int idx) {
199: // Look out outline details of end
200: int endLevel;
201: boolean endHidden;
202: int endOfOutlineGroupIdx = findEndOfColumnOutlineGroup(idx);
203: if (endOfOutlineGroupIdx >= records.size()) {
204: endLevel = 0;
205: endHidden = false;
206: } else if (getColInfo(endOfOutlineGroupIdx).getLastColumn() + 1 != getColInfo(
207: endOfOutlineGroupIdx + 1).getFirstColumn()) {
208: endLevel = 0;
209: endHidden = false;
210: } else {
211: endLevel = getColInfo(endOfOutlineGroupIdx + 1)
212: .getOutlineLevel();
213: endHidden = getColInfo(endOfOutlineGroupIdx + 1)
214: .getHidden();
215: }
216:
217: // Look out outline details of start
218: int startLevel;
219: boolean startHidden;
220: int startOfOutlineGroupIdx = findStartOfColumnOutlineGroup(idx);
221: if (startOfOutlineGroupIdx <= 0) {
222: startLevel = 0;
223: startHidden = false;
224: } else if (getColInfo(startOfOutlineGroupIdx).getFirstColumn() - 1 != getColInfo(
225: startOfOutlineGroupIdx - 1).getLastColumn()) {
226: startLevel = 0;
227: startHidden = false;
228: } else {
229: startLevel = getColInfo(startOfOutlineGroupIdx - 1)
230: .getOutlineLevel();
231: startHidden = getColInfo(startOfOutlineGroupIdx - 1)
232: .getHidden();
233: }
234:
235: if (endLevel > startLevel) {
236: return endHidden;
237: } else {
238: return startHidden;
239: }
240: }
241:
242: public void collapseColumn(short columnNumber) {
243: int idx = findColumnIdx(columnNumber, 0);
244: if (idx == -1)
245: return;
246:
247: // Find the start of the group.
248: ColumnInfoRecord columnInfo = (ColumnInfoRecord) records
249: .get(findStartOfColumnOutlineGroup(idx));
250:
251: // Hide all the columns until the end of the group
252: columnInfo = writeHidden(columnInfo, idx, true);
253:
254: // Write collapse field
255: setColumn((short) (columnInfo.getLastColumn() + 1), null, null,
256: null, null, Boolean.TRUE);
257: }
258:
259: public void expandColumn(short columnNumber) {
260: int idx = findColumnIdx(columnNumber, 0);
261: if (idx == -1)
262: return;
263:
264: // If it is already exapanded do nothing.
265: if (!isColumnGroupCollapsed(idx))
266: return;
267:
268: // Find the start of the group.
269: int startIdx = findStartOfColumnOutlineGroup(idx);
270: ColumnInfoRecord columnInfo = getColInfo(startIdx);
271:
272: // Find the end of the group.
273: int endIdx = findEndOfColumnOutlineGroup(idx);
274: ColumnInfoRecord endColumnInfo = getColInfo(endIdx);
275:
276: // expand:
277: // colapsed bit must be unset
278: // hidden bit gets unset _if_ surrounding groups are expanded you can determine
279: // this by looking at the hidden bit of the enclosing group. You will have
280: // to look at the start and the end of the current group to determine which
281: // is the enclosing group
282: // hidden bit only is altered for this outline level. ie. don't uncollapse contained groups
283: if (!isColumnGroupHiddenByParent(idx)) {
284: for (int i = startIdx; i <= endIdx; i++) {
285: if (columnInfo.getOutlineLevel() == getColInfo(i)
286: .getOutlineLevel())
287: getColInfo(i).setHidden(false);
288: }
289: }
290:
291: // Write collapse field
292: setColumn((short) (columnInfo.getLastColumn() + 1), null, null,
293: null, null, Boolean.FALSE);
294: }
295:
296: /**
297: * creates the ColumnInfo Record and sets it to a default column/width
298: * @see org.apache.poi.hssf.record.ColumnInfoRecord
299: * @return record containing a ColumnInfoRecord
300: */
301: public static Record createColInfo() {
302: ColumnInfoRecord retval = new ColumnInfoRecord();
303:
304: retval.setColumnWidth((short) 2275);
305: // was: retval.setOptions(( short ) 6);
306: retval.setOptions((short) 2);
307: retval.setXFIndex((short) 0x0f);
308: return retval;
309: }
310:
311: public void setColumn(short column, Short xfIndex, Short width,
312: Integer level, Boolean hidden, Boolean collapsed) {
313: ColumnInfoRecord ci = null;
314: int k = 0;
315:
316: for (k = 0; k < records.size(); k++) {
317: ci = (ColumnInfoRecord) records.get(k);
318: if ((ci.getFirstColumn() <= column)
319: && (column <= ci.getLastColumn())) {
320: break;
321: }
322: ci = null;
323: }
324:
325: if (ci != null) {
326: boolean styleChanged = xfIndex != null
327: && ci.getXFIndex() != xfIndex.shortValue();
328: boolean widthChanged = width != null
329: && ci.getColumnWidth() != width.shortValue();
330: boolean levelChanged = level != null
331: && ci.getOutlineLevel() != level.intValue();
332: boolean hiddenChanged = hidden != null
333: && ci.getHidden() != hidden.booleanValue();
334: boolean collapsedChanged = collapsed != null
335: && ci.getCollapsed() != collapsed.booleanValue();
336: boolean columnChanged = styleChanged || widthChanged
337: || levelChanged || hiddenChanged
338: || collapsedChanged;
339: if (!columnChanged) {
340: // do nothing...nothing changed.
341: } else if ((ci.getFirstColumn() == column)
342: && (ci.getLastColumn() == column)) { // if its only for this cell then
343: setColumnInfoFields(ci, xfIndex, width, level, hidden,
344: collapsed);
345: } else if ((ci.getFirstColumn() == column)
346: || (ci.getLastColumn() == column)) {
347: // okay so the width is different but the first or last column == the column we'return setting
348: // we'll just divide the info and create a new one
349: if (ci.getFirstColumn() == column) {
350: ci.setFirstColumn((short) (column + 1));
351: } else {
352: ci.setLastColumn((short) (column - 1));
353: }
354: ColumnInfoRecord nci = (ColumnInfoRecord) createColInfo();
355:
356: nci.setFirstColumn(column);
357: nci.setLastColumn(column);
358: nci.setOptions(ci.getOptions());
359: nci.setXFIndex(ci.getXFIndex());
360: setColumnInfoFields(nci, xfIndex, width, level, hidden,
361: collapsed);
362:
363: insertColumn(k, nci);
364: } else {
365: //split to 3 records
366: short lastcolumn = ci.getLastColumn();
367: ci.setLastColumn((short) (column - 1));
368:
369: ColumnInfoRecord nci = (ColumnInfoRecord) createColInfo();
370: nci.setFirstColumn(column);
371: nci.setLastColumn(column);
372: nci.setOptions(ci.getOptions());
373: nci.setXFIndex(ci.getXFIndex());
374: setColumnInfoFields(nci, xfIndex, width, level, hidden,
375: collapsed);
376: insertColumn(++k, nci);
377:
378: nci = (ColumnInfoRecord) createColInfo();
379: nci.setFirstColumn((short) (column + 1));
380: nci.setLastColumn(lastcolumn);
381: nci.setOptions(ci.getOptions());
382: nci.setXFIndex(ci.getXFIndex());
383: nci.setColumnWidth(ci.getColumnWidth());
384: insertColumn(++k, nci);
385: }
386: } else {
387:
388: // okay so there ISN'T a column info record that cover's this column so lets create one!
389: ColumnInfoRecord nci = (ColumnInfoRecord) createColInfo();
390:
391: nci.setFirstColumn(column);
392: nci.setLastColumn(column);
393: setColumnInfoFields(nci, xfIndex, width, level, hidden,
394: collapsed);
395: insertColumn(k, nci);
396: }
397: }
398:
399: /**
400: * Sets all non null fields into the <code>ci</code> parameter.
401: */
402: private void setColumnInfoFields(ColumnInfoRecord ci,
403: Short xfStyle, Short width, Integer level, Boolean hidden,
404: Boolean collapsed) {
405: if (xfStyle != null)
406: ci.setXFIndex(xfStyle.shortValue());
407: if (width != null)
408: ci.setColumnWidth(width.shortValue());
409: if (level != null)
410: ci.setOutlineLevel(level.shortValue());
411: if (hidden != null)
412: ci.setHidden(hidden.booleanValue());
413: if (collapsed != null)
414: ci.setCollapsed(collapsed.booleanValue());
415: }
416:
417: public int findColumnIdx(int column, int fromIdx) {
418: if (column < 0)
419: throw new IllegalArgumentException(
420: "column parameter out of range: " + column);
421: if (fromIdx < 0)
422: throw new IllegalArgumentException(
423: "fromIdx parameter out of range: " + fromIdx);
424:
425: ColumnInfoRecord ci;
426: for (int k = fromIdx; k < records.size(); k++) {
427: ci = (ColumnInfoRecord) records.get(k);
428: if ((ci.getFirstColumn() <= column)
429: && (column <= ci.getLastColumn())) {
430: return k;
431: }
432: ci = null;
433: }
434: return -1;
435: }
436:
437: public void collapseColInfoRecords(int columnIdx) {
438: if (columnIdx == 0)
439: return;
440: ColumnInfoRecord previousCol = (ColumnInfoRecord) records
441: .get(columnIdx - 1);
442: ColumnInfoRecord currentCol = (ColumnInfoRecord) records
443: .get(columnIdx);
444: boolean adjacentColumns = previousCol.getLastColumn() == currentCol
445: .getFirstColumn() - 1;
446: if (!adjacentColumns)
447: return;
448:
449: boolean columnsMatch = previousCol.getXFIndex() == currentCol
450: .getXFIndex()
451: && previousCol.getOptions() == currentCol.getOptions()
452: && previousCol.getColumnWidth() == currentCol
453: .getColumnWidth();
454:
455: if (columnsMatch) {
456: previousCol.setLastColumn(currentCol.getLastColumn());
457: records.remove(columnIdx);
458: }
459: }
460:
461: /**
462: * Creates an outline group for the specified columns.
463: * @param fromColumn group from this column (inclusive)
464: * @param toColumn group to this column (inclusive)
465: * @param indent if true the group will be indented by one level,
466: * if false indenting will be removed by one level.
467: */
468: public void groupColumnRange(short fromColumn, short toColumn,
469: boolean indent) {
470:
471: // Set the level for each column
472: int fromIdx = 0;
473: for (int i = fromColumn; i <= toColumn; i++) {
474: int level = 1;
475: int columnIdx = findColumnIdx(i, Math.max(0, fromIdx));
476: if (columnIdx != -1) {
477: level = ((ColumnInfoRecord) records.get(columnIdx))
478: .getOutlineLevel();
479: if (indent)
480: level++;
481: else
482: level--;
483: level = Math.max(0, level);
484: level = Math.min(7, level);
485: fromIdx = columnIdx - 1; // subtract 1 just in case this column is collapsed later.
486: }
487: setColumn((short) i, null, null, new Integer(level), null,
488: null);
489: columnIdx = findColumnIdx(i, Math.max(0, fromIdx));
490: collapseColInfoRecords(columnIdx);
491: }
492:
493: }
494:
495: }
|