1 /********************************************************************* 2 * 3 * Copyright (C) 2002 Andrew Khan 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2.1 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 ***************************************************************************/ 19 20 package jxl.biff.drawing; 21 22 import java.io.IOException; 23 24 import jxl.common.Assert; 25 import jxl.common.Logger; 26 27 import jxl.WorkbookSettings; 28 import jxl.biff.ContinueRecord; 29 import jxl.biff.IntegerHelper; 30 import jxl.biff.StringHelper; 31 import jxl.write.biff.File; 32 33 /** 34 * Contains the various biff records used to insert a cell note into a 35 * worksheet 36 */ 37 public class Comment implements DrawingGroupObject 38 { 39 /** 40 * The logger 41 */ 42 private static Logger logger = Logger.getLogger(Comment.class); 43 44 /** 45 * The spContainer that was read in 46 */ 47 private EscherContainer readSpContainer; 48 49 /** 50 * The spContainer that was generated 51 */ 52 private EscherContainer spContainer; 53 54 /** 55 * The MsoDrawingRecord associated with the drawing 56 */ 57 private MsoDrawingRecord msoDrawingRecord; 58 59 /** 60 * The ObjRecord associated with the drawing 61 */ 62 private ObjRecord objRecord; 63 64 /** 65 * Initialized flag 66 */ 67 private boolean initialized = false; 68 69 /** 70 * The object id, assigned by the drawing group 71 */ 72 private int objectId; 73 74 /** 75 * The blip id 76 */ 77 private int blipId; 78 79 /** 80 * The shape id 81 */ 82 private int shapeId; 83 84 /** 85 * The column 86 */ 87 private int column; 88 89 /** 90 * The row position of the image 91 */ 92 private int row; 93 94 /** 95 * The width of the image in cells 96 */ 97 private double width; 98 99 /** 100 * The height of the image in cells 101 */ 102 private double height; 103 104 /** 105 * The number of places this drawing is referenced 106 */ 107 private int referenceCount; 108 109 /** 110 * The top level escher container 111 */ 112 private EscherContainer escherData; 113 114 /** 115 * Where this image came from (read, written or a copy) 116 */ 117 private Origin origin; 118 119 /** 120 * The drawing group for all the images 121 */ 122 private DrawingGroup drawingGroup; 123 124 /** 125 * The drawing data 126 */ 127 private DrawingData drawingData; 128 129 /** 130 * The type of this drawing object 131 */ 132 private ShapeType type; 133 134 /** 135 * The drawing position on the sheet 136 */ 137 private int drawingNumber; 138 139 /** 140 * An mso drawing record, which sometimes appears 141 */ 142 private MsoDrawingRecord mso; 143 144 /** 145 * The text object record 146 */ 147 private TextObjectRecord txo; 148 149 /** 150 * The note record 151 */ 152 private NoteRecord note; 153 154 /** 155 * Text data from the first continue record 156 */ 157 private ContinueRecord text; 158 159 /** 160 * Formatting data from the second continue record 161 */ 162 private ContinueRecord formatting; 163 164 /** 165 * The comment text 166 */ 167 private String commentText; 168 169 /** 170 * The workbook settings 171 */ 172 private WorkbookSettings workbookSettings; 173 174 /** 175 * Constructor used when reading images 176 * 177 * @param msorec the drawing record 178 * @param obj the object record 179 * @param dd the drawing data for all drawings on this sheet 180 * @param dg the drawing group 181 * @param ws the workbook settings 182 */ 183 public Comment(MsoDrawingRecord msorec, ObjRecord obj, DrawingData dd, 184 DrawingGroup dg, WorkbookSettings ws) 185 { 186 drawingGroup = dg; 187 msoDrawingRecord = msorec; 188 drawingData = dd; 189 objRecord = obj; 190 initialized = false; 191 workbookSettings = ws; 192 origin = Origin.READ; 193 drawingData.addData(msoDrawingRecord.getData()); 194 drawingNumber = drawingData.getNumDrawings() - 1; 195 drawingGroup.addDrawing(this); 196 197 Assert.verify(msoDrawingRecord != null && objRecord != null); 198 199 if (!initialized) 200 { 201 initialize(); 202 } 203 } 204 205 /** 206 * Copy constructor used to copy drawings from read to write 207 * 208 * @param dgo the drawing group object 209 * @param dg the drawing group 210 * @param ws the workbook settings 211 */ 212 /*protected*/ public Comment(DrawingGroupObject dgo, 213 DrawingGroup dg, 214 WorkbookSettings ws) 215 { 216 Comment d = (Comment) dgo; 217 Assert.verify(d.origin == Origin.READ); 218 msoDrawingRecord = d.msoDrawingRecord; 219 objRecord = d.objRecord; 220 initialized = false; 221 origin = Origin.READ; 222 drawingData = d.drawingData; 223 drawingGroup = dg; 224 drawingNumber = d.drawingNumber; 225 drawingGroup.addDrawing(this); 226 mso = d.mso; 227 txo = d.txo; 228 text = d.text; 229 formatting = d.formatting; 230 note = d.note; 231 width = d.width; 232 height = d.height; 233 workbookSettings = ws; 234 } 235 236 /** 237 * Constructor invoked when writing the images 238 * 239 * @param txt the comment text 240 * @param c the column 241 * @param r the row 242 */ 243 public Comment(String txt, int c, int r) 244 { 245 initialized = true; 246 origin = Origin.WRITE; 247 column = c; 248 row = r; 249 referenceCount = 1; 250 type = ShapeType.TEXT_BOX; 251 commentText = txt; 252 width = 3; 253 height = 4; 254 } 255 256 /** 257 * Initializes the member variables from the Escher stream data 258 */ 259 private void initialize() 260 { 261 readSpContainer = drawingData.getSpContainer(drawingNumber); 262 Assert.verify(readSpContainer != null); 263 264 EscherRecord[] children = readSpContainer.getChildren(); 265 266 Sp sp = (Sp) readSpContainer.getChildren()[0]; 267 objectId = objRecord.getObjectId(); 268 shapeId = sp.getShapeId(); 269 type = ShapeType.getType(sp.getShapeType()); 270 271 if (type == ShapeType.UNKNOWN) 272 { 273 logger.warn("Unknown shape type"); 274 } 275 276 ClientAnchor clientAnchor = null; 277 for (int i = 0; i < children.length && clientAnchor == null; i++) 278 { 279 if (children[i].getType() == EscherRecordType.CLIENT_ANCHOR) 280 { 281 clientAnchor = (ClientAnchor) children[i]; 282 } 283 } 284 285 if (clientAnchor == null) 286 { 287 logger.warn("client anchor not found"); 288 } 289 else 290 { 291 column = (int) clientAnchor.getX1() - 1; 292 row = (int) clientAnchor.getY1() + 1; 293 width = clientAnchor.getX2() - clientAnchor.getX1(); 294 height = clientAnchor.getY2() - clientAnchor.getY1(); 295 } 296 297 initialized = true; 298 } 299 300 301 /** 302 * Sets the object id. Invoked by the drawing group when the object is 303 * added to id 304 * 305 * @param objid the object id 306 * @param bip the blip id 307 * @param sid the shape id 308 */ 309 public final void setObjectId(int objid, int bip, int sid) 310 { 311 objectId = objid; 312 blipId = bip; 313 shapeId = sid; 314 315 if (origin == Origin.READ) 316 { 317 origin = Origin.READ_WRITE; 318 } 319 } 320 321 /** 322 * Accessor for the object id 323 * 324 * @return the object id 325 */ 326 public final int getObjectId() 327 { 328 if (!initialized) 329 { 330 initialize(); 331 } 332 333 return objectId; 334 } 335 336 /** 337 * Accessor for the shape id 338 * 339 * @return the object id 340 */ 341 public final int getShapeId() 342 { 343 if (!initialized) 344 { 345 initialize(); 346 } 347 348 return shapeId; 349 } 350 351 /** 352 * Accessor for the blip id 353 * 354 * @return the blip id 355 */ 356 public final int getBlipId() 357 { 358 if (!initialized) 359 { 360 initialize(); 361 } 362 363 return blipId; 364 } 365 366 /** 367 * Gets the drawing record which was read in 368 * 369 * @return the drawing record 370 */ 371 public MsoDrawingRecord getMsoDrawingRecord() 372 { 373 return msoDrawingRecord; 374 } 375 376 /** 377 * Creates the main Sp container for the drawing 378 * 379 * @return the SP container 380 */ 381 public EscherContainer getSpContainer() 382 { 383 if (!initialized) 384 { 385 initialize(); 386 } 387 388 if (origin == Origin.READ) 389 { 390 return getReadSpContainer(); 391 } 392 393 if (spContainer == null) 394 { 395 spContainer = new SpContainer(); 396 Sp sp = new Sp(type, shapeId, 2560); 397 spContainer.add(sp); 398 Opt opt = new Opt(); 399 opt.addProperty(344, false, false, 0); // ? 400 opt.addProperty(385, false, false, 134217808); // fill colour 401 opt.addProperty(387, false, false, 134217808); // background colour 402 opt.addProperty(959, false, false, 131074); // hide 403 spContainer.add(opt); 404 405 ClientAnchor clientAnchor = new ClientAnchor(column + 1.3, 406 Math.max(0, row - 0.6), 407 column + 1.3 + width, 408 row + height, 409 0x1); 410 411 spContainer.add(clientAnchor); 412 413 ClientData clientData = new ClientData(); 414 spContainer.add(clientData); 415 416 ClientTextBox clientTextBox = new ClientTextBox(); 417 spContainer.add(clientTextBox); 418 } 419 420 return spContainer; 421 } 422 423 /** 424 * Sets the drawing group for this drawing. Called by the drawing group 425 * when this drawing is added to it 426 * 427 * @param dg the drawing group 428 */ 429 public void setDrawingGroup(DrawingGroup dg) 430 { 431 drawingGroup = dg; 432 } 433 434 /** 435 * Accessor for the drawing group 436 * 437 * @return the drawing group 438 */ 439 public DrawingGroup getDrawingGroup() 440 { 441 return drawingGroup; 442 } 443 444 /** 445 * Gets the origin of this drawing 446 * 447 * @return where this drawing came from 448 */ 449 public Origin getOrigin() 450 { 451 return origin; 452 } 453 454 /** 455 * Accessor for the reference count on this drawing 456 * 457 * @return the reference count 458 */ 459 public int getReferenceCount() 460 { 461 return referenceCount; 462 } 463 464 /** 465 * Sets the new reference count on the drawing 466 * 467 * @param r the new reference count 468 */ 469 public void setReferenceCount(int r) 470 { 471 referenceCount = r; 472 } 473 474 /** 475 * Accessor for the column of this drawing 476 * 477 * @return the column 478 */ 479 public double getX() 480 { 481 if (!initialized) 482 { 483 initialize(); 484 } 485 return column; 486 } 487 488 /** 489 * Sets the column position of this drawing. Used when inserting/removing 490 * columns from the spreadsheet 491 * 492 * @param x the column 493 */ 494 public void setX(double x) 495 { 496 if (origin == Origin.READ) 497 { 498 if (!initialized) 499 { 500 initialize(); 501 } 502 origin = Origin.READ_WRITE; 503 } 504 505 column = (int) x; 506 } 507 508 /** 509 * Accessor for the row of this drawing 510 * 511 * @return the row 512 */ 513 public double getY() 514 { 515 if (!initialized) 516 { 517 initialize(); 518 } 519 520 return row; 521 } 522 523 /** 524 * Accessor for the row of the drawing 525 * 526 * @param y the row 527 */ 528 public void setY(double y) 529 { 530 if (origin == Origin.READ) 531 { 532 if (!initialized) 533 { 534 initialize(); 535 } 536 origin = Origin.READ_WRITE; 537 } 538 539 row = (int) y; 540 } 541 542 543 /** 544 * Accessor for the width of this drawing 545 * 546 * @return the number of columns spanned by this image 547 */ 548 public double getWidth() 549 { 550 if (!initialized) 551 { 552 initialize(); 553 } 554 555 return width; 556 } 557 558 /** 559 * Accessor for the width 560 * 561 * @param w the number of columns to span 562 */ 563 public void setWidth(double w) 564 { 565 if (origin == Origin.READ) 566 { 567 if (!initialized) 568 { 569 initialize(); 570 } 571 origin = Origin.READ_WRITE; 572 } 573 574 width = w; 575 } 576 577 /** 578 * Accessor for the height of this drawing 579 * 580 * @return the number of rows spanned by this image 581 */ 582 public double getHeight() 583 { 584 if (!initialized) 585 { 586 initialize(); 587 } 588 589 return height; 590 } 591 592 /** 593 * Accessor for the height of this drawing 594 * 595 * @param h the number of rows spanned by this image 596 */ 597 public void setHeight(double h) 598 { 599 if (origin == Origin.READ) 600 { 601 if (!initialized) 602 { 603 initialize(); 604 } 605 origin = Origin.READ_WRITE; 606 } 607 608 height = h; 609 } 610 611 612 /** 613 * Gets the SpContainer that was read in 614 * 615 * @return the read sp container 616 */ 617 private EscherContainer getReadSpContainer() 618 { 619 if (!initialized) 620 { 621 initialize(); 622 } 623 624 return readSpContainer; 625 } 626 627 /** 628 * Accessor for the image data 629 * 630 * @return the image data 631 */ 632 public byte[] getImageData() 633 { 634 Assert.verify(origin == Origin.READ || origin == Origin.READ_WRITE); 635 636 if (!initialized) 637 { 638 initialize(); 639 } 640 641 return drawingGroup.getImageData(blipId); 642 } 643 644 /** 645 * Accessor for the type 646 * 647 * @return the type 648 */ 649 public ShapeType getType() 650 { 651 return type; 652 } 653 654 /** 655 * Sets the text object 656 * 657 * @param t the text object 658 */ 659 public void setTextObject(TextObjectRecord t) 660 { 661 txo = t; 662 } 663 664 /** 665 * Sets the note object 666 * 667 * @param t the note record 668 */ 669 public void setNote(NoteRecord t) 670 { 671 note = t; 672 } 673 674 /** 675 * Sets the text data 676 * 677 * @param t the text data 678 */ 679 public void setText(ContinueRecord t) 680 { 681 text = t; 682 } 683 684 /** 685 * Sets the formatting 686 * 687 * @param t the formatting record 688 */ 689 public void setFormatting(ContinueRecord t) 690 { 691 formatting = t; 692 } 693 694 /** 695 * Accessor for the image data 696 * 697 * @return the image data 698 */ 699 public byte[] getImageBytes() 700 { 701 Assert.verify(false); 702 return null; 703 } 704 705 /** 706 * Accessor for the image file path. Normally this is the absolute path 707 * of a file on the directory system, but if this drawing was constructed 708 * using an byte[] then the blip id is returned 709 * 710 * @return the image file path, or the blip id 711 */ 712 public String getImageFilePath() 713 { 714 Assert.verify(false); 715 return null; 716 } 717 718 /** 719 * Adds an mso record to this object 720 * 721 * @param d the mso record 722 */ 723 public void addMso(MsoDrawingRecord d) 724 { 725 mso = d; 726 drawingData.addRawData(mso.getData()); 727 } 728 729 /** 730 * Writes out the additional comment records 731 * 732 * @param outputFile the output file 733 * @exception IOException 734 */ 735 public void writeAdditionalRecords(File outputFile) throws IOException 736 { 737 if (origin == Origin.READ) 738 { 739 outputFile.write(objRecord); 740 741 if (mso != null) 742 { 743 outputFile.write(mso); 744 } 745 outputFile.write(txo); 746 outputFile.write(text); 747 if (formatting != null) 748 { 749 outputFile.write(formatting); 750 } 751 return; 752 } 753 754 // Create the obj record 755 ObjRecord objrec = new ObjRecord(objectId, 756 ObjRecord.EXCELNOTE); 757 758 outputFile.write(objrec); 759 760 // Create the mso data record. Write the text box record again, 761 // although it is already included in the SpContainer 762 ClientTextBox textBox = new ClientTextBox(); 763 MsoDrawingRecord msod = new MsoDrawingRecord(textBox.getData()); 764 outputFile.write(msod); 765 766 TextObjectRecord txorec = new TextObjectRecord(getText()); 767 outputFile.write(txorec); 768 769 // Data for the first continue record 770 byte[] textData = new byte[commentText.length() * 2 + 1]; 771 textData[0] = 0x1; // unicode indicator 772 StringHelper.getUnicodeBytes(commentText, textData, 1); 773 //StringHelper.getBytes(commentText, textData, 1); 774 ContinueRecord textContinue = new ContinueRecord(textData); 775 outputFile.write(textContinue); 776 777 // Data for the formatting runs 778 779 byte[] frData = new byte[16]; 780 781 // First txo run (the user) 782 IntegerHelper.getTwoBytes(0, frData, 0); // index to the first character 783 IntegerHelper.getTwoBytes(0, frData, 2); // index to the font(default) 784 // Mandatory last txo run 785 IntegerHelper.getTwoBytes(commentText.length(), frData, 8); 786 IntegerHelper.getTwoBytes(0, frData, 10); // index to the font(default) 787 788 ContinueRecord frContinue = new ContinueRecord(frData); 789 outputFile.write(frContinue); 790 } 791 792 /** 793 * Writes any records that need to be written after all the drawing group 794 * objects have been written 795 * Writes out all the note records 796 * 797 * @param outputFile the output file 798 * @exception IOException 799 */ 800 public void writeTailRecords(File outputFile) throws IOException 801 { 802 if (origin == Origin.READ) 803 { 804 outputFile.write(note); 805 return; 806 } 807 808 // The note record 809 NoteRecord noteRecord = new NoteRecord(column, row, objectId); 810 outputFile.write(noteRecord); 811 } 812 813 /** 814 * Accessor for the row 815 * 816 * @return the row 817 */ 818 public int getRow() 819 { 820 return note.getRow(); 821 } 822 823 /** 824 * Accessor for the column 825 * 826 * @return the column 827 */ 828 public int getColumn() 829 { 830 return note.getColumn(); 831 } 832 833 /** 834 * Accessor for the comment text 835 * 836 * @return the comment text 837 */ 838 public String getText() 839 { 840 if (commentText == null) 841 { 842 Assert.verify(text != null); 843 844 byte[] td = text.getData(); 845 if (td[0] == 0) 846 { 847 commentText = StringHelper.getString 848 (td, td.length - 1, 1, workbookSettings); 849 } 850 else 851 { 852 commentText = StringHelper.getUnicodeString 853 (td, (td.length - 1) / 2, 1); 854 } 855 } 856 857 return commentText; 858 } 859 860 /** 861 * Hashing algorithm 862 * 863 * @return the hash code 864 */ 865 public int hashCode() 866 { 867 return commentText.hashCode(); 868 } 869 870 /** 871 * Called when the comment text is changed during the sheet copy process 872 * 873 * @param t the new text 874 */ 875 public void setCommentText(String t) 876 { 877 commentText = t; 878 879 if (origin == Origin.READ) 880 { 881 origin = Origin.READ_WRITE; 882 } 883 } 884 885 /** 886 * Accessor for the first drawing on the sheet. This is used when 887 * copying unmodified sheets to indicate that this drawing contains 888 * the first time Escher gubbins 889 * 890 * @return TRUE if this MSORecord is the first drawing on the sheet 891 */ 892 public boolean isFirst() 893 { 894 return msoDrawingRecord.isFirst(); 895 } 896 897 /** 898 * Queries whether this object is a form object. Form objects have their 899 * drawings records spread over several records and require special handling 900 * 901 * @return TRUE if this is a form object, FALSE otherwise 902 */ 903 public boolean isFormObject() 904 { 905 return true; 906 } 907 } 908 909 910