1 /*
   2  * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing.text.html;
  26 
  27 import java.awt.*;
  28 import java.util.*;
  29 import javax.swing.*;
  30 import javax.swing.text.*;
  31 import javax.swing.event.*;
  32 
  33 /**
  34  * Implements a FrameSetView, intended to support the HTML
  35  * <FRAMESET> tag.  Supports the ROWS and COLS attributes.
  36  *
  37  * @author  Sunita Mani
  38  *
  39  *          Credit also to the hotjava browser engineers that
  40  *          worked on making the allocation of space algorithms
  41  *          conform to the HTML 4.0 standard and also be netscape
  42  *          compatible.
  43  *
  44  */
  45 
  46 class FrameSetView extends javax.swing.text.BoxView {
  47 
  48     String[] children;
  49     int[] percentChildren;
  50     int[] absoluteChildren;
  51     int[] relativeChildren;
  52     int percentTotals;
  53     int absoluteTotals;
  54     int relativeTotals;
  55 
  56     /**
  57      * Constructs a FrameSetView for the given element.
  58      *
  59      * @param elem the element that this view is responsible for
  60      */
  61     public FrameSetView(Element elem, int axis) {
  62         super(elem, axis);
  63         children = null;
  64     }
  65 
  66     /**
  67      * Parses the ROW or COL attributes and returns
  68      * an array of strings that represent the space
  69      * distribution.
  70      *
  71      */
  72     private String[] parseRowColSpec(HTML.Attribute key) {
  73 
  74         AttributeSet attributes = getElement().getAttributes();
  75         String spec = "*";
  76         if (attributes != null) {
  77             if (attributes.getAttribute(key) != null) {
  78                 spec = (String)attributes.getAttribute(key);
  79             }
  80         }
  81 
  82         StringTokenizer tokenizer = new StringTokenizer(spec, ",");
  83         int nTokens = tokenizer.countTokens();
  84         int n = getViewCount();
  85         String[] items = new String[Math.max(nTokens, n)];
  86         int i = 0;
  87         for (; i < nTokens; i++) {
  88             items[i] = tokenizer.nextToken().trim();
  89             // As per the spec, 100% is the same as *
  90             // hence the mapping.
  91             //
  92             if (items[i].equals("100%")) {
  93                 items[i] = "*";
  94             }
  95         }
  96         // extend spec if we have more children than specified
  97         // in ROWS or COLS attribute
  98         for (; i < items.length; i++) {
  99             items[i] = "*";
 100         }
 101         return items;
 102     }
 103 
 104 
 105     /**
 106      * Initializes a number of internal state variables
 107      * that store information about space allocation
 108      * for the frames contained within the frameset.
 109      */
 110     private void init() {
 111         if (getAxis() == View.Y_AXIS) {
 112             children = parseRowColSpec(HTML.Attribute.ROWS);
 113         } else {
 114             children = parseRowColSpec(HTML.Attribute.COLS);
 115         }
 116         percentChildren = new int[children.length];
 117         relativeChildren = new int[children.length];
 118         absoluteChildren = new int[children.length];
 119 
 120         for (int i = 0; i < children.length; i++) {
 121             percentChildren[i] = -1;
 122             relativeChildren[i] = -1;
 123             absoluteChildren[i] = -1;
 124 
 125             if (children[i].endsWith("*")) {
 126                 if (children[i].length() > 1) {
 127                     relativeChildren[i] =
 128                         Integer.parseInt(children[i].substring(
 129                             0, children[i].length()-1).trim());
 130                     relativeTotals += relativeChildren[i];
 131                 } else {
 132                     relativeChildren[i] = 1;
 133                     relativeTotals += 1;
 134                 }
 135             } else if (children[i].indexOf('%') != -1) {
 136                 percentChildren[i] = parseDigits(children[i]);
 137                 percentTotals += percentChildren[i];
 138             } else {
 139                 String value = children[i].toLowerCase();
 140                 if (value.endsWith("px")) {
 141                     value = value.substring(0, value.length()-2).trim();
 142                 }
 143                 absoluteChildren[i] = Integer.parseInt(value);
 144             }
 145         }
 146         if (percentTotals > 100) {
 147             for (int i = 0; i < percentChildren.length; i++) {
 148                 if (percentChildren[i] > 0) {
 149                     percentChildren[i] =
 150                         (percentChildren[i] * 100) / percentTotals;
 151                 }
 152             }
 153             percentTotals = 100;
 154         }
 155     }
 156 
 157     /**
 158      * Perform layout for the major axis of the box (i.e. the
 159      * axis that it represents).  The results of the layout should
 160      * be placed in the given arrays which represent the allocations
 161      * to the children along the major axis.
 162      *
 163      * @param targetSpan the total span given to the view, which
 164      *  would be used to layout the children
 165      * @param axis the axis being layed out
 166      * @param offsets the offsets from the origin of the view for
 167      *  each of the child views; this is a return value and is
 168      *  filled in by the implementation of this method
 169      * @param spans the span of each child view; this is a return
 170      *  value and is filled in by the implementation of this method
 171      * @return the offset and span for each child view in the
 172      *  offsets and spans parameters
 173      */
 174     protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets,
 175                                    int[] spans) {
 176         if (children == null) {
 177             init();
 178         }
 179         SizeRequirements.calculateTiledPositions(targetSpan, null,
 180                                                  getChildRequests(targetSpan,
 181                                                                   axis),
 182                                                  offsets, spans);
 183     }
 184 
 185     protected SizeRequirements[] getChildRequests(int targetSpan, int axis) {
 186 
 187         int[] span = new int[children.length];
 188 
 189         spread(targetSpan, span);
 190         int n = getViewCount();
 191         SizeRequirements[] reqs = new SizeRequirements[n];
 192         for (int i = 0, sIndex = 0; i < n; i++) {
 193             View v = getView(i);
 194             if ((v instanceof FrameView) || (v instanceof FrameSetView)) {
 195                 reqs[i] = new SizeRequirements((int) v.getMinimumSpan(axis),
 196                                                span[sIndex],
 197                                                (int) v.getMaximumSpan(axis),
 198                                                0.5f);
 199                 sIndex++;
 200             } else {
 201                 int min = (int) v.getMinimumSpan(axis);
 202                 int pref = (int) v.getPreferredSpan(axis);
 203                 int max = (int) v.getMaximumSpan(axis);
 204                 float a = v.getAlignment(axis);
 205                 reqs[i] = new SizeRequirements(min, pref, max, a);
 206             }
 207         }
 208         return reqs;
 209     }
 210 
 211 
 212     /**
 213      * This method is responsible for returning in span[] the
 214      * span for each child view along the major axis.  it
 215      * computes this based on the information that extracted
 216      * from the value of the ROW/COL attribute.
 217      */
 218     private void spread(int targetSpan, int[] span) {
 219 
 220         if (targetSpan == 0) {
 221             return;
 222         }
 223 
 224         int tempSpace = 0;
 225         int remainingSpace = targetSpan;
 226 
 227         // allocate the absolute's first, they have
 228         // precedence
 229         //
 230         for (int i = 0; i < span.length; i++) {
 231             if (absoluteChildren[i] > 0) {
 232                 span[i] = absoluteChildren[i];
 233                 remainingSpace -= span[i];
 234             }
 235         }
 236 
 237         // then deal with percents.
 238         //
 239         tempSpace = remainingSpace;
 240         for (int i = 0; i < span.length; i++) {
 241             if (percentChildren[i] > 0 && tempSpace > 0) {
 242                 span[i] = (percentChildren[i] * tempSpace) / 100;
 243                 remainingSpace -= span[i];
 244             } else if (percentChildren[i] > 0 && tempSpace <= 0) {
 245                 span[i] = targetSpan / span.length;
 246                 remainingSpace -= span[i];
 247             }
 248         }
 249 
 250         // allocate remainingSpace to relative
 251         if (remainingSpace > 0 && relativeTotals > 0) {
 252             for (int i = 0; i < span.length; i++) {
 253                 if (relativeChildren[i] > 0) {
 254                     span[i] = (remainingSpace *
 255                                 relativeChildren[i]) / relativeTotals;
 256                 }
 257             }
 258         } else if (remainingSpace > 0) {
 259             // There are no relative columns and the space has been
 260             // under- or overallocated.  In this case, turn all the
 261             // percentage and pixel specified columns to percentage
 262             // columns based on the ratio of their pixel count to the
 263             // total "virtual" size. (In the case of percentage columns,
 264             // the pixel count would equal the specified percentage
 265             // of the screen size.
 266 
 267             // This action is in accordance with the HTML
 268             // 4.0 spec (see section 8.3, the end of the discussion of
 269             // the FRAMESET tag).  The precedence of percentage and pixel
 270             // specified columns is unclear (spec seems to indicate that
 271             // they share priority, however, unspecified what happens when
 272             // overallocation occurs.)
 273 
 274             // addendum is that we behave similar to netscape in that specified
 275             // widths have precedance over percentage widths...
 276 
 277             float vTotal = (float)(targetSpan - remainingSpace);
 278             float[] tempPercents = new float[span.length];
 279             remainingSpace = targetSpan;
 280             for (int i = 0; i < span.length; i++) {
 281                 // ok we know what our total space is, and we know how large each
 282                 // column should be relative to each other... therefore we can use
 283                 // that relative information to deduce their percentages of a whole
 284                 // and then scale them appropriately for the correct size
 285                 tempPercents[i] = ((float)span[i] / vTotal) * 100.00f;
 286                 span[i] = (int) ( ((float)targetSpan * tempPercents[i]) / 100.00f);
 287                 remainingSpace -= span[i];
 288             }
 289 
 290 
 291             // this is for just in case there is something left over.. if there is we just
 292             // add it one pixel at a time to the frames in order.. We shouldn't really ever get
 293             // here and if we do it shouldn't be with more than 1 pixel, maybe two.
 294             int i = 0;
 295             while (remainingSpace != 0) {
 296                 if (remainingSpace < 0) {
 297                     span[i++]--;
 298                     remainingSpace++;
 299                 }
 300                 else {
 301                     span[i++]++;
 302                     remainingSpace--;
 303                 }
 304 
 305                 // just in case there are more pixels than frames...should never happen..
 306                 if (i == span.length)i = 0;
 307             }
 308         }
 309     }
 310 
 311     /*
 312      * Users have been known to type things like "%25" and "25 %".  Deal
 313      * with it.
 314      */
 315     private int parseDigits(String mixedStr) {
 316         int result = 0;
 317         for (int i = 0; i < mixedStr.length(); i++) {
 318             char ch = mixedStr.charAt(i);
 319             if (Character.isDigit(ch)) {
 320                 result = (result * 10) + Character.digit(ch, 10);
 321             }
 322         }
 323         return result;
 324     }
 325 
 326 }