001/* BlockView.java -- 
002   Copyright (C) 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text.html;
040
041import gnu.javax.swing.text.html.css.Length;
042
043import java.awt.Graphics;
044import java.awt.Rectangle;
045import java.awt.Shape;
046import java.util.HashMap;
047
048import javax.swing.SizeRequirements;
049import javax.swing.event.DocumentEvent;
050import javax.swing.text.AttributeSet;
051import javax.swing.text.BoxView;
052import javax.swing.text.Element;
053import javax.swing.text.View;
054import javax.swing.text.ViewFactory;
055
056/**
057 * @author Lillian Angel <langel@redhat.com>
058 */
059public 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}