001 /* BlockView.java -- 002 Copyright (C) 2005 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing.text.html; 040 041 import gnu.javax.swing.text.html.css.Length; 042 043 import java.awt.Graphics; 044 import java.awt.Rectangle; 045 import java.awt.Shape; 046 import java.util.HashMap; 047 048 import javax.swing.SizeRequirements; 049 import javax.swing.event.DocumentEvent; 050 import javax.swing.text.AttributeSet; 051 import javax.swing.text.BoxView; 052 import javax.swing.text.Element; 053 import javax.swing.text.View; 054 import javax.swing.text.ViewFactory; 055 056 /** 057 * @author Lillian Angel <langel@redhat.com> 058 */ 059 public class BlockView extends BoxView 060 { 061 062 /** 063 * Stores information about child positioning according to the 064 * CSS attributes position, left, right, top and bottom. 065 */ 066 private static class PositionInfo 067 { 068 // TODO: Use enums when available. 069 070 /** 071 * Static positioning. This is the default and is thus rarely really 072 * used. 073 */ 074 static final int STATIC = 0; 075 076 /** 077 * Relative positioning. The box is teaked relative to its static 078 * computed bounds. 079 */ 080 static final int RELATIVE = 1; 081 082 /** 083 * Absolute positioning. The box is moved relative to the parent's box. 084 */ 085 static final int ABSOLUTE = 2; 086 087 /** 088 * Like ABSOLUTE, with some fixation against the viewport (not yet 089 * implemented). 090 */ 091 static final int FIXED = 3; 092 093 /** 094 * The type according to the constants of this class. 095 */ 096 int type; 097 098 /** 099 * The left constraint, null if not set. 100 */ 101 Length left; 102 103 /** 104 * The right constraint, null if not set. 105 */ 106 Length right; 107 108 /** 109 * The top constraint, null if not set. 110 */ 111 Length top; 112 113 /** 114 * The bottom constraint, null if not set. 115 */ 116 Length bottom; 117 118 /** 119 * Creates a new PositionInfo object. 120 * 121 * @param typ the type to set 122 * @param l the left constraint 123 * @param r the right constraint 124 * @param t the top constraint 125 * @param b the bottom constraint 126 */ 127 PositionInfo(int typ, Length l, Length r, Length t, Length b) 128 { 129 type = typ; 130 left = l; 131 right = r; 132 top = t; 133 bottom = b; 134 } 135 } 136 137 /** 138 * The attributes for this view. 139 */ 140 private AttributeSet attributes; 141 142 /** 143 * The box painter for this view. 144 * 145 * This is package private because the TableView needs access to it. 146 */ 147 StyleSheet.BoxPainter painter; 148 149 /** 150 * The width and height as specified in the stylesheet, null if not 151 * specified. The first value is the X_AXIS, the second the Y_AXIS. You 152 * can index this directly by the X_AXIS and Y_AXIS constants. 153 */ 154 private Length[] cssSpans; 155 156 /** 157 * Stores additional CSS layout information. 158 */ 159 private HashMap positionInfo; 160 161 /** 162 * Creates a new view that represents an html box. 163 * This can be used for a number of elements. 164 * 165 * @param elem - the element to create a view for 166 * @param axis - either View.X_AXIS or View.Y_AXIS 167 */ 168 public BlockView(Element elem, int axis) 169 { 170 super(elem, axis); 171 cssSpans = new Length[2]; 172 positionInfo = new HashMap(); 173 } 174 175 /** 176 * Creates the parent view for this. It is called before 177 * any other methods, if the parent view is working properly. 178 * Implemented to forward to the superclass and call 179 * setPropertiesFromAttributes to set the paragraph 180 * properties. 181 * 182 * @param parent - the new parent, or null if the view 183 * is being removed from a parent it was added to. 184 */ 185 public void setParent(View parent) 186 { 187 super.setParent(parent); 188 189 if (parent != null) 190 setPropertiesFromAttributes(); 191 } 192 193 /** 194 * Calculates the requirements along the major axis. 195 * This is implemented to call the superclass and then 196 * adjust it if the CSS width or height attribute is specified 197 * and applicable. 198 * 199 * @param axis - the axis to check the requirements for. 200 * @param r - the SizeRequirements. If null, one is created. 201 * @return the new SizeRequirements object. 202 */ 203 protected SizeRequirements calculateMajorAxisRequirements(int axis, 204 SizeRequirements r) 205 { 206 if (r == null) 207 r = new SizeRequirements(); 208 209 if (setCSSSpan(r, axis)) 210 { 211 // If we have set the span from CSS, then we need to adjust 212 // the margins. 213 SizeRequirements parent = super.calculateMajorAxisRequirements(axis, 214 null); 215 int margin = axis == X_AXIS ? getLeftInset() + getRightInset() 216 : getTopInset() + getBottomInset(); 217 r.minimum -= margin; 218 r.preferred -= margin; 219 r.maximum -= margin; 220 constrainSize(axis, r, parent); 221 } 222 else 223 r = super.calculateMajorAxisRequirements(axis, r); 224 return r; 225 } 226 227 /** 228 * Calculates the requirements along the minor axis. 229 * This is implemented to call the superclass and then 230 * adjust it if the CSS width or height attribute is specified 231 * and applicable. 232 * 233 * @param axis - the axis to check the requirements for. 234 * @param r - the SizeRequirements. If null, one is created. 235 * @return the new SizeRequirements object. 236 */ 237 protected SizeRequirements calculateMinorAxisRequirements(int axis, 238 SizeRequirements r) 239 { 240 if (r == null) 241 r = new SizeRequirements(); 242 243 if (setCSSSpan(r, axis)) 244 { 245 // If we have set the span from CSS, then we need to adjust 246 // the margins. 247 SizeRequirements parent = super.calculateMinorAxisRequirements(axis, 248 null); 249 int margin = axis == X_AXIS ? getLeftInset() + getRightInset() 250 : getTopInset() + getBottomInset(); 251 r.minimum -= margin; 252 r.preferred -= margin; 253 r.maximum -= margin; 254 constrainSize(axis, r, parent); 255 } 256 else 257 r = super.calculateMinorAxisRequirements(axis, r); 258 259 // Apply text alignment if appropriate. 260 if (axis == X_AXIS) 261 { 262 Object o = getAttributes().getAttribute(CSS.Attribute.TEXT_ALIGN); 263 if (o != null) 264 { 265 String al = o.toString().trim(); 266 if (al.equals("center")) 267 r.alignment = 0.5f; 268 else if (al.equals("right")) 269 r.alignment = 1.0f; 270 else 271 r.alignment = 0.0f; 272 } 273 } 274 return r; 275 } 276 277 /** 278 * Sets the span on the SizeRequirements object according to the 279 * according CSS span value, when it is set. 280 * 281 * @param r the size requirements 282 * @param axis the axis 283 * 284 * @return <code>true</code> when the CSS span has been set, 285 * <code>false</code> otherwise 286 */ 287 private boolean setCSSSpan(SizeRequirements r, int axis) 288 { 289 boolean ret = false; 290 Length span = cssSpans[axis]; 291 // We can't set relative CSS spans here because we don't know 292 // yet about the allocated span. Instead we use the view's 293 // normal requirements. 294 if (span != null && ! span.isPercentage()) 295 { 296 r.minimum = (int) span.getValue(); 297 r.preferred = (int) span.getValue(); 298 r.maximum = (int) span.getValue(); 299 ret = true; 300 } 301 return ret; 302 } 303 304 /** 305 * Constrains the <code>r</code> requirements according to 306 * <code>min</code>. 307 * 308 * @param axis the axis 309 * @param r the requirements to constrain 310 * @param min the constraining requirements 311 */ 312 private void constrainSize(int axis, SizeRequirements r, 313 SizeRequirements min) 314 { 315 if (min.minimum > r.minimum) 316 { 317 r.minimum = min.minimum; 318 r.preferred = min.minimum; 319 r.maximum = Math.max(r.maximum, min.maximum); 320 } 321 } 322 323 /** 324 * Lays out the box along the minor axis (the axis that is 325 * perpendicular to the axis that it represents). The results 326 * of the layout are placed in the given arrays which are 327 * the allocations to the children along the minor axis. 328 * 329 * @param targetSpan - the total span given to the view, also 330 * used to layout the children. 331 * @param axis - the minor axis 332 * @param offsets - the offsets from the origin of the view for 333 * all the child views. This is a return value and is filled in by this 334 * function. 335 * @param spans - the span of each child view. This is a return value and is 336 * filled in by this function. 337 */ 338 protected void layoutMinorAxis(int targetSpan, int axis, 339 int[] offsets, int[] spans) 340 { 341 int viewCount = getViewCount(); 342 for (int i = 0; i < viewCount; i++) 343 { 344 View view = getView(i); 345 int min = (int) view.getMinimumSpan(axis); 346 int max; 347 // Handle CSS span value of child. 348 Length length = cssSpans[axis]; 349 if (length != null) 350 { 351 min = Math.max((int) length.getValue(targetSpan), min); 352 max = min; 353 } 354 else 355 max = (int) view.getMaximumSpan(axis); 356 357 if (max < targetSpan) 358 { 359 // Align child. 360 float align = view.getAlignment(axis); 361 offsets[i] = (int) ((targetSpan - max) * align); 362 spans[i] = max; 363 } 364 else 365 { 366 offsets[i] = 0; 367 spans[i] = Math.max(min, targetSpan); 368 } 369 370 // Adjust according to CSS position info. 371 positionView(targetSpan, axis, i, offsets, spans); 372 } 373 } 374 375 /** 376 * Overridden to perform additional CSS layout (absolute/relative 377 * positioning). 378 */ 379 protected void layoutMajorAxis(int targetSpan, int axis, 380 int[] offsets, int[] spans) 381 { 382 super.layoutMajorAxis(targetSpan, axis, offsets, spans); 383 384 // Adjust according to CSS position info. 385 int viewCount = getViewCount(); 386 for (int i = 0; i < viewCount; i++) 387 { 388 positionView(targetSpan, axis, i, offsets, spans); 389 } 390 } 391 392 /** 393 * Positions a view according to any additional CSS constraints. 394 * 395 * @param targetSpan the target span 396 * @param axis the axis 397 * @param i the index of the view 398 * @param offsets the offsets get placed here 399 * @param spans the spans get placed here 400 */ 401 private void positionView(int targetSpan, int axis, int i, int[] offsets, 402 int[] spans) 403 { 404 View view = getView(i); 405 PositionInfo pos = (PositionInfo) positionInfo.get(view); 406 if (pos != null) 407 { 408 int p0 = -1; 409 int p1 = -1; 410 if (axis == X_AXIS) 411 { 412 Length l = pos.left; 413 if (l != null) 414 p0 = (int) l.getValue(targetSpan); 415 l = pos.right; 416 if (l != null) 417 p1 = (int) l.getValue(targetSpan); 418 } 419 else 420 { 421 Length l = pos.top; 422 if (l != null) 423 p0 = (int) l.getValue(targetSpan); 424 l = pos.bottom; 425 if (l != null) 426 p1 = (int) l.getValue(targetSpan); 427 } 428 if (pos.type == PositionInfo.ABSOLUTE 429 || pos.type == PositionInfo.FIXED) 430 { 431 if (p0 != -1) 432 { 433 offsets[i] = p0; 434 if (p1 != -1) 435 { 436 // Overrides computed width. (Possibly overconstrained 437 // when the width attribute was set too.) 438 spans[i] = targetSpan - p1 - offsets[i]; 439 } 440 } 441 else if (p1 != -1) 442 { 443 // Preserve any computed width. 444 offsets[i] = targetSpan - p1 - spans[i]; 445 } 446 } 447 else if (pos.type == PositionInfo.RELATIVE) 448 { 449 if (p0 != -1) 450 { 451 offsets[i] += p0; 452 if (p1 != -1) 453 { 454 // Overrides computed width. (Possibly overconstrained 455 // when the width attribute was set too.) 456 spans[i] = spans[i] - p0 - p1 - offsets[i]; 457 } 458 } 459 else if (p1 != -1) 460 { 461 // Preserve any computed width. 462 offsets[i] -= p1; 463 } 464 } 465 } 466 } 467 468 /** 469 * Paints using the given graphics configuration and shape. 470 * This delegates to the css box painter to paint the 471 * border and background prior to the interior. 472 * 473 * @param g - Graphics configuration 474 * @param a - the Shape to render into. 475 */ 476 public void paint(Graphics g, Shape a) 477 { 478 Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds(); 479 480 // Debug output. Shows blocks in green rectangles. 481 // g.setColor(Color.GREEN); 482 // g.drawRect(rect.x, rect.y, rect.width, rect.height); 483 484 painter.paint(g, rect.x, rect.y, rect.width, rect.height, this); 485 super.paint(g, a); 486 } 487 488 /** 489 * Fetches the attributes to use when painting. 490 * 491 * @return the attributes of this model. 492 */ 493 public AttributeSet getAttributes() 494 { 495 if (attributes == null) 496 attributes = getStyleSheet().getViewAttributes(this); 497 return attributes; 498 } 499 500 /** 501 * Gets the resize weight. 502 * 503 * @param axis - the axis to get the resize weight for. 504 * @return the resize weight. 505 * @throws IllegalArgumentException - for an invalid axis 506 */ 507 public int getResizeWeight(int axis) throws IllegalArgumentException 508 { 509 // Can't resize the Y_AXIS 510 if (axis == Y_AXIS) 511 return 0; 512 if (axis == X_AXIS) 513 return 1; 514 throw new IllegalArgumentException("Invalid Axis"); 515 } 516 517 /** 518 * Gets the alignment. 519 * 520 * @param axis - the axis to get the alignment for. 521 * @return the alignment. 522 */ 523 public float getAlignment(int axis) 524 { 525 if (axis == X_AXIS) 526 return super.getAlignment(axis); 527 if (axis == Y_AXIS) 528 { 529 if (getViewCount() == 0) 530 return 0.0F; 531 float prefHeight = getPreferredSpan(Y_AXIS); 532 View first = getView(0); 533 float firstRowHeight = first.getPreferredSpan(Y_AXIS); 534 return prefHeight != 0 ? (firstRowHeight * first.getAlignment(Y_AXIS)) 535 / prefHeight 536 : 0; 537 } 538 throw new IllegalArgumentException("Invalid Axis"); 539 } 540 541 /** 542 * Gives notification from the document that attributes were 543 * changed in a location that this view is responsible for. 544 * 545 * @param ev - the change information 546 * @param a - the current shape of the view 547 * @param f - the factory to use to rebuild if the view has children. 548 */ 549 public void changedUpdate(DocumentEvent ev, 550 Shape a, ViewFactory f) 551 { 552 super.changedUpdate(ev, a, f); 553 554 // If more elements were added, then need to set the properties for them 555 int currPos = ev.getOffset(); 556 if (currPos <= getStartOffset() 557 && (currPos + ev.getLength()) >= getEndOffset()) 558 setPropertiesFromAttributes(); 559 } 560 561 /** 562 * Determines the preferred span along the axis. 563 * 564 * @param axis - the view to get the preferred span for. 565 * @return the span the view would like to be painted into >=0/ 566 * The view is usually told to paint into the span that is returned, 567 * although the parent may choose to resize or break the view. 568 * @throws IllegalArgumentException - for an invalid axis 569 */ 570 public float getPreferredSpan(int axis) throws IllegalArgumentException 571 { 572 if (axis == X_AXIS || axis == Y_AXIS) 573 return super.getPreferredSpan(axis); 574 throw new IllegalArgumentException("Invalid Axis"); 575 } 576 577 /** 578 * Determines the minimum span along the axis. 579 * 580 * @param axis - the axis to get the minimum span for. 581 * @return the span the view would like to be painted into >=0/ 582 * The view is usually told to paint into the span that is returned, 583 * although the parent may choose to resize or break the view. 584 * @throws IllegalArgumentException - for an invalid axis 585 */ 586 public float getMinimumSpan(int axis) throws IllegalArgumentException 587 { 588 if (axis == X_AXIS || axis == Y_AXIS) 589 return super.getMinimumSpan(axis); 590 throw new IllegalArgumentException("Invalid Axis"); 591 } 592 593 /** 594 * Determines the maximum span along the axis. 595 * 596 * @param axis - the axis to get the maximum span for. 597 * @return the span the view would like to be painted into >=0/ 598 * The view is usually told to paint into the span that is returned, 599 * although the parent may choose to resize or break the view. 600 * @throws IllegalArgumentException - for an invalid axis 601 */ 602 public float getMaximumSpan(int axis) throws IllegalArgumentException 603 { 604 if (axis == X_AXIS || axis == Y_AXIS) 605 return super.getMaximumSpan(axis); 606 throw new IllegalArgumentException("Invalid Axis"); 607 } 608 609 /** 610 * Updates any cached values that come from attributes. 611 */ 612 protected void setPropertiesFromAttributes() 613 { 614 // Fetch attributes. 615 StyleSheet ss = getStyleSheet(); 616 attributes = ss.getViewAttributes(this); 617 618 // Fetch painter. 619 painter = ss.getBoxPainter(attributes); 620 621 // Update insets. 622 if (attributes != null) 623 { 624 setInsets((short) painter.getInset(TOP, this), 625 (short) painter.getInset(LEFT, this), 626 (short) painter.getInset(BOTTOM, this), 627 (short) painter.getInset(RIGHT, this)); 628 } 629 630 // Fetch width and height. 631 float emBase = ss.getEMBase(attributes); 632 float exBase = ss.getEXBase(attributes); 633 cssSpans[X_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.WIDTH); 634 if (cssSpans[X_AXIS] != null) 635 cssSpans[X_AXIS].setFontBases(emBase, exBase); 636 cssSpans[Y_AXIS] = (Length) attributes.getAttribute(CSS.Attribute.HEIGHT); 637 if (cssSpans[Y_AXIS] != null) 638 cssSpans[Y_AXIS].setFontBases(emBase, exBase); 639 } 640 641 /** 642 * Gets the default style sheet. 643 * 644 * @return the style sheet 645 */ 646 protected StyleSheet getStyleSheet() 647 { 648 HTMLDocument doc = (HTMLDocument) getDocument(); 649 return doc.getStyleSheet(); 650 } 651 652 /** 653 * Overridden to fetch additional CSS layout information. 654 */ 655 public void replace(int offset, int length, View[] views) 656 { 657 // First remove unneeded stuff. 658 for (int i = 0; i < length; i++) 659 { 660 View child = getView(i + offset); 661 positionInfo.remove(child); 662 } 663 664 // Call super to actually replace the views. 665 super.replace(offset, length, views); 666 667 // Now fetch the position infos for the new views. 668 for (int i = 0; i < views.length; i++) 669 { 670 fetchLayoutInfo(views[i]); 671 } 672 } 673 674 /** 675 * Fetches and stores the layout info for the specified view. 676 * 677 * @param view the view for which the layout info is stored 678 */ 679 private void fetchLayoutInfo(View view) 680 { 681 AttributeSet atts = view.getAttributes(); 682 Object o = atts.getAttribute(CSS.Attribute.POSITION); 683 if (o != null && o instanceof String && ! o.equals("static")) 684 { 685 int type; 686 if (o.equals("relative")) 687 type = PositionInfo.RELATIVE; 688 else if (o.equals("absolute")) 689 type = PositionInfo.ABSOLUTE; 690 else if (o.equals("fixed")) 691 type = PositionInfo.FIXED; 692 else 693 type = PositionInfo.STATIC; 694 695 if (type != PositionInfo.STATIC) 696 { 697 StyleSheet ss = getStyleSheet(); 698 float emBase = ss.getEMBase(atts); 699 float exBase = ss.getEXBase(atts); 700 Length left = (Length) atts.getAttribute(CSS.Attribute.LEFT); 701 if (left != null) 702 left.setFontBases(emBase, exBase); 703 Length right = (Length) atts.getAttribute(CSS.Attribute.RIGHT); 704 if (right != null) 705 right.setFontBases(emBase, exBase); 706 Length top = (Length) atts.getAttribute(CSS.Attribute.TOP); 707 if (top != null) 708 top.setFontBases(emBase, exBase); 709 Length bottom = (Length) atts.getAttribute(CSS.Attribute.BOTTOM); 710 if (bottom != null) 711 bottom.setFontBases(emBase, exBase); 712 if (left != null || right != null || top != null || bottom != null) 713 { 714 PositionInfo pos = new PositionInfo(type, left, right, top, 715 bottom); 716 positionInfo.put(view, pos); 717 } 718 } 719 } 720 } 721 }