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    }