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 }