View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.component;
21  
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.myfaces.tobago.component.SupportsMarkup;
24  import org.apache.myfaces.tobago.context.ClientProperties;
25  import org.apache.myfaces.tobago.internal.layout.BankHead;
26  import org.apache.myfaces.tobago.internal.layout.Cell;
27  import org.apache.myfaces.tobago.internal.layout.FactorList;
28  import org.apache.myfaces.tobago.internal.layout.Grid;
29  import org.apache.myfaces.tobago.internal.layout.Interval;
30  import org.apache.myfaces.tobago.internal.layout.IntervalList;
31  import org.apache.myfaces.tobago.internal.layout.LayoutUtils;
32  import org.apache.myfaces.tobago.internal.layout.OriginCell;
33  import org.apache.myfaces.tobago.layout.AutoLayoutToken;
34  import org.apache.myfaces.tobago.layout.Display;
35  import org.apache.myfaces.tobago.layout.LayoutComponent;
36  import org.apache.myfaces.tobago.layout.LayoutContainer;
37  import org.apache.myfaces.tobago.layout.LayoutManager;
38  import org.apache.myfaces.tobago.layout.LayoutToken;
39  import org.apache.myfaces.tobago.layout.LayoutTokens;
40  import org.apache.myfaces.tobago.layout.Measure;
41  import org.apache.myfaces.tobago.layout.Orientation;
42  import org.apache.myfaces.tobago.layout.PixelLayoutToken;
43  import org.apache.myfaces.tobago.layout.RelativeLayoutToken;
44  import org.apache.myfaces.tobago.util.VariableResolverUtils;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import javax.faces.context.FacesContext;
49  import java.util.List;
50  
51  public abstract class AbstractUIGridLayout extends AbstractUILayoutBase implements LayoutManager, SupportsMarkup {
52  
53    private static final Logger LOG = LoggerFactory.getLogger(AbstractUIGridLayout.class);
54  
55    public static final String COMPONENT_FAMILY = "org.apache.myfaces.tobago.GridLayout";
56  
57    private Grid grid;
58  
59    /**
60     * Initialize the grid and remove the current width and height values from the component, recursively.
61     */
62    public void init() {
63  
64      if (!getLayoutContainer().isLayoutChildren()) {
65        return;
66      }
67  
68      grid = new Grid(LayoutTokens.parse(getColumns()), LayoutTokens.parse(getRows()));
69  
70      List<LayoutComponent> components = getLayoutContainer().getComponents();
71      for (LayoutComponent component : components) {
72        component.setCurrentHeight(null);
73        component.setCurrentWidth(null);
74        grid.add(new OriginCell(component), component.getColumnSpan(), component.getRowSpan());
75        if (LOG.isDebugEnabled()) {
76          LOG.debug("\n" + grid);
77        }
78        if (component instanceof LayoutContainer) {
79          ((LayoutContainer) component).getLayoutManager().init();
80        }
81      }
82  
83      grid.setColumnOverflow(isColumnOverflow());
84      grid.setRowOverflow(isRowOverflow());
85    }
86  
87    public void fixRelativeInsideAuto(Orientation orientation, boolean auto) {
88  
89      if (!getLayoutContainer().isLayoutChildren()) {
90        return;
91      }
92  
93      BankHead[] heads = grid.getBankHeads(orientation);
94      BankHead[] heads2 = grid.getBankHeads(orientation.other());
95  
96      if (auto) {
97        for (int i = 0; i < heads.length; i++) {
98          if (heads[i].getToken() instanceof RelativeLayoutToken) {
99            if (LOG.isDebugEnabled()) {
100             LOG.debug("Fixing layout token from * to auto, because a * in not allowed inside of a auto. "
101                 + "Token * at index=" + i + ", orientation=" + orientation + ", grid=\n" + grid);
102           }
103           heads[i].setToken(AutoLayoutToken.INSTANCE);
104         }
105       }
106     }
107 
108     for (int i = 0; i < heads.length; i++) {
109       boolean neitherRendered = true;
110       for (int j = 0; j < heads2.length; j++) {
111         Cell cell = grid.getCell(i, j, orientation);
112         // check rendered = false
113         if (cell != null && cell.getComponent().isRendered()) {
114           neitherRendered = false;
115         }
116         // recursion
117         if (cell instanceof OriginCell) {
118           OriginCell origin = (OriginCell) cell;
119           LayoutComponent component = cell.getComponent();
120           if (component instanceof LayoutContainer) {
121             LayoutManager layoutManager = ((LayoutContainer) component).getLayoutManager();
122             // TODO: may be improved
123             boolean childAuto = origin.getSpan(orientation) == 1 && heads[i].getToken() instanceof AutoLayoutToken;
124             layoutManager.fixRelativeInsideAuto(orientation, childAuto);
125           }
126         }
127       }
128       if (neitherRendered) {
129         heads[i].setRendered(false);
130       }
131     }
132   }
133 
134   public void preProcessing(Orientation orientation) {
135 
136     if (!getLayoutContainer().isLayoutChildren()) {
137       return;
138     }
139 
140     final BankHead[] heads = grid.getBankHeads(orientation);
141     final BankHead[] heads2 = grid.getBankHeads(orientation.other());
142 
143     // process auto tokens
144     int i = 0;
145 
146     for (BankHead head : heads) {
147       LayoutToken token = head.getToken();
148 
149       if (token instanceof PixelLayoutToken && head.isRendered()) {
150         int pixel = ((PixelLayoutToken) token).getPixel();
151         heads[i].setCurrent(Measure.valueOf(pixel)); // XXX refactor
152       }
153 
154       IntervalList intervalList = new IntervalList();
155       for (int j = 0; j < heads2.length; j++) {
156         Cell cell = grid.getCell(i, j, orientation);
157         if (cell instanceof OriginCell) {
158           OriginCell origin = (OriginCell) cell;
159           LayoutComponent component = cell.getComponent();
160 
161           if (component instanceof LayoutContainer) {
162             ((LayoutContainer) component).getLayoutManager().preProcessing(orientation);
163           }
164 
165           if (token instanceof AutoLayoutToken || token instanceof RelativeLayoutToken) {
166             if (origin.getSpan(orientation) == 1 && component.isRendered()) {
167               intervalList.add(new Interval(component, orientation));
168             } else {
169               if (LOG.isDebugEnabled()) {
170                 LOG.debug("Components with span > 1 will be ignored in 'auto' layout rows/columns.");
171                 // todo: give this information to the developer
172               }
173             }
174           }
175         }
176       }
177 
178       intervalList.evaluate();
179       if (token instanceof AutoLayoutToken || token instanceof RelativeLayoutToken) {
180         heads[i].setIntervalList(intervalList);
181       }
182       if (token instanceof AutoLayoutToken) {
183         heads[i].setCurrent(intervalList.getCurrent());
184       }
185       i++;
186     }
187 
188 /*
189     IntervalList relatives = new IntervalList();
190     for (BankHead head : heads) {
191       LayoutToken token = head.getToken();
192       if (token instanceof RelativeLayoutToken) {
193         final int factor = ((RelativeLayoutToken) token).getFactor();
194         for (Interval interval : head.getIntervalList()) {
195           relatives.add(new Interval(interval, factor));
196         }
197       }
198     }
199     relatives.evaluate();
200 */
201 
202     // set the size if all sizes of the grid are set
203     Measure sum = Measure.ZERO;
204     for (BankHead head : heads) {
205       Measure size = null;
206       final LayoutToken token = head.getToken();
207       if (token instanceof RelativeLayoutToken) {
208 //        final int factor = ((RelativeLayoutToken) token).getFactor();
209 //        size = relatives.getCurrent().multiply(factor);
210       } else {
211         size = head.getCurrent();
212       }
213       if (size == null) {
214         sum = null; // set to invalid
215         break;
216 //        LOG.error("TODO: Should not happen!");
217       }
218       sum = sum.add(size);
219     }
220     if (sum != null) {
221       // adding the space between the cells
222       sum = sum.add(LayoutUtils.getBorderBegin(orientation, getLayoutContainer()));
223       sum = sum.add(LayoutUtils.getPaddingBegin(orientation, getLayoutContainer()));
224       sum = sum.add(getMarginBegin(orientation));
225       sum = sum.add(computeSpacing(orientation, 0, heads.length));
226       sum = sum.add(getMarginEnd(orientation));
227       sum = sum.add(LayoutUtils.getPaddingEnd(orientation, getLayoutContainer()));
228       sum = sum.add(LayoutUtils.getBorderEnd(orientation, getLayoutContainer()));
229       LayoutUtils.setCurrentSize(orientation, getLayoutContainer(), sum);
230     }
231   }
232 
233   public void mainProcessing(Orientation orientation) {
234 
235     if (!getLayoutContainer().isLayoutChildren()) {
236       return;
237     }
238 
239     final BankHead[] heads = grid.getBankHeads(orientation);
240     final BankHead[] heads2 = grid.getBankHeads(orientation.other());
241 
242     // find *
243     FactorList factorList = new FactorList();
244     for (BankHead head : heads) {
245       if (head.getToken() instanceof RelativeLayoutToken && head.isRendered()) {
246         factorList.add(((RelativeLayoutToken) head.getToken()).getFactor());
247       }
248     }
249     if (!factorList.isEmpty()) {
250       // find rest
251       LayoutContainer container = getLayoutContainer();
252       Measure available = LayoutUtils.getCurrentSize(orientation, container);
253       if (available != null) {
254         for (BankHead head : heads) {
255           available = available.subtractNotNegative(head.getCurrent());
256         }
257         available = available.subtractNotNegative(LayoutUtils.getBorderBegin(orientation, container));
258         available = available.subtractNotNegative(LayoutUtils.getPaddingBegin(orientation, container));
259         available = available.subtractNotNegative(getMarginBegin(orientation));
260         available = available.subtractNotNegative(computeSpacing(orientation, 0, heads.length));
261         available = available.subtractNotNegative(getMarginEnd(orientation));
262         available = available.subtractNotNegative(LayoutUtils.getPaddingEnd(orientation, container));
263         available = available.subtractNotNegative(LayoutUtils.getBorderEnd(orientation, container));
264 
265         if (grid.isOverflow(orientation.other())) {
266           ClientProperties client = VariableResolverUtils.resolveClientProperties(FacesContext.getCurrentInstance());
267           final Measure scrollbar = orientation
268               == Orientation.HORIZONTAL ? client.getVerticalScrollbarWeight() : client.getHorizontalScrollbarWeight();
269           available = available.subtractNotNegative(scrollbar);
270         }
271 
272         List<Measure> partition = factorList.partition(available);
273 
274         // write values back into the header
275         int i = 0; // index of head
276         int j = 0; // index of partition
277         for (BankHead head : heads) {
278           if (head.getToken() instanceof RelativeLayoutToken && head.isRendered()) {
279             // respect the minimum
280             heads[i].setCurrent(Measure.max(partition.get(j), heads[i].getIntervalList().getMinimum()));
281             j++;
282           }
283           i++;
284         }
285       } else {
286         LOG.warn("No width/height set but needed for *!"); // todo: more information
287       }
288     }
289 
290     // call manage sizes for all sub-layout-managers
291     for (int i = 0; i < heads.length; i++) {
292       for (int j = 0; j < heads2.length; j++) {
293         Cell cell = grid.getCell(i, j, orientation);
294         if (cell instanceof OriginCell) {
295           LayoutComponent component = cell.getComponent();
296 
297           component.setDisplay(Display.BLOCK); // TODO: use CSS via classes and tobago.css
298 
299           Integer span = ((OriginCell) cell).getSpan(orientation);
300 
301           // compute the size of the cell
302           Measure size = Measure.ZERO;
303           for (int k = 0; k < span; k++) {
304             size = size.add(heads[i + k].getCurrent());
305           }
306           size = size.add(computeSpacing(orientation, i, span));
307           Measure current = LayoutUtils.getCurrentSize(orientation, component);
308           if (current == null) {
309             LayoutUtils.setCurrentSize(orientation, component, size);
310           }
311 
312           // call sub layout manager
313           if (component instanceof LayoutContainer) {
314             ((LayoutContainer) component).getLayoutManager().mainProcessing(orientation);
315           }
316         }
317       }
318     }
319 
320     Measure size = Measure.ZERO;
321     size = size.add(LayoutUtils.getPaddingBegin(orientation, getLayoutContainer()));
322     size = size.add(getMarginBegin(orientation));
323     size = size.add(computeSpacing(orientation, 0, heads.length));
324     for (BankHead head : heads) {
325       size = size.add(head.getCurrent());
326     }
327     size = size.add(getMarginEnd(orientation));
328     size = size.add(LayoutUtils.getPaddingEnd(orientation, getLayoutContainer()));
329     if (size.greaterThan(LayoutUtils.getCurrentSize(orientation, getLayoutContainer()))) {
330       grid.setOverflow(true, orientation);
331     }
332   }
333 
334   public void postProcessing(Orientation orientation) {
335 
336     if (!getLayoutContainer().isLayoutChildren()) {
337       return;
338     }
339 
340     final BankHead[] heads = grid.getBankHeads(orientation);
341     final BankHead[] heads2 = grid.getBankHeads(orientation.other());
342 
343     // call manage sizes for all sub-layout-managers
344     for (int i = 0; i < heads.length; i++) {
345       for (int j = 0; j < heads2.length; j++) {
346         Cell cell = grid.getCell(i, j, orientation);
347         if (cell instanceof OriginCell) {
348           LayoutComponent component = cell.getComponent();
349 
350           component.setDisplay(Display.BLOCK);
351 
352           // compute the position of the cell
353           Measure position = Measure.ZERO;
354           position = position.add(LayoutUtils.getPaddingBegin(orientation, getLayoutContainer()));
355           position = position.add(getMarginBegin(orientation));
356           for (int k = 0; k < i; k++) {
357             if (heads[k] != null
358                 && heads[k].getCurrent() != null
359                 && heads[k].isRendered()
360                 && heads[k].getCurrent().greaterThan(Measure.ZERO)) {
361               position = position.add(heads[k].getCurrent());
362               position = position.add(getSpacing(orientation));
363             }
364           }
365           if (orientation == Orientation.HORIZONTAL) {
366             component.setLeft(position);
367           } else {
368             component.setTop(position);
369           }
370 
371           // call sub layout manager
372           if (component instanceof LayoutContainer) {
373             ((LayoutContainer) component).getLayoutManager().postProcessing(orientation);
374           }
375 
376           // set scrolling type
377           final boolean scroll = grid.isOverflow(orientation);
378           if (orientation == Orientation.HORIZONTAL) {
379             getLayoutContainer().setOverflowX(scroll);
380           } else {
381             getLayoutContainer().setOverflowY(scroll);
382           }
383 
384           // todo: optimize: the AutoLayoutTokens with columnSpan=1 are already called
385         }
386       }
387     }
388   }
389 
390   private LayoutContainer getLayoutContainer() {
391     // todo: check with instanceof and do something in the error case
392     return ((LayoutContainer) getParent());
393   }
394 
395   public Measure getSpacing(Orientation orientation) {
396     return orientation == Orientation.HORIZONTAL ? getColumnSpacing() : getRowSpacing();
397   }
398 
399   public Measure getMarginBegin(Orientation orientation) {
400     return orientation == Orientation.HORIZONTAL ? getMarginLeft() : getMarginTop();
401   }
402 
403   public Measure getMarginEnd(Orientation orientation) {
404     return orientation == Orientation.HORIZONTAL ? getMarginRight() : getMarginBottom();
405   }
406 
407   /**
408    * Compute the sum of the space between the cells.
409    * There is one "space" less than cells that are not void.
410    */
411   private Measure computeSpacing(Orientation orientation, int startIndex, int length) {
412 
413     final BankHead[] heads = grid.getBankHeads(orientation);
414 
415     int count = 0;
416     for (int i = startIndex; i < startIndex + length; i++) {
417       if ((heads[i].isRendered())
418           && (heads[i].getCurrent() == null || heads[i].getCurrent().greaterThan(Measure.ZERO))) {
419         count++;
420       }
421     }
422     if (count > 0) {
423       return getSpacing(orientation).multiply(count - 1);
424     } else {
425       return Measure.ZERO;
426     }
427   }
428 
429   public abstract String getRows();
430 
431   public abstract void setRows(String rows);
432 
433   public abstract String getColumns();
434 
435   public abstract void setColumns(String columns);
436 
437   @Deprecated
438   public abstract Measure getCellspacing();
439 
440   public abstract Measure getRowSpacing();
441 
442   public abstract Measure getColumnSpacing();
443 
444   public abstract Measure getMarginLeft();
445 
446   public abstract Measure getMarginTop();
447 
448   public abstract Measure getMarginRight();
449 
450   public abstract Measure getMarginBottom();
451 
452   public abstract boolean isColumnOverflow();
453 
454   public abstract boolean isRowOverflow();
455 
456   @Override
457   public boolean getRendersChildren() {
458     return false;
459   }
460 
461   public String toString(int depth) {
462     StringBuilder builder = new StringBuilder();
463     builder.append(getClass().getSimpleName()).append("#");
464     builder.append(getClientId(FacesContext.getCurrentInstance()));
465     builder.append("\n");
466     if (grid != null) {
467       builder.append(StringUtils.repeat("  ", depth + 4));
468       builder.append("horiz.: ");
469       BankHead[] heads = grid.getBankHeads(Orientation.HORIZONTAL);
470       for (int i = 0; i < heads.length; i++) {
471         if (i != 0) {
472           builder.append(StringUtils.repeat("  ", depth + 4 + 4));
473         }
474         builder.append(heads[i]);
475         builder.append("\n");
476       }
477       builder.append(StringUtils.repeat("  ", depth + 4));
478       builder.append("verti.: ");
479       heads = grid.getBankHeads(Orientation.VERTICAL);
480       for (int i = 0; i < heads.length; i++) {
481         if (i != 0) {
482           builder.append(StringUtils.repeat("  ", depth + 4 + 4));
483         }
484         builder.append(heads[i]);
485         builder.append("\n");
486       }
487     }
488     builder.setLength(builder.length() - 1);
489     return builder.toString();
490   }
491 
492   @Override
493   public String toString() {
494     return toString(0);
495   }
496 }