/*********************************************************************
*
* Copyright (C) 2002 Andrew Khan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
***************************************************************************/
package jxl.write.biff;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import jxl.common.Logger;
import jxl.CellType;
import jxl.SheetSettings;
import jxl.WorkbookSettings;
import jxl.biff.CellReferenceHelper;
import jxl.biff.IndexMapping;
import jxl.biff.IntegerHelper;
import jxl.biff.Type;
import jxl.biff.WritableRecordData;
import jxl.biff.XFRecord;
import jxl.write.Number;
import jxl.write.WritableCellFeatures;
import jxl.write.WritableSheet;
/**
* Contains all the cells for a given row in a sheet
*/
class RowRecord extends WritableRecordData
{
/**
* The logger
*/
private static final Logger logger = Logger.getLogger(RowRecord.class);
/**
* The binary data
*/
private byte[] data;
/**
* The cells which comprise this row
*/
private CellValue[] cells;
/**
* The height of this row in 1/20ths of a point
*/
private int rowHeight;
/**
* Flag to indicate whether this row is outline collapsed or not
*/
private boolean collapsed;
/**
* The number of this row within the worksheet
*/
private int rowNumber;
/**
* The number of columns in this row. This is the largest column value + 1
*/
private int numColumns;
/**
* The xfIndex for this row
*/
private int xfIndex;
/**
* The style for this row
*/
private XFRecord style;
/**
* Flag indicating that this row record has an default format
*/
private boolean defaultFormat;
/**
* Flag indicating whether this row matches the default font height
*/
private boolean matchesDefFontHeight;
/**
* The amount to grow the cells array by
*/
private static final int growSize = 10;
/**
* The maximum integer value that can be squeezed into 30 bits
*/
private static final int maxRKValue = 0x1fffffff;
/**
* The minimum integer value that can be squeezed into 30 bits
*/
private static final int minRKValue = -0x20000000;
/**
* Indicates that the row is default height
*/
private static int defaultHeightIndicator = 0xff;
/**
* The maximum number of columns
*/
private static int maxColumns = 256;
/**
* The outline level of the row
*/
private int outlineLevel;
/**
* Is this the icon indicator row of a group?
*/
private boolean groupStart;
/**
* A handle back to the sheet
*/
private WritableSheet sheet;
/**
* Constructs an empty row which has the specified row number
*
* @param rn the row number of this row
*/
public RowRecord(int rn, WritableSheet ws)
{
super(Type.ROW);
rowNumber = rn;
cells = new CellValue[0];
numColumns = 0;
rowHeight = defaultHeightIndicator;
collapsed = false;
matchesDefFontHeight = true;
sheet = ws;
}
/**
* Sets the height of this row
*
* @param h the row height
*/
public void setRowHeight(int h)
{
if (h == 0)
{
setCollapsed(true);
matchesDefFontHeight = false;
}
else
{
rowHeight = h;
matchesDefFontHeight = false;
}
}
/**
* Sets the row details based upon the readable row record passed in
* Called when copying spreadsheets
*
* @param height the height of the row record in 1/20ths of a point
* @param mdfh matches the default font height
* @param col the collapsed status of the row
* @param ol the outline level
* @param gs the group start
* @param xf the xfrecord for the row (NULL if no default is set)
*/
void setRowDetails(int height,
boolean mdfh,
boolean col,
int ol,
boolean gs,
XFRecord xfr)
{
rowHeight = height;
collapsed = col;
matchesDefFontHeight = mdfh;
outlineLevel = ol;
groupStart = gs;
if (xfr != null)
{
defaultFormat = true;
style = xfr;
xfIndex = style.getXFIndex();
}
}
/**
* Sets the collapsed status of this row
*
* @param c the collapsed flag
*/
public void setCollapsed(boolean c)
{
collapsed = c;
}
/**
* Gets the row number of this row
*
* @return the row number
*/
public int getRowNumber()
{
return rowNumber;
}
/**
* Adds a cell to this row, growing the array of cells as required
*
* @param cv the cell to add
*/
public void addCell(CellValue cv)
{
int col = cv.getColumn();
if (col >= maxColumns)
{
logger.warn("Could not add cell at " +
CellReferenceHelper.getCellReference(cv.getRow(),
cv.getColumn()) +
" because it exceeds the maximum column limit");
return;
}
// Grow the array if needs be
if (col >= cells.length)
{
CellValue[] oldCells = cells;
cells = new CellValue[Math.max(oldCells.length + growSize, col+1)];
System.arraycopy(oldCells, 0, cells, 0, oldCells.length);
oldCells = null;
}
// Remove any cell features from the cell being replaced
if (cells[col] != null)
{
WritableCellFeatures wcf = cells[col].getWritableCellFeatures();
if (wcf != null)
{
wcf.removeComment();
// if the cell is part of a shared data validation,then don't remove
// the validation
if (wcf.getDVParser() != null &&
!wcf.getDVParser().extendedCellsValidation())
{
wcf.removeDataValidation();
}
}
}
cells[col] = cv;
numColumns = Math.max(col+1, numColumns);
}
/**
* Removes a cell from this row
*
* @param col the column at which to remove the cell
*/
public void removeCell(int col)
{
// Grow the array if needs be
if (col >= numColumns)
{
return;
}
cells[col] = null;
}
/**
* Writes out the row information data (but not the individual cells)
*
* @exception IOException
* @param outputFile the output file
*/
public void write(File outputFile) throws IOException
{
outputFile.write(this);
}
/**
* Writes out all the cells in this row. If more than three integer
* values occur consecutively, then a MulRK record is used to group the
* numbers
*
* @exception IOException
* @param outputFile the output file
*/
public void writeCells(File outputFile)
throws IOException
{
// This is the list for integer values
ArrayList integerValues = new ArrayList();
boolean integerValue = false;
// Write out all the records
for (int i = 0; i < numColumns; i++)
{
integerValue = false;
if (cells[i] != null)
{
// See if this cell is a 30-bit integer value (without additional
// cell features)
if (cells[i].getType() == CellType.NUMBER)
{
Number nc = (Number) cells[i];
if (nc.getValue() == (int) nc.getValue() &&
nc.getValue() < maxRKValue &&
nc.getValue() > minRKValue &&
nc.getCellFeatures() == null)
{
integerValue = true;
}
}
if (integerValue)
{
// This cell is an integer, add it to the list
integerValues.add(cells[i]);
}
else
{
// This cell is not an integer. Write out whatever integers we
// have, and then write out this cell
writeIntegerValues(integerValues, outputFile);
outputFile.write(cells[i]);
// If the cell is a string formula, write out the string record
// immediately afterwards
if (cells[i].getType() == CellType.STRING_FORMULA)
{
StringRecord sr = new StringRecord(cells[i].getContents());
outputFile.write(sr);
}
}
}
else
{
// Cell does not exist. Write out the list of integers that
// we have
writeIntegerValues(integerValues, outputFile);
}
}
// All done. Write out any remaining integer values
writeIntegerValues(integerValues, outputFile);
}
/**
* Writes out the list of integer values. If there are more than three,
* a MulRK record is used, otherwise a sequence of Numbers is used
*
* @exception IOException
* @param outputFile the output file
* @param integerValues the array of integer values
*/
private void writeIntegerValues(ArrayList integerValues, File outputFile)
throws IOException
{
if (integerValues.size() == 0)
{
return;
}
if (integerValues.size() >= 3 )
{
// Write out as a MulRK record
MulRKRecord mulrk = new MulRKRecord(integerValues);
outputFile.write(mulrk);
}
else
{
// Write out as number records
Iterator i = integerValues.iterator();
while (i.hasNext())
{
outputFile.write((CellValue) i.next());
}
}
// Clear out the list of integerValues
integerValues.clear();
}
/**
* Gets the row data to output to file
*
* @return the binary data
*/
public byte[] getData()
{
// Write out the row record
byte[] data = new byte[16];
// If the default row height has been changed in the sheet settings,
// then we need to set the rowHeight on this row explicitly, as
// specifying the "match default" flag doesn't work
int rh = rowHeight;
if (sheet.getSettings().getDefaultRowHeight() !=
SheetSettings.DEFAULT_DEFAULT_ROW_HEIGHT)
{
// the default row height has been changed. If this row does not
// have a specific row height set on it, then set it to the default
if (rh == defaultHeightIndicator)
{
rh = sheet.getSettings().getDefaultRowHeight();
}
}
IntegerHelper.getTwoBytes(rowNumber, data, 0);
IntegerHelper.getTwoBytes(numColumns, data, 4);
IntegerHelper.getTwoBytes(rh, data, 6);
int options = 0x100 + outlineLevel;
if (groupStart)
{
options |= 0x10;
}
if (collapsed)
{
options |= 0x20;
}
if (!matchesDefFontHeight)
{
options |= 0x40;
}
if (defaultFormat)
{
options |= 0x80;
options |= (xfIndex << 16);
}
IntegerHelper.getFourBytes(options, data, 12);
return data;
}
/**
* Gets the maximum column value which occurs in this row
*
* @return the maximum column value
*/
public int getMaxColumn()
{
return numColumns;
}
/**
* Gets the cell which occurs at the specified column value
*
* @param col the colun for which to return the cell
* @return the cell value at the specified position, or null if the column
* is invalid
*/
public CellValue getCell(int col)
{
return (col >= 0 && col < numColumns) ? cells[col] : null;
}
/**
* Increments the row of this cell by one. Invoked by the sheet when
* inserting rows
*/
void incrementRow()
{
rowNumber++;
for (int i = 0; i < cells.length; i++)
{
if (cells[i] != null)
{
cells[i].incrementRow();
}
}
}
/**
* Decrements the row of this cell by one. Invoked by the sheet when
* removing rows
*/
void decrementRow()
{
rowNumber--;
for (int i = 0; i < cells.length; i++)
{
if (cells[i] != null)
{
cells[i].decrementRow();
}
}
}
/**
* Inserts a new column at the position specified. If the max column length
* is already reached, then the last column simply gets dropped
*
* @param col the column to insert
*/
void insertColumn(int col)
{
// Don't bother doing anything unless there are cells after the
// column to be inserted
if (col >= numColumns)
{
return;
}
// Create a new array to hold the new column. Grow it if need be
CellValue[] oldCells = cells;
if (numColumns >= cells.length - 1)
{
cells = new CellValue[oldCells.length + growSize];
}
else
{
cells = new CellValue[oldCells.length];
}
// Copy in everything up to the new column
System.arraycopy(oldCells, 0, cells, 0, col);
// Copy in the remaining cells
System.arraycopy(oldCells, col, cells, col+1, numColumns - col);
// Increment all the internal column numbers by one
for (int i = col+1; i <= numColumns; i++)
{
if (cells[i] != null)
{
cells[i].incrementColumn();
}
}
// Adjust the maximum column record
numColumns = Math.min(numColumns+1, maxColumns);
}
/**
* Remove the new column at the position specified
*
* @param col the column to remove
*/
void removeColumn(int col)
{
// Don't bother doing anything unless there are cells after the
// column to be inserted
if (col >= numColumns)
{
return;
}
// Create a new array to hold the new columns
CellValue[] oldCells = cells;
cells = new CellValue[oldCells.length];
// Copy in everything up to the column
System.arraycopy(oldCells, 0, cells, 0, col);
// Copy in the remaining cells after the column
System.arraycopy(oldCells, col + 1, cells, col, numColumns - (col+1));
// Decrement all the internal column numbers by one
for (int i = col; i < numColumns; i++)
{
if (cells[i] != null)
{
cells[i].decrementColumn();
}
}
// Adjust the maximum column record
numColumns--;
}
/**
* Interrogates whether this row is of default height
*
* @return TRUE if this is set to the default height, FALSE otherwise
*/
public boolean isDefaultHeight()
{
return rowHeight == defaultHeightIndicator;
}
/**
* Gets the height of the row
*
* @return the row height
*/
public int getRowHeight()
{
return rowHeight;
}
/**
* Queries whether the row is collapsed
*
* @return the collapsed indicator
*/
public boolean isCollapsed()
{
return collapsed;
}
/**
* Rationalizes the sheets xf index mapping
* @param xfmapping the index mapping
*/
void rationalize(IndexMapping xfmapping)
{
if (defaultFormat)
{
xfIndex = xfmapping.getNewIndex(xfIndex);
}
}
/**
* Accessor for the style. The returned value is only non-null if the
* default style is overridden
*
* @return the style
*/
XFRecord getStyle()
{
return style;
}
/**
* Accessor for the default format flag
*
* @return TRUE if this row has its own default format
*/
boolean hasDefaultFormat()
{
return defaultFormat;
}
/**
* Accessor for the matches default font height flag
*
* @return TRUE if this row matches the default font height
*/
boolean matchesDefaultFontHeight()
{
return matchesDefFontHeight;
}
/**
* Accessor for the column's outline level
*
* @return the column's outline level
*/
public int getOutlineLevel()
{
return outlineLevel;
}
/**
* Accessor for row's groupStart state
*
* @return the row's groupStart state
*/
public boolean getGroupStart()
{
return groupStart;
}
/**
* Increments the row's outline level. This is how groups are made as well
*/
public void incrementOutlineLevel()
{
outlineLevel++;
}
/**
* Decrements the row's outline level. This removes it from a grouping
* level. If
* all outline levels are gone the uncollapse the row.
*/
public void decrementOutlineLevel()
{
if (0 < outlineLevel)
{
outlineLevel--;
}
if (0==outlineLevel)
{
collapsed = false;
}
}
/**
* Sets the row's outline level
*
* @param level the row's outline level
*/
public void setOutlineLevel(int level)
{
outlineLevel = level;
}
/**
* Sets the row's group start state
*
* @param value the group start state
*/
public void setGroupStart(boolean value)
{
groupStart = value;
}
}
|