1 21 package org.lobobrowser.html.renderer; 22 import java.awt.Color ; 23 import java.awt.Dimension ; 24 import java.awt.Graphics ; 25 import java.awt.Insets ; 26 import java.awt.Rectangle ; 27 import java.awt.event.MouseEvent ; 28 import java.util.*; 29 30 import org.lobobrowser.html.*; 31 import org.lobobrowser.html.domimpl.*; 32 import org.lobobrowser.html.style.*; 33 import org.w3c.dom.Node ; 34 import org.w3c.dom.html2.HTMLTableCellElement; 35 import org.w3c.dom.html2.HTMLTableRowElement; 36 37 class TableMatrix { 38 private static final NodeFilter COLUMNS_FILTER = new ColumnsFilter(); 40 private final ArrayList ROWS = new ArrayList(); 41 private final ArrayList ALL_CELLS = new ArrayList(); 42 private final ArrayList ROW_ELEMENTS = new ArrayList(); 43 private final HTMLElementImpl tableElement; 44 private final UserAgentContext parserContext; 45 private final HtmlRendererContext rendererContext; 46 private final FrameContext frameContext; 47 private final RElement relement; 48 private final RenderableContainer container; 49 50 private SizeInfo[] columnSizes; 51 private SizeInfo[] rowSizes; 52 private int tableWidth; 53 private int tableHeight; 54 private int hasOldStyleBorder; 55 56 59 public TableMatrix(HTMLElementImpl element, UserAgentContext pcontext, HtmlRendererContext rcontext, FrameContext frameContext, RenderableContainer tableAsContainer, RElement relement) { 60 this.tableElement = element; 61 this.parserContext = pcontext; 62 this.rendererContext = rcontext; 63 this.frameContext = frameContext; 64 this.relement = relement; 65 this.container = tableAsContainer; 66 } 67 68 public void finalize() throws Throwable { 69 super.finalize(); 70 } 71 72 public int getNumRows() { 73 return this.ROWS.size(); 74 } 75 76 public int getNumColumns() { 77 return this.columnSizes.length; 78 } 79 82 public int getTableHeight() { 83 return this.tableHeight; 84 } 85 88 public int getTableWidth() { 89 return this.tableWidth; 90 } 91 92 private int cellSpacingY; 94 private int cellSpacingX; 95 private int widthsOfExtras; 96 private int heightsOfExtras; 97 private HtmlLength tableWidthLength; 98 99 103 public void reset(Insets insets, int availWidth, int availHeight) { 104 ROWS.clear(); 107 ALL_CELLS.clear(); 108 ROW_ELEMENTS.clear(); 109 String borderText = this.tableElement.getAttribute("border"); 110 int border = 0; 111 if(borderText != null) { 112 try { 113 border = Integer.parseInt(borderText); 114 if(border < 0) { 115 border = 0; 116 } 117 } catch(NumberFormatException nfe) { 118 } 120 } 121 String cellSpacingText = this.tableElement.getAttribute("cellspacing"); 122 int cellSpacing = 1; 123 if(cellSpacingText != null) { 124 try { 125 cellSpacing = Integer.parseInt(cellSpacingText); 127 if(cellSpacing < 0) { 128 cellSpacing = 0; 129 } 130 } catch(NumberFormatException nfe) { 131 } 133 } 134 this.cellSpacingX = cellSpacing; 135 this.cellSpacingY = cellSpacing; 136 137 this.tableWidthLength = TableMatrix.getWidthLength(this.tableElement, availWidth); 138 139 this.populateRows(); 140 this.adjustForCellSpans(); 141 this.createSizeArrays(); 142 143 SizeInfo[] columnSizes = this.columnSizes; 145 int numCols = columnSizes.length; 146 int widthsOfExtras = insets.left + insets.right + (numCols + 1) * cellSpacing; 147 if(border > 0) { 148 widthsOfExtras += (numCols * 2); 149 } 150 this.widthsOfExtras = widthsOfExtras; 151 152 SizeInfo[] rowSizes = this.rowSizes; 154 int numRows = rowSizes.length; 155 int heightsOfExtras = insets.top + insets.bottom + (numRows + 1) * cellSpacing; 156 if(border > 0) { 157 heightsOfExtras += (numRows * 2); 158 } 159 this.heightsOfExtras = heightsOfExtras; 160 this.hasOldStyleBorder = border > 0 ? 1 : 0; 161 } 162 163 public void build(int availWidth, int availHeight) { 164 int hasBorder = this.hasOldStyleBorder; 165 this.determineColumnSizes(hasBorder, this.cellSpacingX, this.cellSpacingY, availWidth); 166 this.determineRowSizes(hasBorder, this.cellSpacingY, availHeight); 167 } 168 169 private final HTMLTableRowElementImpl getParentRow(HTMLTableCellElementImpl cellNode) { 170 org.w3c.dom.Node parentNode = cellNode.getParentNode(); 171 for(;;) { 172 if(parentNode instanceof HTMLTableRowElementImpl) { 173 return (HTMLTableRowElementImpl) parentNode; 174 } 175 if(parentNode instanceof HTMLTableElementImpl) { 176 return null; 177 } 178 parentNode = parentNode.getParentNode(); 179 } 180 } 181 182 private static HtmlLength getWidthLength(HTMLElementImpl element, int availWidth) { 183 try { 184 CSS2PropertiesImpl props = element.getCurrentStyle(); 185 String widthText = props == null ? null : props.getWidth(); 186 if(widthText == null) { 187 return new HtmlLength(element.getAttribute("width")); 188 } 189 else { 190 return new HtmlLength(HtmlValues.getPixelSize(widthText, element.getRenderState(), 0, availWidth)); 191 } 192 } catch(Exception err) { 193 return null; 194 } 195 } 196 197 private static HtmlLength getHeightLength(HTMLElementImpl element, int availHeight) { 198 try { 199 CSS2PropertiesImpl props = element.getCurrentStyle(); 200 String heightText = props == null ? null : props.getHeight(); 201 if(heightText == null) { 202 return new HtmlLength(element.getAttribute("height")); 203 } 204 else { 205 return new HtmlLength(HtmlValues.getPixelSize(heightText, element.getRenderState(), 0, availHeight)); 206 } 207 } catch(Exception err) { 208 return null; 209 } 210 } 211 212 215 private void populateRows() { 216 HTMLElementImpl te = this.tableElement; 217 String cellPaddingText = te.getAttribute("cellpadding"); 218 int cellPadding = 1; 219 if(cellPaddingText != null) { 220 try { 221 cellPadding = Integer.parseInt(cellPaddingText); 222 } catch(NumberFormatException nfe) { 225 } 227 } 228 ArrayList rows = this.ROWS; 229 ArrayList rowElements = this.ROW_ELEMENTS; 230 ArrayList allCells = this.ALL_CELLS; 231 Map rowElementToRowArray = new HashMap(2); 232 ArrayList cellList = te.getDescendents(COLUMNS_FILTER); 233 ArrayList currentNullRow = null; 234 Iterator ci = cellList.iterator(); 235 while(ci.hasNext()) { 236 HTMLTableCellElementImpl columnNode = (HTMLTableCellElementImpl) ci.next(); 237 HTMLTableRowElementImpl rowElement = this.getParentRow(columnNode); 238 ArrayList row; 239 if(rowElement != null) { 240 currentNullRow = null; 241 row = (ArrayList) rowElementToRowArray.get(rowElement); 242 if(row == null) { 243 row = new ArrayList(); 244 rowElementToRowArray.put(rowElement, row); 245 rows.add(row); 246 rowElements.add(rowElement); 247 } 248 } 249 else { 250 if(currentNullRow != null) { 253 row = currentNullRow; 254 } 255 else { 256 row = new ArrayList(); 257 currentNullRow = row; 258 rows.add(row); 259 rowElements.add(null); 261 } 262 } 263 RTableCell ac = (RTableCell) columnNode.getUINode(); 264 if(ac == null) { 265 ac = new RTableCell(columnNode, this.parserContext, this.rendererContext, this.frameContext, this.container); 268 ac.setParent(this.relement); 269 columnNode.setUINode(ac); 270 } 271 ac.setCellPadding(cellPadding); 272 VirtualCell vc = new VirtualCell(ac, true); 273 ac.setTopLeftVirtualCell(vc); 274 row.add(vc); 275 allCells.add(ac); 276 } 277 } 278 279 284 private void adjustForCellSpans() { 285 ArrayList rows = this.ROWS; 286 int numRows = rows.size(); 287 for(int r = 0; r < numRows; r++) { 288 ArrayList row = (ArrayList) rows.get(r); 289 int numCols = row.size(); 290 for(int c = 0; c < numCols; c++) { 291 VirtualCell vc = (VirtualCell) row.get(c); 292 if(vc != null && vc.isTopLeft()) { 293 RTableCell ac = vc.getActualCell(); 294 int colspan = ac.getColSpan(); 295 if(colspan < 1) { 296 colspan = 1; 297 } 298 int rowspan = ac.getRowSpan(); 299 if(rowspan < 1) { 300 rowspan = 1; 301 } 302 303 int targetRows = r + rowspan; 305 while(rows.size() < targetRows) { 306 rows.add(new ArrayList()); 307 } 308 309 numRows = rows.size(); 310 for(int y = 0; y < rowspan; y++) { 311 if(colspan > 1 || y > 0) { 312 int nr = r + y; 314 ArrayList newRow = (ArrayList) rows.get(nr); 315 316 int xstart = y == 0 ? 1 : 0; 318 319 for(int cc = xstart; cc < colspan; cc++) { 322 int nc = c + cc; 323 while(newRow.size() < nc) { 324 newRow.add(null); 325 } 326 newRow.add(nc, new VirtualCell(ac, false)); 327 } 328 if(row == newRow) { 329 numCols = row.size(); 330 } 331 } 332 } 333 } 334 } 335 } 336 337 for(int r = 0; r < numRows; r++) { 339 ArrayList row = (ArrayList) rows.get(r); 340 int numCols = row.size(); 341 for(int c = 0; c < numCols; c++) { 342 VirtualCell vc = (VirtualCell) row.get(c); 343 if(vc != null) { 344 vc.setColumn(c); 345 vc.setRow(r); 346 } 347 } 348 } 349 } 350 351 355 private void createSizeArrays() { 356 ArrayList rows = this.ROWS; 357 int numRows = rows.size(); 358 SizeInfo[] rowSizes = new SizeInfo[numRows]; 359 this.rowSizes = rowSizes; 360 int numCols = 0; 361 ArrayList rowElements = this.ROW_ELEMENTS; 362 for(int i = 0; i < numRows; i++) { 363 ArrayList row = (ArrayList) rows.get(i); 364 int rs = row.size(); 365 if(rs > numCols) { 366 numCols = rs; 367 } 368 SizeInfo rowSizeInfo = new SizeInfo(); 369 rowSizes[i] = rowSizeInfo; 370 HTMLTableRowElement rowElement; 371 try { 372 rowElement = (HTMLTableRowElement) rowElements.get(i); 373 } catch(IndexOutOfBoundsException iob) { 375 rowElement = null; 377 } 378 String rowHeightText = rowElement == null ? null : rowElement.getAttribute("height"); 380 HtmlLength rowHeightLength = null; 381 if(rowHeightText != null) { 382 try { 383 rowHeightLength = new HtmlLength(rowHeightText); 384 } catch(Exception err) { 385 } 387 } 388 if(rowHeightLength != null) { 389 rowSizeInfo.htmlLength = rowHeightLength; 390 } 391 else { 392 HtmlLength bestHeightLength = null; 393 for(int x = 0; x < rs; x++) { 394 VirtualCell vc = (VirtualCell) row.get(x); 395 if(vc != null) { 396 HtmlLength vcHeightLength = vc.getHeightLength(); 397 if(vcHeightLength != null && vcHeightLength.isPreferredOver(bestHeightLength)) { 398 bestHeightLength = vcHeightLength; 399 } 400 } 401 } 402 rowSizeInfo.htmlLength = bestHeightLength; 403 } 404 } 405 SizeInfo[] columnSizes = new SizeInfo[numCols]; 406 this.columnSizes = columnSizes; 407 for(int i = 0; i < numCols; i++) { 408 HtmlLength bestWidthLength = null; 409 410 for(int y = 0; y < numRows; y++) { 412 ArrayList row = (ArrayList) rows.get(y); 413 VirtualCell vc; 414 try { 415 vc = (VirtualCell) row.get(i); 416 } catch(IndexOutOfBoundsException iob) { 417 vc = null; 418 } 419 if(vc != null) { 420 RTableCell ac = vc.getActualCell(); 421 if(ac.getColSpan() == 1) { 422 HtmlLength vcWidthLength = vc.getWidthLength(); 423 if(vcWidthLength != null && vcWidthLength.isPreferredOver(bestWidthLength)) { 424 bestWidthLength = vcWidthLength; 425 } 426 } 427 } 428 } 429 if(bestWidthLength == null) { 431 for(int y = 0; y < numRows; y++) { 432 ArrayList row = (ArrayList) rows.get(y); 433 VirtualCell vc; 434 try { 435 vc = (VirtualCell) row.get(i); 436 } catch(IndexOutOfBoundsException iob) { 437 vc = null; 438 } 439 if(vc != null) { 440 RTableCell ac = vc.getActualCell(); 441 if(ac.getColSpan() > 1) { 442 HtmlLength vcWidthLength = vc.getWidthLength(); 443 if(vcWidthLength != null && vcWidthLength.isPreferredOver(bestWidthLength)) { 444 bestWidthLength = vcWidthLength; 445 } 446 } 447 } 448 } 449 } 450 SizeInfo colSizeInfo = new SizeInfo(); 451 colSizeInfo.htmlLength = bestWidthLength; 452 columnSizes[i] = colSizeInfo; 453 } 454 } 455 456 485 private void determineColumnSizes(int hasBorder, int cellSpacingX, int cellSpacingY, int availWidth) { 486 HtmlLength tableWidthLength = this.tableWidthLength; 487 int tableWidth; 488 boolean widthKnown; 489 if(tableWidthLength != null) { 490 tableWidth = tableWidthLength.getLength(availWidth); 491 widthKnown = true; 492 } 493 else { 494 tableWidth = availWidth; 495 widthKnown = false; 496 } 497 SizeInfo[] columnSizes = this.columnSizes; 498 int widthsOfExtras = this.widthsOfExtras; 499 int cellAvailWidth = tableWidth - widthsOfExtras; 500 if(cellAvailWidth < 0) { 501 tableWidth += (-cellAvailWidth); 502 cellAvailWidth = 0; 503 } 504 505 507 this.determineTentativeSizes(columnSizes, widthsOfExtras, cellAvailWidth, widthKnown); 508 509 512 this.preLayout(hasBorder, cellSpacingX, cellSpacingY, widthKnown); 513 514 516 this.adjustForRenderWidths(columnSizes, hasBorder, cellSpacingX, widthKnown); 517 518 520 this.adjustWidthsForExpectedMax(columnSizes, cellAvailWidth, widthKnown); 521 } 522 523 530 private void determineTentativeSizes(SizeInfo[] columnSizes, int widthsOfExtras, int cellAvailWidth, boolean setNoWidthColumns) { 531 int numCols = columnSizes.length; 532 533 int widthUsedByPercent = 0; 535 for(int i = 0; i < numCols; i++) { 536 SizeInfo colSizeInfo = columnSizes[i]; 537 HtmlLength widthLength = colSizeInfo.htmlLength; 538 if(widthLength != null && widthLength.getLengthType() == HtmlLength.LENGTH) { 539 int actualSizeInt = widthLength.getLength(cellAvailWidth); 540 widthUsedByPercent += actualSizeInt; 541 colSizeInfo.actualSize = actualSizeInt; 542 } 543 } 544 545 int widthUsedByAbsolute = 0; 547 int numNoWidthColumns = 0; 548 for(int i = 0; i < numCols; i++) { 549 SizeInfo colSizeInfo = columnSizes[i]; 550 HtmlLength widthLength = colSizeInfo.htmlLength; 551 if(widthLength != null && widthLength.getLengthType() != HtmlLength.LENGTH) { 552 int actualSizeInt = widthLength.getRawValue(); 554 widthUsedByAbsolute += actualSizeInt; 555 colSizeInfo.actualSize = actualSizeInt; 556 } 557 else if(widthLength == null) { 558 numNoWidthColumns++; 559 } 560 } 561 562 566 583 587 if(numNoWidthColumns == 0) { 588 int totalWidthUsed = widthUsedByPercent + widthUsedByAbsolute; 589 int difference = totalWidthUsed - cellAvailWidth; 590 if(difference > 0) { 592 if(widthUsedByAbsolute > 0) { 593 int expectedAbsoluteWidthTotal = widthUsedByAbsolute - difference; 594 if(expectedAbsoluteWidthTotal < 0) { 595 expectedAbsoluteWidthTotal = 0; 596 } 597 double ratio = (double) expectedAbsoluteWidthTotal / widthUsedByAbsolute; 598 for(int i = 0; i < numCols; i++) { 599 SizeInfo sizeInfo = columnSizes[i]; 600 HtmlLength widthLength = columnSizes[i].htmlLength; 601 if(widthLength != null && widthLength.getLengthType() != HtmlLength.LENGTH) { 602 int oldActualSize = sizeInfo.actualSize; 603 int newActualSize = (int) Math.round(oldActualSize * ratio); 604 sizeInfo.actualSize = newActualSize; 605 totalWidthUsed += (newActualSize - oldActualSize); 606 } 607 } 608 difference = totalWidthUsed - cellAvailWidth; 609 } 610 611 if(difference > 0) { 613 if(widthUsedByPercent > 0) { 614 int expectedPercentWidthTotal = widthUsedByPercent - difference; 615 if(expectedPercentWidthTotal < 0) { 616 expectedPercentWidthTotal = 0; 617 } 618 double ratio = (double) expectedPercentWidthTotal / widthUsedByPercent; 619 for(int i = 0; i < numCols; i++) { 620 SizeInfo sizeInfo = columnSizes[i]; 621 HtmlLength widthLength = columnSizes[i].htmlLength; 622 if(widthLength != null && widthLength.getLengthType() == HtmlLength.LENGTH) { 623 int oldActualSize = sizeInfo.actualSize; 624 int newActualSize = (int) Math.round(oldActualSize * ratio); 625 sizeInfo.actualSize = newActualSize; 626 totalWidthUsed += (newActualSize - oldActualSize); 627 } 628 } 629 } 630 } 631 } 632 } 633 } 634 635 638 private void adjustForRenderWidths(SizeInfo[] columnSizes, int hasBorder, int cellSpacing, boolean tableWidthKnown) { 639 int numCols = columnSizes.length; 640 for(int i = 0; i < numCols; i++) { 641 SizeInfo si = columnSizes[i]; 642 if(si.actualSize < si.layoutSize) { 643 si.actualSize = si.layoutSize; 644 } 645 } 653 } 654 655 private void layoutColumn(SizeInfo[] columnSizes, SizeInfo colSize, int col, int cellSpacingX, int hasBorder) { 656 SizeInfo[] rowSizes = this.rowSizes; 657 ArrayList rows = this.ROWS; 658 int numRows = rows.size(); 659 int actualSize = colSize.actualSize; 660 colSize.layoutSize = 0; 661 for(int row = 0; row < numRows;) { 662 ArrayList columns = (ArrayList) rows.get(row); 664 VirtualCell vc = null; 665 try { 666 vc = (VirtualCell) columns.get(col); 667 } catch(IndexOutOfBoundsException iob) { 668 vc = null; 669 } 670 RTableCell ac = vc == null ? null : vc.getActualCell(); 671 if(ac != null) { 672 int colSpan = ac.getColSpan(); 673 if(colSpan > 1) { 674 int firstCol = ac.getVirtualColumn(); 675 int cellExtras = (colSpan - 1) * (cellSpacingX + 2 * hasBorder); 676 int vcActualWidth = cellExtras; 677 for(int x = 0; x < colSpan; x++) { 678 vcActualWidth += columnSizes[firstCol + x].actualSize; 679 } 680 Dimension size = ac.doCellLayout(vcActualWidth, 0, false, false); 682 int vcRenderWidth = size.width; 683 684 int denominator = (vcActualWidth - cellExtras); 685 int newTentativeCellWidth; 686 if(denominator > 0) { 687 newTentativeCellWidth = actualSize * (vcRenderWidth - cellExtras) / denominator; 688 } 689 else { 690 newTentativeCellWidth = (vcRenderWidth - cellExtras) / colSpan; 691 } 692 if(newTentativeCellWidth > colSize.layoutSize) { 693 colSize.layoutSize = newTentativeCellWidth; 694 } 695 int rowSpan = ac.getRowSpan(); 696 int vch = (size.height - (rowSpan - 1) * (this.cellSpacingY + 2 * hasBorder)) / rowSpan; 697 for(int y = 0; y < rowSpan; y++) { 698 if(rowSizes[row + y].minSize < vch) { 699 rowSizes[row + y].minSize = vch; 700 } 701 } 702 } 703 else { 704 Dimension size = ac.doCellLayout(actualSize, 0, false, false); 706 if(size.width > colSize.layoutSize) { 707 colSize.layoutSize = size.width; 708 } 709 int rowSpan = ac.getRowSpan(); 710 int vch = (size.height - (rowSpan - 1) * (this.cellSpacingY + 2 * hasBorder)) / rowSpan; 711 for(int y = 0; y < rowSpan; y++) { 712 if(rowSizes[row + y].minSize < vch) { 713 rowSizes[row + y].minSize = vch; 714 } 715 } 716 } 717 } 718 row = (ac == null ? row + 1 : ac.getVirtualRow() + ac.getRowSpan()); 719 } 720 } 721 722 private int adjustWidthsForExpectedMax(SizeInfo[] columnSizes, int cellAvailWidth, boolean expand) { 723 int hasBorder = this.hasOldStyleBorder; 724 int cellSpacingX = this.cellSpacingX; 725 int currentTotal = 0; 726 int numCols = columnSizes.length; 727 for(int i = 0; i < numCols; i++) { 728 currentTotal += columnSizes[i].actualSize; 729 } 730 int difference = currentTotal - cellAvailWidth; 731 if(difference > 0 || (difference < 0 && expand)) { 732 int noWidthTotal = 0; 734 int numNoWidth = 0; 735 for(int i = 0; i < numCols; i++) { 736 if(columnSizes[i].htmlLength == null) { 737 numNoWidth++; 738 noWidthTotal += columnSizes[i].actualSize; 739 } 740 } 741 if(noWidthTotal > 0) { 742 int expectedNoWidthTotal = noWidthTotal - difference; 744 if(expectedNoWidthTotal < 0) { 745 expectedNoWidthTotal = 0; 746 } 747 double ratio = (double) expectedNoWidthTotal / noWidthTotal; 748 int noWidthCount = 0; 749 for(int i = 0; i < numCols; i++) { 750 SizeInfo sizeInfo = columnSizes[i]; 751 if(sizeInfo.htmlLength == null) { 752 int oldActualSize = sizeInfo.actualSize; 753 int newActualSize; 754 if(++noWidthCount == numNoWidth) { 755 int currentDiff = currentTotal - cellAvailWidth; 757 newActualSize = oldActualSize - currentDiff; 758 if(newActualSize < 0) { 759 newActualSize = 0; 760 } 761 } 762 else { 763 newActualSize = (int) Math.round(oldActualSize * ratio); 764 } 765 sizeInfo.actualSize = newActualSize; 766 if(newActualSize < sizeInfo.layoutSize) { 767 this.layoutColumn(columnSizes, sizeInfo, i, cellSpacingX, hasBorder); 769 if(newActualSize < sizeInfo.layoutSize) { 770 newActualSize = sizeInfo.layoutSize; 772 sizeInfo.actualSize = newActualSize; 773 } 774 } 775 currentTotal += (newActualSize - oldActualSize); 776 } 777 } 778 difference = currentTotal - cellAvailWidth; 779 } 780 781 if(difference > 0 || (difference < 0 && expand)) { 783 int absoluteWidthTotal = 0; 784 for(int i = 0; i < numCols; i++) { 785 HtmlLength widthLength = columnSizes[i].htmlLength; 786 if(widthLength != null && widthLength.getLengthType() != HtmlLength.LENGTH) { 787 absoluteWidthTotal += columnSizes[i].actualSize; 788 } 789 } 790 if(absoluteWidthTotal > 0) { 791 int expectedAbsoluteWidthTotal = absoluteWidthTotal - difference; 792 if(expectedAbsoluteWidthTotal < 0) { 793 expectedAbsoluteWidthTotal = 0; 794 } 795 double ratio = (double) expectedAbsoluteWidthTotal / absoluteWidthTotal; 796 for(int i = 0; i < numCols; i++) { 797 SizeInfo sizeInfo = columnSizes[i]; 798 HtmlLength widthLength = columnSizes[i].htmlLength; 799 if(widthLength != null && widthLength.getLengthType() != HtmlLength.LENGTH) { 800 int oldActualSize = sizeInfo.actualSize; 801 int newActualSize = (int) Math.round(oldActualSize * ratio); 802 sizeInfo.actualSize = newActualSize; 803 if(newActualSize < sizeInfo.layoutSize) { 804 this.layoutColumn(columnSizes, sizeInfo, i, cellSpacingX, hasBorder); 806 if(newActualSize < sizeInfo.layoutSize) { 807 newActualSize = sizeInfo.layoutSize; 809 sizeInfo.actualSize = newActualSize; 810 } 811 } 812 currentTotal += (newActualSize - oldActualSize); 813 } 814 } 815 difference = currentTotal - cellAvailWidth; 816 } 817 818 if(difference > 0 || (difference < 0 && expand)) { 820 int percentWidthTotal = 0; 821 for(int i = 0; i < numCols; i++) { 822 HtmlLength widthLength = columnSizes[i].htmlLength; 823 if(widthLength != null && widthLength.getLengthType() == HtmlLength.LENGTH) { 824 percentWidthTotal += columnSizes[i].actualSize; 825 } 826 } 827 if(percentWidthTotal > 0) { 828 int expectedPercentWidthTotal = percentWidthTotal - difference; 829 if(expectedPercentWidthTotal < 0) { 830 expectedPercentWidthTotal = 0; 831 } 832 double ratio = (double) expectedPercentWidthTotal / percentWidthTotal; 833 for(int i = 0; i < numCols; i++) { 834 SizeInfo sizeInfo = columnSizes[i]; 835 HtmlLength widthLength = columnSizes[i].htmlLength; 836 if(widthLength != null && widthLength.getLengthType() == HtmlLength.LENGTH) { 837 int oldActualSize = sizeInfo.actualSize; 838 int newActualSize = (int) Math.round(oldActualSize * ratio); 839 sizeInfo.actualSize = newActualSize; 840 if(newActualSize < sizeInfo.layoutSize) { 841 this.layoutColumn(columnSizes, sizeInfo, i, cellSpacingX, hasBorder); 843 if(newActualSize < sizeInfo.layoutSize) { 844 newActualSize = sizeInfo.layoutSize; 846 sizeInfo.actualSize = newActualSize; 847 } 848 } 849 currentTotal += (newActualSize - oldActualSize); 850 } 851 } 852 } 853 } 854 } 855 } 856 return currentTotal; 857 } 858 859 863 private final void preLayout(int hasBorder, int cellSpacingX, int cellSpacingY, boolean tableWidthKnown) { 864 868 SizeInfo[] colSizes = this.columnSizes; 869 SizeInfo[] rowSizes = this.rowSizes; 870 871 int numRows = rowSizes.length; 873 for(int i = 0; i < numRows; i++) { 874 rowSizes[i].minSize = 0; 875 } 876 877 int numCols = colSizes.length; 879 for(int i = 0; i < numCols; i++) { 880 colSizes[i].layoutSize = 0; 881 } 882 883 ArrayList allCells = this.ALL_CELLS; 884 int numCells = allCells.size(); 885 for(int i = 0; i < numCells; i++) { 886 RTableCell cell = (RTableCell) allCells.get(i); 887 int col = cell.getVirtualColumn(); 888 int colSpan = cell.getColSpan(); 889 int cellsTotalWidth; 890 int cellsUsedWidth; 891 boolean widthDeclared = false; 892 if(colSpan > 1) { 893 cellsUsedWidth = 0; 894 for(int x = 0; x < colSpan; x++) { 895 SizeInfo colSize = colSizes[col + x]; 896 if(colSize.htmlLength != null) { 897 widthDeclared = true; 898 } 899 cellsUsedWidth += colSize.actualSize; 900 } 901 cellsTotalWidth = cellsUsedWidth + (colSpan - 1) * (cellSpacingX + 2 * hasBorder); 902 } 903 else { 904 SizeInfo colSize = colSizes[col]; 905 if(colSize.htmlLength != null) { 906 widthDeclared = true; 907 } 908 cellsUsedWidth = cellsTotalWidth = colSize.actualSize; 909 } 910 911 914 java.awt.Dimension size; 915 RenderThreadState state = RenderThreadState.getState(); 916 boolean prevOverrideNoWrap = state.overrideNoWrap; 917 try { 918 if(!prevOverrideNoWrap) { 919 state.overrideNoWrap = !widthDeclared; 920 } 921 size = cell.doCellLayout(cellsTotalWidth, 0, false, false); 922 } finally { 923 state.overrideNoWrap = prevOverrideNoWrap; 924 } 925 int cellLayoutWidth = size.width; 927 if(colSpan > 1) { 928 if(cellsUsedWidth > 0) { 929 double ratio = (double) cellLayoutWidth / cellsUsedWidth; 930 for(int x = 0; x < colSpan; x++) { 931 SizeInfo si = colSizes[col + x]; 932 int newLayoutSize = (int) Math.round(si.actualSize * ratio); 933 if(si.layoutSize < newLayoutSize) { 934 si.layoutSize = newLayoutSize; 935 } 936 } 937 } 938 else { 939 int newLayoutSize = cellLayoutWidth / colSpan; 940 for(int x = 0; x < colSpan; x++) { 941 SizeInfo si = colSizes[col + x]; 942 if(si.layoutSize < newLayoutSize) { 943 si.layoutSize = newLayoutSize; 944 } 945 } 946 } 947 } 948 else { 949 SizeInfo colSizeInfo = colSizes[col]; 950 if(colSizeInfo.layoutSize < cellLayoutWidth) { 951 colSizeInfo.layoutSize = cellLayoutWidth; 952 } 953 } 954 955 int actualCellHeight = size.height; 957 int row = cell.getVirtualRow(); 958 int rowSpan = cell.getRowSpan(); 959 if(rowSpan > 1) { 960 int vch = (actualCellHeight - (rowSpan - 1) * (cellSpacingY + 2 * hasBorder)) / rowSpan; 961 for(int y = 0; y < rowSpan; y++) { 962 if(rowSizes[row + y].minSize < vch) { 963 rowSizes[row + y].minSize = vch; 964 } 965 } 966 } 967 else { 968 if(rowSizes[row].minSize < actualCellHeight) { 969 rowSizes[row].minSize = actualCellHeight; 970 } 971 } 972 } 973 } 974 975 private void determineRowSizes(int hasBorder, int cellSpacing, int availHeight) { 976 HtmlLength tableHeightLength = TableMatrix.getHeightLength(this.tableElement, availHeight); 977 int tableHeight; 978 SizeInfo[] rowSizes = this.rowSizes; 979 int numRows = rowSizes.length; 980 int heightsOfExtras = this.heightsOfExtras; 981 if(tableHeightLength != null) { 982 tableHeight = tableHeightLength.getLength(availHeight); 983 this.determineRowSizesFixedTH(hasBorder, cellSpacing, availHeight, tableHeight); 984 } 985 else { 986 tableHeight = heightsOfExtras; 987 for(int row = 0; row < numRows; row++) { 988 tableHeight += rowSizes[row].minSize; 989 } 990 this.determineRowSizesFlexibleTH(hasBorder, cellSpacing, availHeight); 991 } 992 } 993 994 private void determineRowSizesFixedTH(int hasBorder, int cellSpacing, int availHeight, int tableHeight) { 995 SizeInfo[] rowSizes = this.rowSizes; 996 int numRows = rowSizes.length; 997 int heightsOfExtras = this.heightsOfExtras; 998 int cellAvailHeight = tableHeight - heightsOfExtras; 999 if(cellAvailHeight < 0) { 1000 cellAvailHeight = 0; 1001 } 1002 1003 1005 int heightUsedbyPercent = 0; 1006 int otherMinSize = 0; 1007 for(int i = 0; i < numRows; i++) { 1008 SizeInfo rowSizeInfo = rowSizes[i]; 1009 HtmlLength heightLength = rowSizeInfo.htmlLength; 1010 if(heightLength != null && heightLength.getLengthType() == HtmlLength.LENGTH) { 1011 int actualSizeInt = heightLength.getLength(cellAvailHeight); 1012 if(actualSizeInt < rowSizeInfo.minSize) { 1013 actualSizeInt = rowSizeInfo.minSize; 1014 } 1015 heightUsedbyPercent += actualSizeInt; 1016 rowSizeInfo.actualSize = actualSizeInt; 1017 } 1018 else { 1019 otherMinSize += rowSizeInfo.minSize; 1020 } 1021 } 1022 1023 1025 if(heightUsedbyPercent + otherMinSize > cellAvailHeight) { 1026 double ratio = (double) (cellAvailHeight - otherMinSize) / heightUsedbyPercent; 1027 for(int i = 0; i < numRows; i++) { 1028 SizeInfo rowSizeInfo = rowSizes[i]; 1029 HtmlLength heightLength = rowSizeInfo.htmlLength; 1030 if(heightLength != null && heightLength.getLengthType() == HtmlLength.LENGTH) { 1031 int actualSize = rowSizeInfo.actualSize; 1032 int prevActualSize = actualSize; 1033 int newActualSize = (int) Math.round(prevActualSize * ratio); 1034 if(newActualSize < rowSizeInfo.minSize) { 1035 newActualSize = rowSizeInfo.minSize; 1036 } 1037 heightUsedbyPercent += (newActualSize - prevActualSize); 1038 rowSizeInfo.actualSize = newActualSize; 1039 } 1040 } 1041 } 1042 1043 1045 int heightUsedByAbsolute = 0; 1046 int noHeightMinSize = 0; 1047 int numNoHeightColumns = 0; 1048 for(int i = 0; i < numRows; i++) { 1049 SizeInfo rowSizeInfo = rowSizes[i]; 1050 HtmlLength heightLength = rowSizeInfo.htmlLength; 1051 if(heightLength != null && heightLength.getLengthType() != HtmlLength.LENGTH) { 1052 int actualSizeInt = heightLength.getRawValue(); 1054 if(actualSizeInt < rowSizeInfo.minSize) { 1055 actualSizeInt = rowSizeInfo.minSize; 1056 } 1057 heightUsedByAbsolute += actualSizeInt; 1058 rowSizeInfo.actualSize = actualSizeInt; 1059 } 1060 else if(heightLength == null) { 1061 numNoHeightColumns++; 1062 noHeightMinSize += rowSizeInfo.minSize; 1063 } 1064 } 1065 1066 1068 if(heightUsedByAbsolute + heightUsedbyPercent + noHeightMinSize > cellAvailHeight) { 1069 double ratio = (double) (cellAvailHeight - noHeightMinSize - heightUsedbyPercent) / heightUsedByAbsolute; 1070 for(int i = 0; i < numRows; i++) { 1071 SizeInfo rowSizeInfo = rowSizes[i]; 1072 HtmlLength heightLength = rowSizeInfo.htmlLength; 1073 if(heightLength != null && heightLength.getLengthType() != HtmlLength.LENGTH) { 1074 int actualSize = rowSizeInfo.actualSize; 1075 int prevActualSize = actualSize; 1076 int newActualSize = (int) Math.round(prevActualSize * ratio); 1077 if(newActualSize < rowSizeInfo.minSize) { 1078 newActualSize = rowSizeInfo.minSize; 1079 } 1080 heightUsedByAbsolute += (newActualSize - prevActualSize); 1081 rowSizeInfo.actualSize = newActualSize; 1082 } 1083 } 1084 } 1085 1086 1088 int remainingHeight = cellAvailHeight - heightUsedByAbsolute - heightUsedbyPercent; 1089 int heightUsedByRemaining = 0; 1090 for(int i = 0; i < numRows; i++) { 1091 SizeInfo rowSizeInfo = rowSizes[i]; 1092 HtmlLength heightLength = rowSizeInfo.htmlLength; 1093 if(heightLength == null) { 1094 int actualSizeInt = remainingHeight / numNoHeightColumns; 1095 if(actualSizeInt < rowSizeInfo.minSize) { 1096 actualSizeInt = rowSizeInfo.minSize; 1097 } 1098 heightUsedByRemaining += actualSizeInt; 1099 rowSizeInfo.actualSize = actualSizeInt; 1100 } 1101 } 1102 1103 1105 int totalUsed = heightUsedByAbsolute + heightUsedbyPercent + heightUsedByRemaining; 1106 if(totalUsed >= cellAvailHeight) { 1107 this.tableHeight = totalUsed + heightsOfExtras; 1108 } 1109 else { 1110 double ratio = (double) cellAvailHeight / totalUsed; 1112 for(int i = 0; i < numRows; i++) { 1113 SizeInfo rowSizeInfo = rowSizes[i]; 1114 int actualSize = rowSizeInfo.actualSize; 1115 rowSizeInfo.actualSize = (int) Math.round(actualSize * ratio); 1116 } 1117 this.tableHeight = tableHeight; 1118 } 1119 1120 1125 this.finalRender(hasBorder, cellSpacing); 1126 } 1127 1128 private void determineRowSizesFlexibleTH(int hasBorder, int cellSpacing, int availHeight) { 1129 SizeInfo[] rowSizes = this.rowSizes; 1130 int numRows = rowSizes.length; 1131 int heightsOfExtras = this.heightsOfExtras; 1132 1133 int heightUsedByAbsolute = 0; 1135 int percentSum = 0; 1136 for(int i = 0; i < numRows; i++) { 1137 SizeInfo rowSizeInfo = rowSizes[i]; 1138 HtmlLength heightLength = rowSizeInfo.htmlLength; 1139 if(heightLength != null && heightLength.getLengthType() == HtmlLength.PIXELS) { 1140 int actualSizeInt = heightLength.getRawValue(); 1142 if(actualSizeInt < rowSizeInfo.minSize) { 1143 actualSizeInt = rowSizeInfo.minSize; 1144 } 1145 heightUsedByAbsolute += actualSizeInt; 1146 rowSizeInfo.actualSize = actualSizeInt; 1147 } 1148 else if(heightLength != null && heightLength.getLengthType() == HtmlLength.LENGTH) { 1149 percentSum += heightLength.getRawValue(); 1150 } 1151 } 1152 1153 int heightUsedByNoSize = 0; 1155 1156 for(int i = 0; i < numRows; i++) { 1158 SizeInfo rowSizeInfo = rowSizes[i]; 1159 HtmlLength widthLength = rowSizeInfo.htmlLength; 1160 if(widthLength == null) { 1161 int actualSizeInt = rowSizeInfo.minSize; 1162 heightUsedByNoSize += actualSizeInt; 1163 rowSizeInfo.actualSize = actualSizeInt; 1164 } 1165 } 1166 1167 int expectedTotalCellHeight = (int) Math.round((heightUsedByAbsolute + heightUsedByNoSize) / (1 - (percentSum / 100.0))); 1169 1170 int heightUsedByPercent = 0; 1172 for(int i = 0; i < numRows; i++) { 1173 SizeInfo rowSizeInfo = rowSizes[i]; 1174 HtmlLength heightLength = rowSizeInfo.htmlLength; 1175 if(heightLength != null && heightLength.getLengthType() == HtmlLength.LENGTH) { 1176 int actualSizeInt = heightLength.getLength(expectedTotalCellHeight); 1177 if(actualSizeInt < rowSizeInfo.minSize) { 1178 actualSizeInt = rowSizeInfo.minSize; 1179 } 1180 heightUsedByPercent += actualSizeInt; 1181 rowSizeInfo.actualSize = actualSizeInt; 1182 } 1183 } 1184 1185 this.tableHeight = heightUsedByAbsolute + heightUsedByNoSize + heightUsedByPercent + heightsOfExtras; 1187 1188 this.finalRender(hasBorder, cellSpacing); 1190 } 1191 1192 1196 private final void finalRender(int hasBorder, int cellSpacing) { 1197 ArrayList allCells = this.ALL_CELLS; 1200 SizeInfo[] colSizes = this.columnSizes; 1201 SizeInfo[] rowSizes = this.rowSizes; 1202 int numCells = allCells.size(); 1203 for(int i = 0; i < numCells; i++) { 1204 RTableCell cell = (RTableCell) allCells.get(i); 1205 int col = cell.getVirtualColumn(); 1206 int colSpan = cell.getColSpan(); 1207 int totalCellWidth; 1208 if(colSpan > 1) { 1209 totalCellWidth = (colSpan - 1) * (cellSpacing + 2 * hasBorder); 1210 for(int x = 0; x < colSpan; x++) { 1211 totalCellWidth += colSizes[col + x].actualSize; 1212 } 1213 } 1214 else { 1215 totalCellWidth = colSizes[col].actualSize; 1216 } 1217 int row = cell.getVirtualRow(); 1218 int rowSpan = cell.getRowSpan(); 1219 int totalCellHeight; 1220 if(rowSpan > 1) { 1221 totalCellHeight = (rowSpan - 1) * (cellSpacing + 2 * hasBorder); 1222 for(int y = 0; y < rowSpan; y++) { 1223 totalCellHeight += rowSizes[row + y].actualSize; 1224 } 1225 } 1226 else { 1227 totalCellHeight = rowSizes[row].actualSize; 1228 } 1229 Dimension size = cell.doCellLayout(totalCellWidth, totalCellHeight, true, true); 1230 if(size.width > totalCellWidth) { 1231 if(colSpan == 1) { 1232 if(size.width > colSizes[col].actualSize) { 1233 colSizes[col].actualSize = size.width; 1234 } 1235 } 1236 else { 1237 } 1239 } 1240 if(size.height > totalCellHeight) { 1241 if(rowSpan == 1) { 1242 if(size.height > rowSizes[row].actualSize) { 1243 rowSizes[row].actualSize = size.height; 1244 } 1245 } 1246 else { 1247 } 1249 } 1250 } 1251 } 1252 1253 1257 public final void doLayout(Insets insets) { 1258 1259 1261 SizeInfo[] rowSizes = this.rowSizes; 1262 int numRows = rowSizes.length; 1263 int yoffset = insets.top; 1264 int cellSpacingY = this.cellSpacingY; 1265 int hasBorder = this.hasOldStyleBorder; 1266 for(int i = 0; i < numRows; i++) { 1267 yoffset += cellSpacingY; 1268 yoffset += hasBorder; 1269 SizeInfo rowSizeInfo = rowSizes[i]; 1270 rowSizeInfo.offset = yoffset; 1271 yoffset += rowSizeInfo.actualSize; 1272 yoffset += hasBorder; 1273 } 1274 this.tableHeight = yoffset + cellSpacingY + insets.bottom; 1275 1276 1278 SizeInfo[] colSizes = this.columnSizes; 1279 int numColumns = colSizes.length; 1280 int xoffset = insets.left; 1281 int cellSpacingX = this.cellSpacingX; 1282 for(int i = 0; i < numColumns; i++) { 1283 xoffset += cellSpacingX; 1284 xoffset += hasBorder; 1285 SizeInfo colSizeInfo = colSizes[i]; 1286 colSizeInfo.offset = xoffset; 1287 xoffset += colSizeInfo.actualSize; 1288 xoffset += hasBorder; 1289 } 1290 this.tableWidth = xoffset + cellSpacingX + insets.right; 1291 1292 1294 ArrayList allCells = this.ALL_CELLS; 1295 int numCells = allCells.size(); 1296 for(int i = 0; i < numCells; i++) { 1297 RTableCell cell = (RTableCell) allCells.get(i); 1298 cell.setCellBounds(colSizes, rowSizes, hasBorder, cellSpacingX, cellSpacingY); 1299 } 1300 } 1301 1302 public final void paint(Graphics g, Dimension size) { 1303 ArrayList allCells = this.ALL_CELLS; 1304 int numCells = allCells.size(); 1305 for(int i = 0; i < numCells; i++) { 1306 RTableCell cell = (RTableCell) allCells.get(i); 1307 Graphics newG = g.create(cell.x, cell.y, cell.width, cell.height); 1309 try { 1310 cell.paint(newG); 1311 } finally { 1312 newG.dispose(); 1313 } 1314 } 1315 1316 if(this.hasOldStyleBorder > 0) { 1317 1328 1330 g.setColor(Color.GRAY); 1331 for(int i = 0; i < numCells; i++) { 1332 RTableCell cell = (RTableCell) allCells.get(i); 1333 int cx = cell.getX() - 1; 1334 int cy = cell.getY() - 1; 1335 int cwidth = cell.getWidth() + 1; 1336 int cheight = cell.getHeight() + 1; 1337 g.drawRect(cx, cy, cwidth, cheight); 1338 } 1339 } 1340 } 1341 1342 1378 1381 public RenderableSpot getLowestRenderableSpot(int x, int y) { 1382 ArrayList allCells = this.ALL_CELLS; 1383 int numCells = allCells.size(); 1384 for(int i = 0; i < numCells; i++) { 1385 RTableCell cell = (RTableCell) allCells.get(i); 1386 Rectangle bounds = cell.getBounds(); 1387 if(bounds.contains(x, y)) { 1388 RenderableSpot rp = cell.getLowestRenderableSpot(x - bounds.x, y - bounds.y); 1389 if(rp != null) { 1390 return rp; 1391 } 1392 } 1393 } 1394 return null; 1395 } 1396 1397 1400 public boolean onMouseClick(MouseEvent event, int x, int y) { 1401 ArrayList allCells = this.ALL_CELLS; 1402 int numCells = allCells.size(); 1403 for(int i = 0; i < numCells; i++) { 1404 RTableCell cell = (RTableCell) allCells.get(i); 1405 Rectangle bounds = cell.getBounds(); 1406 if(bounds.contains(x, y)) { 1407 if(!cell.onMouseClick(event, x - bounds.x, y - bounds.y)) { 1408 return false; 1409 } 1410 break; 1411 } 1412 } 1413 return true; 1414 } 1415 1416 public boolean onDoubleClick(MouseEvent event, int x, int y) { 1417 ArrayList allCells = this.ALL_CELLS; 1418 int numCells = allCells.size(); 1419 for(int i = 0; i < numCells; i++) { 1420 RTableCell cell = (RTableCell) allCells.get(i); 1421 Rectangle bounds = cell.getBounds(); 1422 if(bounds.contains(x, y)) { 1423 if(!cell.onDoubleClick(event, x - bounds.x, y - bounds.y)) { 1424 return false; 1425 } 1426 break; 1427 } 1428 } 1429 return true; 1430 } 1431 1432 private BoundableRenderable armedRenderable; 1433 1434 1437 public boolean onMouseDisarmed(MouseEvent event) { 1438 BoundableRenderable ar = this.armedRenderable; 1439 if(ar != null) { 1440 this.armedRenderable = null; 1441 return ar.onMouseDisarmed(event); 1442 } 1443 else { 1444 return true; 1445 } 1446 } 1447 1448 1451 public boolean onMousePressed(MouseEvent event, int x, int y) { 1452 ArrayList allCells = this.ALL_CELLS; 1453 int numCells = allCells.size(); 1454 for(int i = 0; i < numCells; i++) { 1455 RTableCell cell = (RTableCell) allCells.get(i); 1456 Rectangle bounds = cell.getBounds(); 1457 if(bounds.contains(x, y)) { 1458 if(!cell.onMousePressed(event, x - bounds.x, y - bounds.y)) { 1459 this.armedRenderable = cell; 1460 return false; 1461 } 1462 break; 1463 } 1464 } 1465 return true; 1466 } 1467 1468 1471 public boolean onMouseReleased(MouseEvent event, int x, int y) { 1472 ArrayList allCells = this.ALL_CELLS; 1473 int numCells = allCells.size(); 1474 boolean found = false; 1475 for(int i = 0; i < numCells; i++) { 1476 RTableCell cell = (RTableCell) allCells.get(i); 1477 Rectangle bounds = cell.getBounds(); 1478 if(bounds.contains(x, y)) { 1479 found = true; 1480 BoundableRenderable oldArmedRenderable = this.armedRenderable; 1481 if(oldArmedRenderable != null && cell != oldArmedRenderable) { 1482 oldArmedRenderable.onMouseDisarmed(event); 1483 this.armedRenderable = null; 1484 } 1485 if(!cell.onMouseReleased(event, x - bounds.x, y - bounds.y)) { 1486 return false; 1487 } 1488 break; 1489 } 1490 } 1491 if(!found) { 1492 BoundableRenderable oldArmedRenderable = this.armedRenderable; 1493 if(oldArmedRenderable != null) { 1494 oldArmedRenderable.onMouseDisarmed(event); 1495 this.armedRenderable = null; 1496 } 1497 } 1498 return true; 1499 } 1500 1501 public Iterator getRenderables() { 1502 return this.ALL_CELLS.iterator(); 1503 } 1504 1505 private static class RowsFilter implements NodeFilter { 1506 public final boolean accept(Node node) { 1507 return (node instanceof HTMLTableRowElement); 1508 } 1509 } 1510 1511 private static class ColumnsFilter implements NodeFilter { 1512 public final boolean accept(Node node) { 1513 return (node instanceof HTMLTableCellElement); 1514 } 1515 } 1516 1517 public static class SizeInfo { 1518 public HtmlLength htmlLength; 1519 public int actualSize; 1520 public int layoutSize; 1521 public int minSize; 1522 public int offset; 1523 } 1524} 1525 | Popular Tags |