1 /* ====================================================================
2 Licensed to the Apache Software Foundation (ASF) under one or more
3 contributor license agreements. See the NOTICE file distributed with
4 this work for additional information regarding copyright ownership.
5 The ASF licenses this file to You under the Apache License, Version 2.0
6 (the "License"); you may not use this file except in compliance with
7 the License. You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 ==================================================================== */
17
18 package org.apache.poi.hssf.usermodel;
19
20 import java.awt.font.FontRenderContext;
21 import java.awt.font.TextAttribute;
22 import java.awt.font.TextLayout;
23 import java.awt.geom.AffineTransform;
24 import java.io.PrintWriter;
25 import java.text.AttributedString;
26 import java.text.DecimalFormat;
27 import java.text.NumberFormat;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Stack;
32 import java.util.TreeMap;
33
34 import org.apache.poi.ddf.EscherRecord;
35 import org.apache.poi.hssf.model.FormulaParser;
36 import org.apache.poi.hssf.model.Sheet;
37 import org.apache.poi.hssf.model.Workbook;
38 import org.apache.poi.hssf.record;
39 import org.apache.poi.hssf.record.formula.Ptg;
40 import org.apache.poi.hssf.record.formula.RefPtg;
41 import org.apache.poi.hssf.util.HSSFCellRangeAddress;
42 import org.apache.poi.hssf.util.HSSFDataValidation;
43 import org.apache.poi.hssf.util.PaneInformation;
44 import org.apache.poi.hssf.util.Region;
45 import org.apache.poi.util.POILogFactory;
46 import org.apache.poi.util.POILogger;
47
48 /**
49 * High level representation of a worksheet.
50 * @author Andrew C. Oliver (acoliver at apache dot org)
51 * @author Glen Stampoultzis (glens at apache.org)
52 * @author Libin Roman (romal at vistaportal.com)
53 * @author Shawn Laubach (slaubach at apache dot org) (Just a little)
54 * @author Jean-Pierre Paris (jean-pierre.paris at m4x dot org) (Just a little, too)
55 * @author Yegor Kozlov (yegor at apache.org) (Autosizing columns)
56 */
57 public final class HSSFSheet {
58 private static final int DEBUG = POILogger.DEBUG;
59
60 /* Constants for margins */
61 public static final short LeftMargin = Sheet.LeftMargin;
62 public static final short RightMargin = Sheet.RightMargin;
63 public static final short TopMargin = Sheet.TopMargin;
64 public static final short BottomMargin = Sheet.BottomMargin;
65
66 public static final byte PANE_LOWER_RIGHT = (byte)0;
67 public static final byte PANE_UPPER_RIGHT = (byte)1;
68 public static final byte PANE_LOWER_LEFT = (byte)2;
69 public static final byte PANE_UPPER_LEFT = (byte)3;
70
71
72 /**
73 * Used for compile-time optimization. This is the initial size for the collection of
74 * rows. It is currently set to 20. If you generate larger sheets you may benefit
75 * by setting this to a higher number and recompiling a custom edition of HSSFSheet.
76 */
77
78 public final static int INITIAL_CAPACITY = 20;
79
80 /**
81 * reference to the low level Sheet object
82 */
83
84 private Sheet sheet;
85 private TreeMap rows;
86 protected Workbook book;
87 protected HSSFWorkbook workbook;
88 private int firstrow;
89 private int lastrow;
90 private static POILogger log = POILogFactory.getLogger(HSSFSheet.class);
91
92 /**
93 * Creates new HSSFSheet - called by HSSFWorkbook to create a sheet from
94 * scratch. You should not be calling this from application code (its protected anyhow).
95 *
96 * @param workbook - The HSSF Workbook object associated with the sheet.
97 * @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createSheet()
98 */
99
100 protected HSSFSheet(HSSFWorkbook workbook)
101 {
102 sheet = Sheet.createSheet();
103 rows = new TreeMap(); // new ArrayList(INITIAL_CAPACITY);
104 this.workbook = workbook;
105 this.book = workbook.getWorkbook();
106 }
107
108 /**
109 * Creates an HSSFSheet representing the given Sheet object. Should only be
110 * called by HSSFWorkbook when reading in an exisiting file.
111 *
112 * @param workbook - The HSSF Workbook object associated with the sheet.
113 * @param sheet - lowlevel Sheet object this sheet will represent
114 * @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createSheet()
115 */
116
117 protected HSSFSheet(HSSFWorkbook workbook, Sheet sheet)
118 {
119 this.sheet = sheet;
120 rows = new TreeMap();
121 this.workbook = workbook;
122 this.book = workbook.getWorkbook();
123 setPropertiesFromSheet(sheet);
124 }
125
126 HSSFSheet cloneSheet(HSSFWorkbook workbook) {
127 return new HSSFSheet(workbook, sheet.cloneSheet());
128 }
129
130
131 /**
132 * used internally to set the properties given a Sheet object
133 */
134
135 private void setPropertiesFromSheet(Sheet sheet)
136 {
137 int sloc = sheet.getLoc();
138 RowRecord row = sheet.getNextRow();
139 boolean rowRecordsAlreadyPresent = row!=null;
140
141 while (row != null)
142 {
143 createRowFromRecord(row);
144
145 row = sheet.getNextRow();
146 }
147 sheet.setLoc(sloc);
148 CellValueRecordInterface cval = sheet.getNextValueRecord();
149 long timestart = System.currentTimeMillis();
150
151 if (log.check( POILogger.DEBUG ))
152 log.log(DEBUG, "Time at start of cell creating in HSSF sheet = ",
153 new Long(timestart));
154 HSSFRow lastrow = null;
155
156 while (cval != null)
157 {
158 long cellstart = System.currentTimeMillis();
159 HSSFRow hrow = lastrow;
160
161 if ( ( lastrow == null ) || ( lastrow.getRowNum() != cval.getRow() ) )
162 {
163 hrow = getRow( cval.getRow() );
164 if (hrow == null) {
165 // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords
166 // Excel, OpenOffice.org and GoogleDocs are all OK with this, so POI should be too.
167 if (rowRecordsAlreadyPresent) {
168 // if at least one row record is present, all should be present.
169 throw new RuntimeException("Unexpected missing row when some rows already present");
170 }
171 // create the row record on the fly now.
172 RowRecord rowRec = new RowRecord(cval.getRow());
173 sheet.addRow(rowRec);
174 hrow = createRowFromRecord(rowRec);
175 }
176 }
177 if ( hrow != null )
178 {
179 lastrow = hrow;
180 if (log.check( POILogger.DEBUG ))
181 log.log( DEBUG, "record id = " + Integer.toHexString( ( (Record) cval ).getSid() ) );
182 hrow.createCellFromRecord( cval );
183 cval = sheet.getNextValueRecord();
184 if (log.check( POILogger.DEBUG ))
185 log.log( DEBUG, "record took ",
186 new Long( System.currentTimeMillis() - cellstart ) );
187 }
188 else
189 {
190 cval = null;
191 }
192 }
193 if (log.check( POILogger.DEBUG ))
194 log.log(DEBUG, "total sheet cell creation took ",
195 new Long(System.currentTimeMillis() - timestart));
196 }
197
198 /**
199 * Create a new row within the sheet and return the high level representation
200 *
201 * @param rownum row number
202 * @return High level HSSFRow object representing a row in the sheet
203 * @see org.apache.poi.hssf.usermodel.HSSFRow
204 * @see #removeRow(HSSFRow)
205 */
206 public HSSFRow createRow(int rownum)
207 {
208 HSSFRow row = new HSSFRow(workbook, sheet, rownum);
209
210 addRow(row, true);
211 return row;
212 }
213
214 /**
215 * Used internally to create a high level Row object from a low level row object.
216 * USed when reading an existing file
217 * @param row low level record to represent as a high level Row and add to sheet
218 * @return HSSFRow high level representation
219 */
220
221 private HSSFRow createRowFromRecord(RowRecord row)
222 {
223 HSSFRow hrow = new HSSFRow(workbook, sheet, row);
224
225 addRow(hrow, false);
226 return hrow;
227 }
228
229 /**
230 * Remove a row from this sheet. All cells contained in the row are removed as well
231 *
232 * @param row representing a row to remove.
233 */
234
235 public void removeRow(HSSFRow row)
236 {
237 sheet.setLoc(sheet.getDimsLoc());
238 if (rows.size() > 0)
239 {
240 rows.remove(row);
241 if (row.getRowNum() == getLastRowNum())
242 {
243 lastrow = findLastRow(lastrow);
244 }
245 if (row.getRowNum() == getFirstRowNum())
246 {
247 firstrow = findFirstRow(firstrow);
248 }
249 Iterator iter = row.cellIterator();
250
251 while (iter.hasNext())
252 {
253 HSSFCell cell = (HSSFCell) iter.next();
254
255 sheet.removeValueRecord(row.getRowNum(),
256 cell.getCellValueRecord());
257 }
258 sheet.removeRow(row.getRowRecord());
259 }
260 }
261
262 /**
263 * used internally to refresh the "last row" when the last row is removed.
264 */
265
266 private int findLastRow(int lastrow)
267 {
268 int rownum = lastrow - 1;
269 HSSFRow r = getRow(rownum);
270
271 while (r == null && rownum > 0)
272 {
273 r = getRow(--rownum);
274 }
275 if (r == null)
276 return -1;
277 return rownum;
278 }
279
280 /**
281 * used internally to refresh the "first row" when the first row is removed.
282 */
283
284 private int findFirstRow(int firstrow)
285 {
286 int rownum = firstrow + 1;
287 HSSFRow r = getRow(rownum);
288
289 while (r == null && rownum <= getLastRowNum())
290 {
291 r = getRow(++rownum);
292 }
293
294 if (rownum > getLastRowNum())
295 return -1;
296
297 return rownum;
298 }
299
300 /**
301 * add a row to the sheet
302 *
303 * @param addLow whether to add the row to the low level model - false if its already there
304 */
305
306 private void addRow(HSSFRow row, boolean addLow)
307 {
308 rows.put(row, row);
309 if (addLow)
310 {
311 sheet.addRow(row.getRowRecord());
312 }
313 if (row.getRowNum() > getLastRowNum())
314 {
315 lastrow = row.getRowNum();
316 }
317 if (row.getRowNum() < getFirstRowNum())
318 {
319 firstrow = row.getRowNum();
320 }
321 }
322
323 /**
324 * Returns the logical row (not physical) 0-based. If you ask for a row that is not
325 * defined you get a null. This is to say row 4 represents the fifth row on a sheet.
326 * @param rownum row to get
327 * @return HSSFRow representing the rownumber or null if its not defined on the sheet
328 */
329
330 public HSSFRow getRow(int rownum)
331 {
332 HSSFRow row = new HSSFRow();
333
334 //row.setRowNum((short) rownum);
335 row.setRowNum( rownum);
336 return (HSSFRow) rows.get(row);
337 }
338
339 /**
340 * Returns the number of phsyically defined rows (NOT the number of rows in the sheet)
341 */
342
343 public int getPhysicalNumberOfRows()
344 {
345 return rows.size();
346 }
347
348 /**
349 * gets the first row on the sheet
350 * @return the number of the first logical row on the sheet
351 */
352
353 public int getFirstRowNum()
354 {
355 return firstrow;
356 }
357
358 /**
359 * gets the last row on the sheet
360 * @return last row contained n this sheet.
361 */
362
363 public int getLastRowNum()
364 {
365 return lastrow;
366 }
367
368 /**
369 * Creates a data validation object
370 * @param obj_validation The Data validation object settings
371 */
372 public void addValidationData(HSSFDataValidation obj_validation)
373 {
374 if ( obj_validation == null )
375 {
376 return;
377 }
378 DVALRecord dvalRec = (DVALRecord)sheet.findFirstRecordBySid( DVALRecord.sid );
379 int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid );
380 if ( dvalRec == null )
381 {
382 dvalRec = new DVALRecord();
383 sheet.getRecords().add( eofLoc, dvalRec );
384 }
385 int curr_dvRecNo = dvalRec.getDVRecNo();
386 dvalRec.setDVRecNo(curr_dvRecNo+1);
387
388 //create dv record
389 DVRecord dvRecord = new DVRecord();
390
391 //dv record's option flags
392 dvRecord.setDataType( obj_validation.getDataValidationType() );
393 dvRecord.setErrorStyle(obj_validation.getErrorStyle());
394 dvRecord.setEmptyCellAllowed(obj_validation.getEmptyCellAllowed());
395 dvRecord.setSurppresDropdownArrow(obj_validation.getSurppressDropDownArrow());
396 dvRecord.setShowPromptOnCellSelected(obj_validation.getShowPromptBox());
397 dvRecord.setShowErrorOnInvalidValue(obj_validation.getShowErrorBox());
398 dvRecord.setConditionOperator(obj_validation.getOperator());
399
400 //string fields
401 dvRecord.setStringField( DVRecord.STRING_PROMPT_TITLE,obj_validation.getPromptBoxTitle());
402 dvRecord.setStringField( DVRecord.STRING_PROMPT_TEXT, obj_validation.getPromptBoxText());
403 dvRecord.setStringField( DVRecord.STRING_ERROR_TITLE, obj_validation.getErrorBoxTitle());
404 dvRecord.setStringField( DVRecord.STRING_ERROR_TEXT, obj_validation.getErrorBoxText());
405
406 //formula fields ( size and data )
407 String str_formula = obj_validation.getFirstFormula();
408 FormulaParser fp = new FormulaParser(str_formula, workbook);
409 fp.parse();
410 Stack ptg_arr = new Stack();
411 Ptg[] ptg = fp.getRPNPtg();
412 int size = 0;
413 for (int k = 0; k < ptg.length; k++)
414 {
415 if ( ptg[k] instanceof org.apache.poi.hssf.record.formula.AreaPtg )
416 {
417 //we should set ptgClass to Ptg.CLASS_REF and explicit formula string to false
418 ptg[k].setClass(Ptg.CLASS_REF);
419 obj_validation.setExplicitListFormula(false);
420 }
421 size += ptg[k].getSize();
422 ptg_arr.push(ptg[k]);
423 }
424 dvRecord.setFirstFormulaRPN(ptg_arr);
425 dvRecord.setFirstFormulaSize((short)size);
426
427 dvRecord.setListExplicitFormula(obj_validation.getExplicitListFormula());
428
429 if ( obj_validation.getSecondFormula() != null )
430 {
431 str_formula = obj_validation.getSecondFormula();
432 fp = new FormulaParser(str_formula, workbook);
433 fp.parse();
434 ptg_arr = new Stack();
435 ptg = fp.getRPNPtg();
436 size = 0;
437 for (int k = 0; k < ptg.length; k++)
438 {
439 size += ptg[k].getSize();
440 ptg_arr.push(ptg[k]);
441 }
442 dvRecord.setSecFormulaRPN(ptg_arr);
443 dvRecord.setSecFormulaSize((short)size);
444 }
445
446 //dv records cell range field
447 HSSFCellRangeAddress cell_range = new HSSFCellRangeAddress();
448 cell_range.addADDRStructure(obj_validation.getFirstRow(), obj_validation.getFirstColumn(), obj_validation.getLastRow(), obj_validation.getLastColumn());
449 dvRecord.setCellRangeAddress(cell_range);
450
451 //add dv record
452 eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid );
453 sheet.getRecords().add( eofLoc, dvRecord );
454 }
455
456 /**
457 * Get the visibility state for a given column.
458 * @param column - the column to get (0-based)
459 * @param hidden - the visiblity state of the column
460 */
461
462 public void setColumnHidden(short column, boolean hidden)
463 {
464 sheet.setColumnHidden(column, hidden);
465 }
466
467 /**
468 * Get the hidden state for a given column.
469 * @param column - the column to set (0-based)
470 * @return hidden - the visiblity state of the column
471 */
472
473 public boolean isColumnHidden(short column)
474 {
475 return sheet.isColumnHidden(column);
476 }
477
478 /**
479 * set the width (in units of 1/256th of a character width)
480 * @param column - the column to set (0-based)
481 * @param width - the width in units of 1/256th of a character width
482 */
483
484 public void setColumnWidth(short column, short width)
485 {
486 sheet.setColumnWidth(column, width);
487 }
488
489 /**
490 * get the width (in units of 1/256th of a character width )
491 * @param column - the column to set (0-based)
492 * @return width - the width in units of 1/256th of a character width
493 */
494
495 public short getColumnWidth(short column)
496 {
497 return sheet.getColumnWidth(column);
498 }
499
500 /**
501 * get the default column width for the sheet (if the columns do not define their own width) in
502 * characters
503 * @return default column width
504 */
505
506 public short getDefaultColumnWidth()
507 {
508 return sheet.getDefaultColumnWidth();
509 }
510
511 /**
512 * get the default row height for the sheet (if the rows do not define their own height) in
513 * twips (1/20 of a point)
514 * @return default row height
515 */
516
517 public short getDefaultRowHeight()
518 {
519 return sheet.getDefaultRowHeight();
520 }
521
522 /**
523 * get the default row height for the sheet (if the rows do not define their own height) in
524 * points.
525 * @return default row height in points
526 */
527
528 public float getDefaultRowHeightInPoints()
529 {
530 return (sheet.getDefaultRowHeight() / 20);
531 }
532
533 /**
534 * set the default column width for the sheet (if the columns do not define their own width) in
535 * characters
536 * @param width default column width
537 */
538
539 public void setDefaultColumnWidth(short width)
540 {
541 sheet.setDefaultColumnWidth(width);
542 }
543
544 /**
545 * set the default row height for the sheet (if the rows do not define their own height) in
546 * twips (1/20 of a point)
547 * @param height default row height
548 */
549
550 public void setDefaultRowHeight(short height)
551 {
552 sheet.setDefaultRowHeight(height);
553 }
554
555 /**
556 * set the default row height for the sheet (if the rows do not define their own height) in
557 * points
558 * @param height default row height
559 */
560
561 public void setDefaultRowHeightInPoints(float height)
562 {
563 sheet.setDefaultRowHeight((short) (height * 20));
564 }
565
566 /**
567 * get whether gridlines are printed.
568 * @return true if printed
569 */
570
571 public boolean isGridsPrinted()
572 {
573 return sheet.isGridsPrinted();
574 }
575
576 /**
577 * set whether gridlines printed.
578 * @param value false if not printed.
579 */
580
581 public void setGridsPrinted(boolean value)
582 {
583 sheet.setGridsPrinted(value);
584 }
585
586 /**
587 * adds a merged region of cells (hence those cells form one)
588 * @param region (rowfrom/colfrom-rowto/colto) to merge
589 * @return index of this region
590 */
591
592 public int addMergedRegion(Region region)
593 {
594 //return sheet.addMergedRegion((short) region.getRowFrom(),
595 return sheet.addMergedRegion( region.getRowFrom(),
596 region.getColumnFrom(),
597 //(short) region.getRowTo(),
598 region.getRowTo(),
599 region.getColumnTo());
600 }
601
602 /**
603 * Whether a record must be inserted or not at generation to indicate that
604 * formula must be recalculated when workbook is opened.
605 * @param value true if an uncalced record must be inserted or not at generation
606 */
607 public void setForceFormulaRecalculation(boolean value)
608 {
609 sheet.setUncalced(value);
610 }
611 /**
612 * Whether a record must be inserted or not at generation to indicate that
613 * formula must be recalculated when workbook is opened.
614 * @return true if an uncalced record must be inserted or not at generation
615 */
616 public boolean getForceFormulaRecalculation()
617 {
618 return sheet.getUncalced();
619 }
620
621
622 /**
623 * determines whether the output is vertically centered on the page.
624 * @param value true to vertically center, false otherwise.
625 */
626
627 public void setVerticallyCenter(boolean value)
628 {
629 VCenterRecord record =
630 (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid);
631
632 record.setVCenter(value);
633 }
634
635 /**
636 * TODO: Boolean not needed, remove after next release
637 * @deprecated use getVerticallyCenter() instead
638 */
639 public boolean getVerticallyCenter(boolean value) {
640 return getVerticallyCenter();
641 }
642
643 /**
644 * Determine whether printed output for this sheet will be vertically centered.
645 */
646 public boolean getVerticallyCenter()
647 {
648 VCenterRecord record =
649 (VCenterRecord) sheet.findFirstRecordBySid(VCenterRecord.sid);
650
651 return record.getVCenter();
652 }
653
654 /**
655 * determines whether the output is horizontally centered on the page.
656 * @param value true to horizontally center, false otherwise.
657 */
658
659 public void setHorizontallyCenter(boolean value)
660 {
661 HCenterRecord record =
662 (HCenterRecord) sheet.findFirstRecordBySid(HCenterRecord.sid);
663
664 record.setHCenter(value);
665 }
666
667 /**
668 * Determine whether printed output for this sheet will be horizontally centered.
669 */
670
671 public boolean getHorizontallyCenter()
672 {
673 HCenterRecord record =
674 (HCenterRecord) sheet.findFirstRecordBySid(HCenterRecord.sid);
675
676 return record.getHCenter();
677 }
678
679
680
681 /**
682 * removes a merged region of cells (hence letting them free)
683 * @param index of the region to unmerge
684 */
685
686 public void removeMergedRegion(int index)
687 {
688 sheet.removeMergedRegion(index);
689 }
690
691 /**
692 * returns the number of merged regions
693 * @return number of merged regions
694 */
695
696 public int getNumMergedRegions()
697 {
698 return sheet.getNumMergedRegions();
699 }
700
701 /**
702 * gets the region at a particular index
703 * @param index of the region to fetch
704 * @return the merged region (simple eh?)
705 */
706
707 public Region getMergedRegionAt(int index)
708 {
709 return new Region(sheet.getMergedRegionAt(index));
710 }
711
712 /**
713 * @return an iterator of the PHYSICAL rows. Meaning the 3rd element may not
714 * be the third row if say for instance the second row is undefined.
715 * Call getRowNum() on each row if you care which one it is.
716 */
717 public Iterator rowIterator()
718 {
719 return rows.values().iterator();
720 }
721 /**
722 * Alias for {@link #rowIterator()} to allow
723 * foreach loops
724 */
725 public Iterator iterator() {
726 return rowIterator();
727 }
728
729
730 /**
731 * used internally in the API to get the low level Sheet record represented by this
732 * Object.
733 * @return Sheet - low level representation of this HSSFSheet.
734 */
735
736 protected Sheet getSheet()
737 {
738 return sheet;
739 }
740
741 /**
742 * whether alternate expression evaluation is on
743 * @param b alternative expression evaluation or not
744 */
745
746 public void setAlternativeExpression(boolean b)
747 {
748 WSBoolRecord record =
749 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
750
751 record.setAlternateExpression(b);
752 }
753
754 /**
755 * whether alternative formula entry is on
756 * @param b alternative formulas or not
757 */
758
759 public void setAlternativeFormula(boolean b)
760 {
761 WSBoolRecord record =
762 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
763
764 record.setAlternateFormula(b);
765 }
766
767 /**
768 * show automatic page breaks or not
769 * @param b whether to show auto page breaks
770 */
771
772 public void setAutobreaks(boolean b)
773 {
774 WSBoolRecord record =
775 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
776
777 record.setAutobreaks(b);
778 }
779
780 /**
781 * set whether sheet is a dialog sheet or not
782 * @param b isDialog or not
783 */
784
785 public void setDialog(boolean b)
786 {
787 WSBoolRecord record =
788 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
789
790 record.setDialog(b);
791 }
792
793 /**
794 * set whether to display the guts or not
795 *
796 * @param b guts or no guts (or glory)
797 */
798
799 public void setDisplayGuts(boolean b)
800 {
801 WSBoolRecord record =
802 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
803
804 record.setDisplayGuts(b);
805 }
806
807 /**
808 * fit to page option is on
809 * @param b fit or not
810 */
811
812 public void setFitToPage(boolean b)
813 {
814 WSBoolRecord record =
815 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
816
817 record.setFitToPage(b);
818 }
819
820 /**
821 * set if row summaries appear below detail in the outline
822 * @param b below or not
823 */
824
825 public void setRowSumsBelow(boolean b)
826 {
827 WSBoolRecord record =
828 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
829
830 record.setRowSumsBelow(b);
831 }
832
833 /**
834 * set if col summaries appear right of the detail in the outline
835 * @param b right or not
836 */
837
838 public void setRowSumsRight(boolean b)
839 {
840 WSBoolRecord record =
841 (WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid);
842
843 record.setRowSumsRight(b);
844 }
845
846 /**
847 * whether alternate expression evaluation is on
848 * @return alternative expression evaluation or not
849 */
850
851 public boolean getAlternateExpression()
852 {
853 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
854 .getAlternateExpression();
855 }
856
857 /**
858 * whether alternative formula entry is on
859 * @return alternative formulas or not
860 */
861
862 public boolean getAlternateFormula()
863 {
864 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
865 .getAlternateFormula();
866 }
867
868 /**
869 * show automatic page breaks or not
870 * @return whether to show auto page breaks
871 */
872
873 public boolean getAutobreaks()
874 {
875 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
876 .getAutobreaks();
877 }
878
879 /**
880 * get whether sheet is a dialog sheet or not
881 * @return isDialog or not
882 */
883
884 public boolean getDialog()
885 {
886 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
887 .getDialog();
888 }
889
890 /**
891 * get whether to display the guts or not
892 *
893 * @return guts or no guts (or glory)
894 */
895
896 public boolean getDisplayGuts()
897 {
898 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
899 .getDisplayGuts();
900 }
901
902 /**
903 * fit to page option is on
904 * @return fit or not
905 */
906
907 public boolean getFitToPage()
908 {
909 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
910 .getFitToPage();
911 }
912
913 /**
914 * get if row summaries appear below detail in the outline
915 * @return below or not
916 */
917
918 public boolean getRowSumsBelow()
919 {
920 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
921 .getRowSumsBelow();
922 }
923
924 /**
925 * get if col summaries appear right of the detail in the outline
926 * @return right or not
927 */
928
929 public boolean getRowSumsRight()
930 {
931 return ((WSBoolRecord) sheet.findFirstRecordBySid(WSBoolRecord.sid))
932 .getRowSumsRight();
933 }
934
935 /**
936 * Returns whether gridlines are printed.
937 * @return Gridlines are printed
938 */
939 public boolean isPrintGridlines() {
940 return getSheet().getPrintGridlines().getPrintGridlines();
941 }
942
943 /**
944 * Turns on or off the printing of gridlines.
945 * @param newPrintGridlines boolean to turn on or off the printing of
946 * gridlines
947 */
948 public void setPrintGridlines( boolean newPrintGridlines )
949 {
950 getSheet().getPrintGridlines().setPrintGridlines( newPrintGridlines );
951 }
952
953 /**
954 * Gets the print setup object.
955 * @return The user model for the print setup object.
956 */
957 public HSSFPrintSetup getPrintSetup()
958 {
959 return new HSSFPrintSetup( getSheet().getPrintSetup() );
960 }
961
962 /**
963 * Gets the user model for the document header.
964 * @return The Document header.
965 */
966 public HSSFHeader getHeader()
967 {
968 return new HSSFHeader( getSheet().getHeader() );
969 }
970
971 /**
972 * Gets the user model for the document footer.
973 * @return The Document footer.
974 */
975 public HSSFFooter getFooter()
976 {
977 return new HSSFFooter( getSheet().getFooter() );
978 }
979
980 /**
981 * Note - this is not the same as whether the sheet is focused (isActive)
982 * @return <code>true</code> if this sheet is currently selected
983 */
984 public boolean isSelected() {
985 return getSheet().getWindowTwo().getSelected();
986 }
987 /**
988 * Sets whether sheet is selected.
989 * @param sel Whether to select the sheet or deselect the sheet.
990 */
991 public void setSelected( boolean sel )
992 {
993 getSheet().getWindowTwo().setSelected(sel);
994 }
995 /**
996 * @return <code>true</code> if this sheet is currently focused
997 */
998 public boolean isActive() {
999 return getSheet().getWindowTwo().isActive();
1000 }
1001 /**
1002 * Sets whether sheet is selected.
1003 * @param sel Whether to select the sheet or deselect the sheet.
1004 */
1005 public void setActive(boolean sel )
1006 {
1007 getSheet().getWindowTwo().setActive(sel);
1008 }
1009
1010 /**
1011 * Gets the size of the margin in inches.
1012 * @param margin which margin to get
1013 * @return the size of the margin
1014 */
1015 public double getMargin( short margin )
1016 {
1017 return getSheet().getMargin( margin );
1018 }
1019
1020 /**
1021 * Sets the size of the margin in inches.
1022 * @param margin which margin to get
1023 * @param size the size of the margin
1024 */
1025 public void setMargin( short margin, double size )
1026 {
1027 getSheet().setMargin( margin, size );
1028 }
1029
1030 /**
1031 * Answer whether protection is enabled or disabled
1032 * @return true => protection enabled; false => protection disabled
1033 */
1034 public boolean getProtect() {
1035 return getSheet().isProtected()[0];
1036 }
1037
1038 /**
1039 * @return hashed password
1040 */
1041 public short getPassword() {
1042 return getSheet().getPassword().getPassword();
1043 }
1044
1045 /**
1046 * Answer whether object protection is enabled or disabled
1047 * @return true => protection enabled; false => protection disabled
1048 */
1049 public boolean getObjectProtect() {
1050 return getSheet().isProtected()[1];
1051 }
1052
1053 /**
1054 * Answer whether scenario protection is enabled or disabled
1055 * @return true => protection enabled; false => protection disabled
1056 */
1057 public boolean getScenarioProtect() {
1058 return getSheet().isProtected()[2];
1059 }
1060
1061 /**
1062 * Sets the protection on enabled or disabled
1063 * @param protect true => protection enabled; false => protection disabled
1064 * @deprecated use protectSheet(String, boolean, boolean)
1065 */
1066 public void setProtect(boolean protect) {
1067 getSheet().getProtect().setProtect(protect);
1068 }
1069
1070 /**
1071 * Sets the protection enabled as well as the password
1072 * @param password to set for protection
1073 */
1074 public void protectSheet(String password) {
1075 getSheet().protectSheet(password, true, true); //protect objs&scenarios(normal)
1076 }
1077
1078 /**
1079 * Sets the zoom magnication for the sheet. The zoom is expressed as a
1080 * fraction. For example to express a zoom of 75% use 3 for the numerator
1081 * and 4 for the denominator.
1082 *
1083 * @param numerator The numerator for the zoom magnification.
1084 * @param denominator The denominator for the zoom magnification.
1085 */
1086 public void setZoom( int numerator, int denominator)
1087 {
1088 if (numerator < 1 || numerator > 65535)
1089 throw new IllegalArgumentException("Numerator must be greater than 1 and less than 65536");
1090 if (denominator < 1 || denominator > 65535)
1091 throw new IllegalArgumentException("Denominator must be greater than 1 and less than 65536");
1092
1093 SCLRecord sclRecord = new SCLRecord();
1094 sclRecord.setNumerator((short)numerator);
1095 sclRecord.setDenominator((short)denominator);
1096 getSheet().setSCLRecord(sclRecord);
1097 }
1098
1099 /**
1100 * The top row in the visible view when the sheet is
1101 * first viewed after opening it in a viewer
1102 * @return short indicating the rownum (0 based) of the top row
1103 */
1104 public short getTopRow()
1105 {
1106 return sheet.getTopRow();
1107 }
1108
1109 /**
1110 * The left col in the visible view when the sheet is
1111 * first viewed after opening it in a viewer
1112 * @return short indicating the rownum (0 based) of the top row
1113 */
1114 public short getLeftCol()
1115 {
1116 return sheet.getLeftCol();
1117 }
1118
1119 /**
1120 * Sets desktop window pane display area, when the
1121 * file is first opened in a viewer.
1122 * @param toprow the top row to show in desktop window pane
1123 * @param leftcol the left column to show in desktop window pane
1124 */
1125 public void showInPane(short toprow, short leftcol){
1126 this.sheet.setTopRow(toprow);
1127 this.sheet.setLeftCol(leftcol);
1128 }
1129
1130 /**
1131 * Shifts the merged regions left or right depending on mode
1132 * <p>
1133 * TODO: MODE , this is only row specific
1134 * @param startRow
1135 * @param endRow
1136 * @param n
1137 * @param isRow
1138 */
1139 protected void shiftMerged(int startRow, int endRow, int n, boolean isRow) {
1140 List shiftedRegions = new ArrayList();
1141 //move merged regions completely if they fall within the new region boundaries when they are shifted
1142 for (int i = 0; i < this.getNumMergedRegions(); i++) {
1143 Region merged = this.getMergedRegionAt(i);
1144
1145 boolean inStart = (merged.getRowFrom() >= startRow || merged.getRowTo() >= startRow);
1146 boolean inEnd = (merged.getRowTo() <= endRow || merged.getRowFrom() <= endRow);
1147
1148 //dont check if it's not within the shifted area
1149 if (! (inStart && inEnd)) continue;
1150
1151 //only shift if the region outside the shifted rows is not merged too
1152 if (!merged.contains(startRow-1, (short)0) && !merged.contains(endRow+1, (short)0)){
1153 merged.setRowFrom(merged.getRowFrom()+n);
1154 merged.setRowTo(merged.getRowTo()+n);
1155 //have to remove/add it back
1156 shiftedRegions.add(merged);
1157 this.removeMergedRegion(i);
1158 i = i -1; // we have to back up now since we removed one
1159
1160 }
1161
1162 }
1163
1164 //readd so it doesn't get shifted again
1165 Iterator iterator = shiftedRegions.iterator();
1166 while (iterator.hasNext()) {
1167 Region region = (Region)iterator.next();
1168
1169 this.addMergedRegion(region);
1170 }
1171
1172 }
1173
1174 /**
1175 * Shifts rows between startRow and endRow n number of rows.
1176 * If you use a negative number, it will shift rows up.
1177 * Code ensures that rows don't wrap around.
1178 *
1179 * Calls shiftRows(startRow, endRow, n, false, false);
1180 *
1181 * <p>
1182 * Additionally shifts merged regions that are completely defined in these
1183 * rows (ie. merged 2 cells on a row to be shifted).
1184 * @param startRow the row to start shifting
1185 * @param endRow the row to end shifting
1186 * @param n the number of rows to shift
1187 */
1188 public void shiftRows( int startRow, int endRow, int n ) {
1189 shiftRows(startRow, endRow, n, false, false);
1190 }
1191
1192 /**
1193 * Shifts rows between startRow and endRow n number of rows.
1194 * If you use a negative number, it will shift rows up.
1195 * Code ensures that rows don't wrap around
1196 *
1197 * <p>
1198 * Additionally shifts merged regions that are completely defined in these
1199 * rows (ie. merged 2 cells on a row to be shifted).
1200 * <p>
1201 * TODO Might want to add bounds checking here
1202 * @param startRow the row to start shifting
1203 * @param endRow the row to end shifting
1204 * @param n the number of rows to shift
1205 * @param copyRowHeight whether to copy the row height during the shift
1206 * @param resetOriginalRowHeight whether to set the original row's height to the default
1207 */
1208 public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight)
1209 {
1210 int s, e, inc;
1211 if ( n < 0 )
1212 {
1213 s = startRow;
1214 e = endRow;
1215 inc = 1;
1216 }
1217 else
1218 {
1219 s = endRow;
1220 e = startRow;
1221 inc = -1;
1222 }
1223
1224 shiftMerged(startRow, endRow, n, true);
1225 sheet.shiftRowBreaks(startRow, endRow, n);
1226
1227 for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc )
1228 {
1229 HSSFRow row = getRow( rowNum );
1230 HSSFRow row2Replace = getRow( rowNum + n );
1231 if ( row2Replace == null )
1232 row2Replace = createRow( rowNum + n );
1233
1234 HSSFCell cell;
1235
1236
1237
1238
1239 // Removes the cells before over writting them.
1240 for ( short col = row2Replace.getFirstCellNum(); col <= row2Replace.getLastCellNum(); col++ )
1241 {
1242 cell = row2Replace.getCell( col );
1243 if ( cell != null )
1244 row2Replace.removeCell( cell );
1245 }
1246 if (row == null) continue; // Nothing to do for this row
1247 else {
1248 if (copyRowHeight) {
1249 row2Replace.setHeight(row.getHeight());
1250 }
1251
1252 if (resetOriginalRowHeight) {
1253 row.setHeight((short)0xff);
1254 }
1255 }
1256 for ( short col = row.getFirstCellNum(); col <= row.getLastCellNum(); col++ )
1257 {
1258 cell = row.getCell( col );
1259 if ( cell != null )
1260 {
1261 row.removeCell( cell );
1262 CellValueRecordInterface cellRecord = cell.getCellValueRecord();
1263 cellRecord.setRow( rowNum + n );
1264 row2Replace.createCellFromRecord( cellRecord );
1265 sheet.addValueRecord( rowNum + n, cellRecord );
1266 }
1267
1268 // move comments if exist (can exist even if cell is null)
1269 HSSFComment comment = getCellComment(rowNum, col);
1270 if (comment != null) {
1271 comment.setRow(rowNum + n);
1272 }
1273 }
1274 }
1275 if ( endRow == lastrow || endRow + n > lastrow ) lastrow = Math.min( endRow + n, 65535 );
1276 if ( startRow == firstrow || startRow + n < firstrow ) firstrow = Math.max( startRow + n, 0 );
1277
1278 // Update any formulas on this sheet that point to
1279 // rows which have been moved
1280 updateFormulasAfterShift(startRow, endRow, n);
1281 }
1282
1283 /**
1284 * Called by shiftRows to update formulas on this sheet
1285 * to point to the new location of moved rows
1286 */
1287 private void updateFormulasAfterShift(int startRow, int endRow, int n) {
1288 // Need to look at every cell on the sheet
1289 // Not just those that were moved
1290 Iterator ri = rowIterator();
1291 while(ri.hasNext()) {
1292 HSSFRow r = (HSSFRow)ri.next();
1293 Iterator ci = r.cellIterator();
1294 while(ci.hasNext()) {
1295 HSSFCell c = (HSSFCell)ci.next();
1296 if(c.getCellType() == HSSFCell.CELL_TYPE_FORMULA) {
1297 // Since it's a formula cell, process the
1298 // formula string, and look to see if
1299 // it contains any references
1300 FormulaParser fp = new FormulaParser(c.getCellFormula(), workbook);
1301 fp.parse();
1302
1303 // Look for references, and update if needed
1304 Ptg[] ptgs = fp.getRPNPtg();
1305 boolean changed = false;
1306 for(int i=0; i<ptgs.length; i++) {
1307 if(ptgs[i] instanceof RefPtg) {
1308 RefPtg rptg = (RefPtg)ptgs[i];
1309 if(startRow <= rptg.getRowAsInt() &&
1310 rptg.getRowAsInt() <= endRow) {
1311 // References a row that moved
1312 rptg.setRow(rptg.getRowAsInt() + n);
1313 changed = true;
1314 }
1315 }
1316 }
1317 // If any references were changed, then
1318 // re-create the formula string
1319 if(changed) {
1320 c.setCellFormula(
1321 fp.toFormulaString(ptgs)
1322 );
1323 }
1324 }
1325 }
1326 }
1327 }
1328
1329 protected void insertChartRecords( List records )
1330 {
1331 int window2Loc = sheet.findFirstRecordLocBySid( WindowTwoRecord.sid );
1332 sheet.getRecords().addAll( window2Loc, records );
1333 }
1334
1335 /**
1336 * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
1337 * @param colSplit Horizonatal position of split.
1338 * @param rowSplit Vertical position of split.
1339 * @param topRow Top row visible in bottom pane
1340 * @param leftmostColumn Left column visible in right pane.
1341 */
1342 public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow )
1343 {
1344 if (colSplit < 0 || colSplit > 255) throw new IllegalArgumentException("Column must be between 0 and 255");
1345 if (rowSplit < 0 || rowSplit > 65535) throw new IllegalArgumentException("Row must be between 0 and 65535");
1346 if (leftmostColumn < colSplit) throw new IllegalArgumentException("leftmostColumn parameter must not be less than colSplit parameter");
1347 if (topRow < rowSplit) throw new IllegalArgumentException("topRow parameter must not be less than leftmostColumn parameter");
1348 getSheet().createFreezePane( colSplit, rowSplit, topRow, leftmostColumn );
1349 }
1350
1351 /**
1352 * Creates a split (freezepane). Any existing freezepane or split pane is overwritten.
1353 * @param colSplit Horizonatal position of split.
1354 * @param rowSplit Vertical position of split.
1355 */
1356 public void createFreezePane( int colSplit, int rowSplit )
1357 {
1358 createFreezePane( colSplit, rowSplit, colSplit, rowSplit );
1359 }
1360
1361 /**
1362 * Creates a split pane. Any existing freezepane or split pane is overwritten.
1363 * @param xSplitPos Horizonatal position of split (in 1/20th of a point).
1364 * @param ySplitPos Vertical position of split (in 1/20th of a point).
1365 * @param topRow Top row visible in bottom pane
1366 * @param leftmostColumn Left column visible in right pane.
1367 * @param activePane Active pane. One of: PANE_LOWER_RIGHT,
1368 * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT
1369 * @see #PANE_LOWER_LEFT
1370 * @see #PANE_LOWER_RIGHT
1371 * @see #PANE_UPPER_LEFT
1372 * @see #PANE_UPPER_RIGHT
1373 */
1374 public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane )
1375 {
1376 getSheet().createSplitPane( xSplitPos, ySplitPos, topRow, leftmostColumn, activePane );
1377 }
1378
1379 /**
1380 * Returns the information regarding the currently configured pane (split or freeze).
1381 * @return null if no pane configured, or the pane information.
1382 */
1383 public PaneInformation getPaneInformation() {
1384 return getSheet().getPaneInformation();
1385 }
1386
1387 /**
1388 * Sets whether the gridlines are shown in a viewer.
1389 * @param show whether to show gridlines or not
1390 */
1391 public void setDisplayGridlines(boolean show) {
1392 sheet.setDisplayGridlines(show);
1393 }
1394
1395 /**
1396 * Returns if gridlines are displayed.
1397 * @return whether gridlines are displayed
1398 */
1399 public boolean isDisplayGridlines() {
1400 return sheet.isDisplayGridlines();
1401 }
1402
1403 /**
1404 * Sets whether the formulas are shown in a viewer.
1405 * @param show whether to show formulas or not
1406 */
1407 public void setDisplayFormulas(boolean show) {
1408 sheet.setDisplayFormulas(show);
1409 }
1410
1411 /**
1412 * Returns if formulas are displayed.
1413 * @return whether formulas are displayed
1414 */
1415 public boolean isDisplayFormulas() {
1416 return sheet.isDisplayFormulas();
1417 }
1418
1419 /**
1420 * Sets whether the RowColHeadings are shown in a viewer.
1421 * @param show whether to show RowColHeadings or not
1422 */
1423 public void setDisplayRowColHeadings(boolean show) {
1424 sheet.setDisplayRowColHeadings(show);
1425 }
1426
1427 /**
1428 * Returns if RowColHeadings are displayed.
1429 * @return whether RowColHeadings are displayed
1430 */
1431 public boolean isDisplayRowColHeadings() {
1432 return sheet.isDisplayRowColHeadings();
1433 }
1434
1435 /**
1436 * Sets a page break at the indicated row
1437 * @param row FIXME: Document this!
1438 */
1439 public void setRowBreak(int row) {
1440 validateRow(row);
1441 sheet.setRowBreak(row, (short)0, (short)255);
1442 }
1443
1444 /**
1445 * Determines if there is a page break at the indicated row
1446 * @param row FIXME: Document this!
1447 * @return FIXME: Document this!
1448 */
1449 public boolean isRowBroken(int row) {
1450 return sheet.isRowBroken(row);
1451 }
1452
1453 /**
1454 * Removes the page break at the indicated row
1455 * @param row
1456 */
1457 public void removeRowBreak(int row) {
1458 sheet.removeRowBreak(row);
1459 }
1460
1461 /**
1462 * Retrieves all the horizontal page breaks
1463 * @return all the horizontal page breaks, or null if there are no row page breaks
1464 */
1465 public int[] getRowBreaks(){
1466 //we can probably cache this information, but this should be a sparsely used function
1467 int count = sheet.getNumRowBreaks();
1468 if (count > 0) {
1469 int[] returnValue = new int[count];
1470 Iterator iterator = sheet.getRowBreaks();
1471 int i = 0;
1472 while (iterator.hasNext()) {
1473 PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next();
1474 returnValue[i++] = breakItem.main;
1475 }
1476 return returnValue;
1477 }
1478 return null;
1479 }
1480
1481 /**
1482 * Retrieves all the vertical page breaks
1483 * @return all the vertical page breaks, or null if there are no column page breaks
1484 */
1485 public short[] getColumnBreaks(){
1486 //we can probably cache this information, but this should be a sparsely used function
1487 int count = sheet.getNumColumnBreaks();
1488 if (count > 0) {
1489 short[] returnValue = new short[count];
1490 Iterator iterator = sheet.getColumnBreaks();
1491 int i = 0;
1492 while (iterator.hasNext()) {
1493 PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next();
1494 returnValue[i++] = breakItem.main;
1495 }
1496 return returnValue;
1497 }
1498 return null;
1499 }
1500
1501
1502 /**
1503 * Sets a page break at the indicated column
1504 * @param column
1505 */
1506 public void setColumnBreak(short column) {
1507 validateColumn(column);
1508 sheet.setColumnBreak(column, (short)0, (short)65535);
1509 }
1510
1511 /**
1512 * Determines if there is a page break at the indicated column
1513 * @param column FIXME: Document this!
1514 * @return FIXME: Document this!
1515 */
1516 public boolean isColumnBroken(short column) {
1517 return sheet.isColumnBroken(column);
1518 }
1519
1520 /**
1521 * Removes a page break at the indicated column
1522 * @param column
1523 */
1524 public void removeColumnBreak(short column) {
1525 sheet.removeColumnBreak(column);
1526 }
1527
1528 /**
1529 * Runs a bounds check for row numbers
1530 * @param row
1531 */
1532 protected void validateRow(int row) {
1533 if (row > 65535) throw new IllegalArgumentException("Maximum row number is 65535");
1534 if (row < 0) throw new IllegalArgumentException("Minumum row number is 0");
1535 }
1536
1537 /**
1538 * Runs a bounds check for column numbers
1539 * @param column
1540 */
1541 protected void validateColumn(short column) {
1542 if (column > 255) throw new IllegalArgumentException("Maximum column number is 255");
1543 if (column < 0) throw new IllegalArgumentException("Minimum column number is 0");
1544 }
1545
1546 /**
1547 * Aggregates the drawing records and dumps the escher record hierarchy
1548 * to the standard output.
1549 */
1550 public void dumpDrawingRecords(boolean fat)
1551 {
1552 sheet.aggregateDrawingRecords(book.getDrawingManager(), false);
1553
1554 EscherAggregate r = (EscherAggregate) getSheet().findFirstRecordBySid(EscherAggregate.sid);
1555 List escherRecords = r.getEscherRecords();
1556 PrintWriter w = new PrintWriter(System.out);
1557 for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); )
1558 {
1559 EscherRecord escherRecord = (EscherRecord) iterator.next();
1560 if (fat)
1561 System.out.println(escherRecord.toString());
1562 else
1563 escherRecord.display(w, 0);
1564 }
1565 w.flush();
1566 }
1567
1568 /**
1569 * Creates the top-level drawing patriarch. This will have
1570 * the effect of removing any existing drawings on this
1571 * sheet.
1572 * This may then be used to add graphics or charts
1573 * @return The new patriarch.
1574 */
1575 public HSSFPatriarch createDrawingPatriarch()
1576 {
1577 // Create the drawing group if it doesn't already exist.
1578 book.createDrawingGroup();
1579
1580 sheet.aggregateDrawingRecords(book.getDrawingManager(), true);
1581 EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid);
1582 HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
1583 agg.clear(); // Initially the behaviour will be to clear out any existing shapes in the sheet when
1584 // creating a new patriarch.
1585 agg.setPatriarch(patriarch);
1586 return patriarch;
1587 }
1588
1589 /**
1590 * Returns the agregate escher records for this sheet,
1591 * it there is one.
1592 * WARNING - calling this will trigger a parsing of the
1593 * associated escher records. Any that aren't supported
1594 * (such as charts and complex drawing types) will almost
1595 * certainly be lost or corrupted when written out.
1596 */
1597 public EscherAggregate getDrawingEscherAggregate() {
1598 book.findDrawingGroup();
1599
1600 // If there's now no drawing manager, then there's
1601 // no drawing escher records on the workbook
1602 if(book.getDrawingManager() == null) {
1603 return null;
1604 }
1605
1606 int found = sheet.aggregateDrawingRecords(
1607 book.getDrawingManager(), false
1608 );
1609 if(found == -1) {
1610 // Workbook has drawing stuff, but this sheet doesn't
1611 return null;
1612 }
1613
1614 // Grab our aggregate record, and wire it up
1615 EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid);
1616 return agg;
1617 }
1618
1619 /**
1620 * Returns the top-level drawing patriach, if there is
1621 * one.
1622 * This will hold any graphics or charts for the sheet.
1623 * WARNING - calling this will trigger a parsing of the
1624 * associated escher records. Any that aren't supported
1625 * (such as charts and complex drawing types) will almost
1626 * certainly be lost or corrupted when written out. Only
1627 * use this with simple drawings, otherwise call
1628 * {@link HSSFSheet#createDrawingPatriarch()} and
1629 * start from scratch!
1630 */
1631 public HSSFPatriarch getDrawingPatriarch() {
1632 EscherAggregate agg = getDrawingEscherAggregate();
1633 if(agg == null) return null;
1634
1635 HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
1636 agg.setPatriarch(patriarch);
1637
1638 // Have it process the records into high level objects
1639 // as best it can do (this step may eat anything
1640 // that isn't supported, you were warned...)
1641 agg.convertRecordsToUserModel();
1642
1643 // Return what we could cope with
1644 return patriarch;
1645 }
1646
1647 /**
1648 * Expands or collapses a column group.
1649 *
1650 * @param columnNumber One of the columns in the group.
1651 * @param collapsed true = collapse group, false = expand group.
1652 */
1653 public void setColumnGroupCollapsed( short columnNumber, boolean collapsed )
1654 {
1655 sheet.setColumnGroupCollapsed( columnNumber, collapsed );
1656 }
1657
1658 /**
1659 * Create an outline for the provided column range.
1660 *
1661 * @param fromColumn beginning of the column range.
1662 * @param toColumn end of the column range.
1663 */
1664 public void groupColumn(short fromColumn, short toColumn)
1665 {
1666 sheet.groupColumnRange( fromColumn, toColumn, true );
1667 }
1668
1669 public void ungroupColumn( short fromColumn, short toColumn )
1670 {
1671 sheet.groupColumnRange( fromColumn, toColumn, false );
1672 }
1673
1674 public void groupRow(int fromRow, int toRow)
1675 {
1676 sheet.groupRowRange( fromRow, toRow, true );
1677 }
1678
1679 public void ungroupRow(int fromRow, int toRow)
1680 {
1681 sheet.groupRowRange( fromRow, toRow, false );
1682 }
1683
1684 public void setRowGroupCollapsed( int row, boolean collapse )
1685 {
1686 sheet.setRowGroupCollapsed( row, collapse );
1687 }
1688
1689 /**
1690 * Sets the default column style for a given column. POI will only apply this style to new cells added to the sheet.
1691 *
1692 * @param column the column index
1693 * @param style the style to set
1694 */
1695 public void setDefaultColumnStyle(short column, HSSFCellStyle style) {
1696 sheet.setColumn(column, new Short(style.getIndex()), null, null, null, null);
1697 }
1698
1699 /**
1700 * Adjusts the column width to fit the contents.
1701 *
1702 * This process can be relatively slow on large sheets, so this should
1703 * normally only be called once per column, at the end of your
1704 * processing.
1705 *
1706 * @param column the column index
1707 */
1708 public void autoSizeColumn(short column) {
1709 autoSizeColumn(column, false);
1710 }
1711
1712 /**
1713 * Adjusts the column width to fit the contents.
1714 *
1715 * This process can be relatively slow on large sheets, so this should
1716 * normally only be called once per column, at the end of your
1717 * processing.
1718 *
1719 * You can specify whether the content of merged cells should be considered or ignored.
1720 * Default is to ignore merged cells.
1721 *
1722 * @param column the column index
1723 * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column
1724 */
1725 public void autoSizeColumn(short column, boolean useMergedCells) {
1726 AttributedString str;
1727 TextLayout layout;
1728 /**
1729 * Excel measures columns in units of 1/256th of a character width
1730 * but the docs say nothing about what particular character is used.
1731 * '0' looks to be a good choice.
1732 */
1733 char defaultChar = '0';
1734
1735 /**
1736 * This is the multiple that the font height is scaled by when determining the
1737 * boundary of rotated text.
1738 */
1739 double fontHeightMultiple = 2.0;
1740
1741 FontRenderContext frc = new FontRenderContext(null, true, true);
1742
1743 HSSFWorkbook wb = new HSSFWorkbook(book);
1744 HSSFFont defaultFont = wb.getFontAt((short) 0);
1745
1746 str = new AttributedString("" + defaultChar);
1747 copyAttributes(defaultFont, str, 0, 1);
1748 layout = new TextLayout(str.getIterator(), frc);
1749 int defaultCharWidth = (int)layout.getAdvance();
1750
1751 double width = -1;
1752 rows:
1753 for (Iterator it = rowIterator(); it.hasNext();) {
1754 HSSFRow row = (HSSFRow) it.next();
1755 HSSFCell cell = row.getCell(column);
1756
1757 if (cell == null) continue;
1758
1759 int colspan = 1;
1760 for (int i = 0 ; i < getNumMergedRegions(); i++) {
1761 if (getMergedRegionAt(i).contains(row.getRowNum(), column)) {
1762 if (!useMergedCells) {
1763 // If we're not using merged cells, skip this one and move on to the next.
1764 continue rows;
1765 }
1766 cell = row.getCell(getMergedRegionAt(i).getColumnFrom());
1767 colspan = 1+ getMergedRegionAt(i).getColumnTo() - getMergedRegionAt(i).getColumnFrom();
1768 }
1769 }
1770
1771 HSSFCellStyle style = cell.getCellStyle();
1772 HSSFFont font = wb.getFontAt(style.getFontIndex());
1773
1774 if (cell.getCellType() == HSSFCell.CELL_TYPE_STRING) {
1775 HSSFRichTextString rt = cell.getRichStringCellValue();
1776 String[] lines = rt.getString().split("\\n");
1777 for (int i = 0; i < lines.length; i++) {
1778 String txt = lines[i] + defaultChar;
1779 str = new AttributedString(txt);
1780 copyAttributes(font, str, 0, txt.length());
1781
1782 if (rt.numFormattingRuns() > 0) {
1783 for (int j = 0; j < lines[i].length(); j++) {
1784 int idx = rt.getFontAtIndex(j);
1785 if (idx != 0) {
1786 HSSFFont fnt = wb.getFontAt((short) idx);
1787 copyAttributes(fnt, str, j, j + 1);
1788 }
1789 }
1790 }
1791
1792 layout = new TextLayout(str.getIterator(), frc);
1793 if(style.getRotation() != 0){
1794 /*
1795 * Transform the text using a scale so that it's height is increased by a multiple of the leading,
1796 * and then rotate the text before computing the bounds. The scale results in some whitespace around
1797 * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
1798 * is added by the standard Excel autosize.
1799 */
1800 AffineTransform trans = new AffineTransform();
1801 trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
1802 trans.concatenate(
1803 AffineTransform.getScaleInstance(1, fontHeightMultiple)
1804 );
1805 width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1806 } else {
1807 width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1808 }
1809 }
1810 } else {
1811 String sval = null;
1812 if (cell.getCellType() == HSSFCell.CELL_TYPE_NUMERIC) {
1813 HSSFDataFormat dataformat = wb.createDataFormat();
1814 short idx = style.getDataFormat();
1815 String format = dataformat.getFormat(idx).replaceAll("\"", "");
1816 double value = cell.getNumericCellValue();
1817 try {
1818 NumberFormat fmt;
1819 if ("General".equals(format))
1820 sval = "" + value;
1821 else
1822 {
1823 fmt = new DecimalFormat(format);
1824 sval = fmt.format(value);
1825 }
1826 } catch (Exception e) {
1827 sval = "" + value;
1828 }
1829 } else if (cell.getCellType() == HSSFCell.CELL_TYPE_BOOLEAN) {
1830 sval = String.valueOf(cell.getBooleanCellValue());
1831 }
1832 if(sval != null) {
1833 String txt = sval + defaultChar;
1834 str = new AttributedString(txt);
1835 copyAttributes(font, str, 0, txt.length());
1836
1837 layout = new TextLayout(str.getIterator(), frc);
1838 if(style.getRotation() != 0){
1839 /*
1840 * Transform the text using a scale so that it's height is increased by a multiple of the leading,
1841 * and then rotate the text before computing the bounds. The scale results in some whitespace around
1842 * the unrotated top and bottom of the text that normally wouldn't be present if unscaled, but
1843 * is added by the standard Excel autosize.
1844 */
1845 AffineTransform trans = new AffineTransform();
1846 trans.concatenate(AffineTransform.getRotateInstance(style.getRotation()*2.0*Math.PI/360.0));
1847 trans.concatenate(
1848 AffineTransform.getScaleInstance(1, fontHeightMultiple)
1849 );
1850 width = Math.max(width, ((layout.getOutline(trans).getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1851 } else {
1852 width = Math.max(width, ((layout.getBounds().getWidth() / colspan) / defaultCharWidth) + cell.getCellStyle().getIndention());
1853 }
1854 }
1855 }
1856
1857 }
1858 if (width != -1) {
1859 if (width > Short.MAX_VALUE) { //width can be bigger that Short.MAX_VALUE!
1860 width = Short.MAX_VALUE;
1861 }
1862 sheet.setColumnWidth(column, (short) (width * 256));
1863 }
1864 }
1865
1866 /**
1867 * Copy text attributes from the supplied HSSFFont to Java2D AttributedString
1868 */
1869 private void copyAttributes(HSSFFont font, AttributedString str, int startIdx, int endIdx) {
1870 str.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx);
1871 str.addAttribute(TextAttribute.SIZE, new Float(font.getFontHeightInPoints()));
1872 if (font.getBoldweight() == HSSFFont.BOLDWEIGHT_BOLD) str.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx);
1873 if (font.getItalic() ) str.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx);
1874 if (font.getUnderline() == HSSFFont.U_SINGLE ) str.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx);
1875 }
1876
1877 /**
1878 * Returns cell comment for the specified row and column
1879 *
1880 * @return cell comment or <code>null</code> if not found
1881 */
1882 public HSSFComment getCellComment(int row, int column) {
1883 // Don't call findCellComment directly, otherwise
1884 // two calls to this method will result in two
1885 // new HSSFComment instances, which is bad
1886 HSSFRow r = getRow(row);
1887 if(r != null) {
1888 HSSFCell c = r.getCell((short)column);
1889 if(c != null) {
1890 return c.getCellComment();
1891 } else {
1892 // No cell, so you will get new
1893 // objects every time, sorry...
1894 return HSSFCell.findCellComment(sheet, row, column);
1895 }
1896 }
1897 return null;
1898 }
1899
1900 public HSSFSheetConditionalFormatting getSheetConditionalFormatting() {
1901 return new HSSFSheetConditionalFormatting(workbook, sheet);
1902 }
1903 }