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