1/*
2 * Copyright (c) 1998, 2013, 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 */
25package javax.swing.text.html;
26
27import java.awt.*;
28import java.util.*;
29import javax.swing.*;
30import javax.swing.text.*;
31import 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
46class 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}
327