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.myfaces.tobago.component.SupportsMarkup;
23  import org.apache.myfaces.tobago.context.ClientProperties;
24  import org.apache.myfaces.tobago.internal.layout.BankHead;
25  import org.apache.myfaces.tobago.internal.layout.Cell;
26  import org.apache.myfaces.tobago.internal.layout.FactorList;
27  import org.apache.myfaces.tobago.internal.layout.Grid;
28  import org.apache.myfaces.tobago.internal.layout.Interval;
29  import org.apache.myfaces.tobago.internal.layout.IntervalList;
30  import org.apache.myfaces.tobago.internal.layout.LayoutUtils;
31  import org.apache.myfaces.tobago.internal.layout.OriginCell;
32  import org.apache.myfaces.tobago.internal.util.StringUtils;
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.ComponentUtils;
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      final List<LayoutComponent> components = getLayoutContainer().getComponents();
71      for (final 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 && (component.isRendered() || isRigid())) {
79          ((LayoutContainer) component).getLayoutManager().init();
80        }
81      }
82  
83      grid.setColumnOverflow(isColumnOverflow());
84      grid.setRowOverflow(isRowOverflow());
85    }
86  
87    public void fixRelativeInsideAuto(final Orientation orientation, final boolean auto) {
88  
89      if (!getLayoutContainer().isLayoutChildren()) {
90        return;
91      }
92  
93      final BankHead[] heads = grid.getBankHeads(orientation);
94      final 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         final 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           final OriginCell origin = (OriginCell) cell;
119           final LayoutComponent component = cell.getComponent();
120           if (component instanceof LayoutContainer && component.isRendered()) {
121             final LayoutManager layoutManager = ((LayoutContainer) component).getLayoutManager();
122             // TODO: may be improved
123             final boolean childAuto
124                 = origin.getSpan(orientation) == 1 && heads[i].getToken() instanceof AutoLayoutToken;
125             layoutManager.fixRelativeInsideAuto(orientation, childAuto);
126           }
127         }
128       }
129       if (neitherRendered && !isRigid()) {
130         heads[i].setRendered(false);
131       }
132     }
133   }
134 
135   public void preProcessing(final Orientation orientation) {
136 
137     if (!getLayoutContainer().isLayoutChildren()) {
138       return;
139     }
140 
141     final BankHead[] heads = grid.getBankHeads(orientation);
142     final BankHead[] heads2 = grid.getBankHeads(orientation.other());
143 
144     // process auto tokens
145     int i = 0;
146 
147     for (final BankHead head : heads) {
148       final LayoutToken token = head.getToken();
149 
150       if (token instanceof PixelLayoutToken) {
151         if (head.isRendered() || isRigid()) {
152           heads[i].setCurrent(((PixelLayoutToken) token).getMeasure());
153         } else {
154           heads[i].setCurrent(Measure.ZERO);
155         }
156       }
157 
158       final IntervalList intervalList = new IntervalList();
159       for (int j = 0; j < heads2.length; j++) {
160         final Cell cell = grid.getCell(i, j, orientation);
161         if (cell instanceof OriginCell) {
162           final OriginCell origin = (OriginCell) cell;
163           final LayoutComponent component = cell.getComponent();
164 
165           if (component instanceof LayoutContainer && (component.isRendered() || isRigid())) {
166             ((LayoutContainer) component).getLayoutManager().preProcessing(orientation);
167           }
168 
169           if (token instanceof AutoLayoutToken || token instanceof RelativeLayoutToken) {
170             if ((component.isRendered() || isRigid())) {
171               if (origin.getSpan(orientation) == 1) {
172                 intervalList.add(new Interval(component, orientation));
173               } else {
174                 if (LOG.isDebugEnabled()) {
175                   LOG.debug("Components with span > 1 will be ignored in 'auto' layout rows/columns.");
176                 }
177               }
178             }
179           }
180         }
181       }
182 
183       intervalList.evaluate();
184       if (token instanceof AutoLayoutToken || token instanceof RelativeLayoutToken) {
185         heads[i].setIntervalList(intervalList);
186       }
187       if (token instanceof AutoLayoutToken) {
188         if (heads[i].isRendered()) {
189           if (intervalList.size() > 0) {
190             heads[i].setCurrent(intervalList.getCurrent());
191           } else {
192             heads[i].setCurrent(Measure.valueOf(100));
193             LOG.warn("Found an 'auto' token in {} definition, but there is no component inside with span = 1! "
194                 + "So the value for 'auto' can't be evaluated (clientId={}). Using 100px.",
195                 orientation == Orientation.HORIZONTAL ? "columns" : "rows",
196                 getClientId(getFacesContext()));
197           }
198         } else {
199           heads[i].setCurrent(Measure.ZERO);
200         }
201       }
202       i++;
203     }
204 
205 /*
206     IntervalList relatives = new IntervalList();
207     for (BankHead head : heads) {
208       LayoutToken token = head.getToken();
209       if (token instanceof RelativeLayoutToken) {
210         final int factor = ((RelativeLayoutToken) token).getFactor();
211         for (Interval interval : head.getIntervalList()) {
212           relatives.add(new Interval(interval, factor));
213         }
214       }
215     }
216     relatives.evaluate();
217 */
218 
219     // set the size if all sizes of the grid are set
220     Measure sum = Measure.ZERO;
221     for (final BankHead head : heads) {
222       Measure size = null;
223       final LayoutToken token = head.getToken();
224       if (token instanceof RelativeLayoutToken) {
225 //        final int factor = ((RelativeLayoutToken) token).getFactor();
226 //        size = relatives.getCurrent().multiply(factor);
227       } else {
228         size = head.getCurrent();
229       }
230       if (size == null) {
231         sum = null; // set to invalid
232         break;
233 //        LOG.error("TODO: Should not happen!");
234       }
235       sum = sum.add(size);
236     }
237     if (sum != null) {
238       // adding the space between the cells
239       sum = sum.add(LayoutUtils.getBorderBegin(orientation, getLayoutContainer()));
240       sum = sum.add(LayoutUtils.getPaddingBegin(orientation, getLayoutContainer()));
241       sum = sum.add(getMarginBegin(orientation));
242       sum = sum.add(computeSpacing(orientation, 0, heads.length));
243       sum = sum.add(getMarginEnd(orientation));
244       sum = sum.add(LayoutUtils.getPaddingEnd(orientation, getLayoutContainer()));
245       sum = sum.add(LayoutUtils.getBorderEnd(orientation, getLayoutContainer()));
246       LayoutUtils.setCurrentSize(orientation, getLayoutContainer(), sum);
247     }
248   }
249 
250   public void mainProcessing(final Orientation orientation) {
251 
252     if (!getLayoutContainer().isLayoutChildren()) {
253       return;
254     }
255 
256     final BankHead[] heads = grid.getBankHeads(orientation);
257     final BankHead[] heads2 = grid.getBankHeads(orientation.other());
258 
259     // find *
260     final FactorList factorList = new FactorList();
261     for (final BankHead head : heads) {
262       if (head.getToken() instanceof RelativeLayoutToken && head.isRendered()) {
263         factorList.add(((RelativeLayoutToken) head.getToken()).getFactor());
264       }
265     }
266     if (!factorList.isEmpty()) {
267       // find rest
268       final LayoutContainer container = getLayoutContainer();
269       Measure available = LayoutUtils.getCurrentSize(orientation, container);
270       if (available != null) {
271         for (final BankHead head : heads) {
272           available = available.subtractNotNegative(head.getCurrent());
273         }
274         available = available.subtractNotNegative(LayoutUtils.getBorderBegin(orientation, container));
275         available = available.subtractNotNegative(LayoutUtils.getPaddingBegin(orientation, container));
276         available = available.subtractNotNegative(getMarginBegin(orientation));
277         available = available.subtractNotNegative(computeSpacing(orientation, 0, heads.length));
278         available = available.subtractNotNegative(getMarginEnd(orientation));
279         available = available.subtractNotNegative(LayoutUtils.getPaddingEnd(orientation, container));
280         available = available.subtractNotNegative(LayoutUtils.getBorderEnd(orientation, container));
281 
282         if (grid.isOverflow(orientation.other())) {
283           final ClientProperties client = ClientProperties.getInstance(FacesContext.getCurrentInstance());
284           final Measure scrollbar = orientation
285               == Orientation.HORIZONTAL ? client.getVerticalScrollbarWeight() : client.getHorizontalScrollbarWeight();
286           available = available.subtractNotNegative(scrollbar);
287         }
288 
289         final List<Measure> partition = factorList.partition(available);
290 
291         // write values back into the header
292         int i = 0; // index of head
293         int j = 0; // index of partition
294         for (final BankHead head : heads) {
295           if (head.getToken() instanceof RelativeLayoutToken && head.isRendered()) {
296             // respect the minimum
297             heads[i].setCurrent(Measure.max(partition.get(j), heads[i].getIntervalList().getMinimum()));
298             j++;
299           }
300           i++;
301         }
302       } else {
303         LOG.warn("No width/height set but needed for *!"); // todo: more information
304       }
305     }
306 
307     // call manage sizes for all sub-layout-managers
308     for (int i = 0; i < heads.length; i++) {
309       for (int j = 0; j < heads2.length; j++) {
310         final Cell cell = grid.getCell(i, j, orientation);
311         if (cell instanceof OriginCell) {
312           final LayoutComponent component = cell.getComponent();
313 
314           component.setDisplay(Display.BLOCK); // TODO: use CSS via classes and tobago.css
315 
316           final Integer span = ((OriginCell) cell).getSpan(orientation);
317 
318           // compute the size of the cell
319           Measure size = Measure.ZERO;
320           for (int k = 0; k < span; k++) {
321             size = size.add(heads[i + k].getCurrent());
322           }
323           size = size.add(computeSpacing(orientation, i, span));
324           final Measure current = LayoutUtils.getCurrentSize(orientation, component);
325           if (current == null) {
326             LayoutUtils.setCurrentSize(orientation, component, size);
327           }
328 
329           // call sub layout manager
330           if (component instanceof LayoutContainer && (component.isRendered() || isRigid())) {
331             ((LayoutContainer) component).getLayoutManager().mainProcessing(orientation);
332           }
333         }
334       }
335     }
336 
337     Measure size = Measure.ZERO;
338     size = size.add(LayoutUtils.getPaddingBegin(orientation, getLayoutContainer()));
339     size = size.add(getMarginBegin(orientation));
340     size = size.add(computeSpacing(orientation, 0, heads.length));
341     for (final BankHead head : heads) {
342       size = size.add(head.getCurrent());
343     }
344     size = size.add(getMarginEnd(orientation));
345     size = size.add(LayoutUtils.getPaddingEnd(orientation, getLayoutContainer()));
346     if (size.greaterThan(LayoutUtils.getCurrentSize(orientation, getLayoutContainer()))) {
347       grid.setOverflow(true, orientation);
348     }
349   }
350 
351   public void postProcessing(final Orientation orientation) {
352 
353     if (!getLayoutContainer().isLayoutChildren()) {
354       return;
355     }
356 
357     final BankHead[] heads = grid.getBankHeads(orientation);
358     final BankHead[] heads2 = grid.getBankHeads(orientation.other());
359 
360     // call manage sizes for all sub-layout-managers
361     for (int i = 0; i < heads.length; i++) {
362       for (int j = 0; j < heads2.length; j++) {
363         final Cell cell = grid.getCell(i, j, orientation);
364         if (cell instanceof OriginCell) {
365           final LayoutComponent component = cell.getComponent();
366 
367           component.setDisplay(Display.BLOCK);
368 
369           // compute the position of the cell
370           Measure position = Measure.ZERO;
371           position = position.add(LayoutUtils.getPaddingBegin(orientation, getLayoutContainer()));
372           position = position.add(getMarginBegin(orientation));
373           for (int k = 0; k < i; k++) {
374             if (heads[k] != null
375                 && heads[k].getCurrent() != null
376                 && heads[k].isRendered()
377                 && heads[k].getCurrent().greaterThan(Measure.ZERO)) {
378               position = position.add(heads[k].getCurrent());
379               position = position.add(getSpacing(orientation));
380             }
381           }
382           if (orientation == Orientation.HORIZONTAL) {
383             component.setLeft(position);
384           } else {
385             component.setTop(position);
386           }
387 
388           // call sub layout manager
389           if (component instanceof LayoutContainer && (component.isRendered() || isRigid())) {
390             ((LayoutContainer) component).getLayoutManager().postProcessing(orientation);
391           }
392 
393           // set scrolling type
394           final boolean scroll = grid.isOverflow(orientation);
395           if (orientation == Orientation.HORIZONTAL) {
396             getLayoutContainer().setOverflowX(scroll);
397           } else {
398             getLayoutContainer().setOverflowY(scroll);
399           }
400 
401           // todo: optimize: the AutoLayoutTokens with columnSpan=1 are already called
402         }
403       }
404     }
405   }
406 
407   private LayoutContainer getLayoutContainer() {
408     return ComponentUtils.findAncestor(getParent(), LayoutContainer.class);
409   }
410 
411   public Measure getSpacing(final Orientation orientation) {
412     return orientation == Orientation.HORIZONTAL ? getColumnSpacing() : getRowSpacing();
413   }
414 
415   public Measure getMarginBegin(final Orientation orientation) {
416     return orientation == Orientation.HORIZONTAL ? getMarginLeft() : getMarginTop();
417   }
418 
419   public Measure getMarginEnd(final Orientation orientation) {
420     return orientation == Orientation.HORIZONTAL ? getMarginRight() : getMarginBottom();
421   }
422 
423   /**
424    * Compute the sum of the space between the cells.
425    * There is one "space" less than cells that are not void.
426    */
427   private Measure computeSpacing(final Orientation orientation, final int startIndex, final int length) {
428 
429     final BankHead[] heads = grid.getBankHeads(orientation);
430 
431     int count = 0;
432     for (int i = startIndex; i < startIndex + length; i++) {
433       if ((heads[i].isRendered())
434           && (heads[i].getCurrent() == null || heads[i].getCurrent().greaterThan(Measure.ZERO))) {
435         count++;
436       }
437     }
438     if (count > 0) {
439       return getSpacing(orientation).multiply(count - 1);
440     } else {
441       return Measure.ZERO;
442     }
443   }
444 
445   public abstract String getRows();
446 
447   public abstract void setRows(String rows);
448 
449   public abstract String getColumns();
450 
451   public abstract void setColumns(String columns);
452 
453   @Deprecated
454   public abstract Measure getCellspacing();
455 
456   public abstract Measure getRowSpacing();
457 
458   public abstract Measure getColumnSpacing();
459 
460   public abstract Measure getMarginLeft();
461 
462   public abstract Measure getMarginTop();
463 
464   public abstract Measure getMarginRight();
465 
466   public abstract Measure getMarginBottom();
467 
468   public abstract boolean isColumnOverflow();
469 
470   public abstract boolean isRowOverflow();
471 
472   public abstract boolean isRigid();
473 
474   @Override
475   public boolean getRendersChildren() {
476     return false;
477   }
478 
479   public String toString(final int depth) {
480     final StringBuilder builder = new StringBuilder();
481     builder.append(getClass().getSimpleName()).append("#");
482     builder.append(getClientId(FacesContext.getCurrentInstance()));
483     builder.append("\n");
484     if (grid != null) {
485       builder.append(StringUtils.repeat("  ", depth + 4));
486       builder.append("horiz.: ");
487       BankHead[] heads = grid.getBankHeads(Orientation.HORIZONTAL);
488       for (int i = 0; i < heads.length; i++) {
489         if (i != 0) {
490           builder.append(StringUtils.repeat("  ", depth + 4 + 4));
491         }
492         builder.append(heads[i]);
493         builder.append("\n");
494       }
495       builder.append(StringUtils.repeat("  ", depth + 4));
496       builder.append("verti.: ");
497       heads = grid.getBankHeads(Orientation.VERTICAL);
498       for (int i = 0; i < heads.length; i++) {
499         if (i != 0) {
500           builder.append(StringUtils.repeat("  ", depth + 4 + 4));
501         }
502         builder.append(heads[i]);
503         builder.append("\n");
504       }
505     }
506     builder.setLength(builder.length() - 1);
507     return builder.toString();
508   }
509 
510   @Override
511   public String toString() {
512     return toString(0);
513   }
514 }