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