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.Attributes;
23  import org.apache.myfaces.tobago.internal.layout.Grid;
24  import org.apache.myfaces.tobago.internal.layout.IntervalList;
25  import org.apache.myfaces.tobago.internal.layout.LayoutUtils;
26  import org.apache.myfaces.tobago.internal.layout.OriginCell;
27  import org.apache.myfaces.tobago.internal.util.StringUtils;
28  import org.apache.myfaces.tobago.layout.AutoLayoutToken;
29  import org.apache.myfaces.tobago.layout.LayoutBox;
30  import org.apache.myfaces.tobago.layout.LayoutComponent;
31  import org.apache.myfaces.tobago.layout.LayoutContainer;
32  import org.apache.myfaces.tobago.layout.LayoutManager;
33  import org.apache.myfaces.tobago.layout.LayoutToken;
34  import org.apache.myfaces.tobago.layout.LayoutTokens;
35  import org.apache.myfaces.tobago.layout.Measure;
36  import org.apache.myfaces.tobago.layout.Orientation;
37  import org.apache.myfaces.tobago.layout.RelativeLayoutToken;
38  import org.apache.myfaces.tobago.model.SheetState;
39  import org.apache.myfaces.tobago.renderkit.LayoutComponentRenderer;
40  import org.apache.myfaces.tobago.util.LayoutInfo;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  import javax.faces.component.UIColumn;
45  import javax.faces.component.UIComponent;
46  import javax.faces.context.FacesContext;
47  import java.util.List;
48  import java.util.Map;
49  
50  /**
51   * XXX: Not completely implemented yet.
52   */ 
53  public abstract class AbstractUISheetLayout extends AbstractUILayoutBase implements LayoutManager {
54  
55    private static final Logger LOG = LoggerFactory.getLogger(AbstractUISheetLayout.class);
56  
57    private boolean horizontalAuto;
58    private boolean verticalAuto;
59  
60    public void init() {
61  
62      layoutHeader();
63  
64      for (final LayoutComponent component : getLayoutContainer().getComponents()) {
65        if (component instanceof LayoutContainer && component.isRendered()) {
66          ((LayoutContainer) component).getLayoutManager().init();
67        }
68      }
69    }
70  
71    public void fixRelativeInsideAuto(final Orientation orientation, final boolean auto) {
72  
73      if (orientation == Orientation.HORIZONTAL) {
74        horizontalAuto = auto;
75      } else {
76        verticalAuto = auto;
77      }
78  
79      for (final LayoutComponent component : getLayoutContainer().getComponents()) {
80        if (component instanceof LayoutContainer && component.isRendered()) {
81          ((LayoutContainer) component).getLayoutManager().fixRelativeInsideAuto(orientation, auto);
82        }
83      }
84    }
85  
86    public void preProcessing(final Orientation orientation) {
87  
88      // process auto tokens
89      final IntervalList intervals = new IntervalList();
90      for (final LayoutComponent component : getLayoutContainer().getComponents()) {
91  
92        if (component != null) {
93          if (component instanceof LayoutContainer && component.isRendered()) {
94            ((LayoutContainer) component).getLayoutManager().preProcessing(orientation);
95          }
96  
97  /*
98          if (orientation == Orientation.HORIZONTAL && horizontalAuto
99              || orientation == Orientation.VERTICAL && verticalAuto) {
100           intervals.add(new Interval(component, orientation));
101         }
102 */
103       }
104     }
105 
106 /*
107     if (intervals.size() >= 1) {
108       intervals.evaluate();
109       Measure size = intervals.getCurrent();
110       size = size.add(LayoutUtils.getBorderBegin(orientation, getLayoutContainer()));
111       size = size.add(LayoutUtils.getBorderEnd(orientation, getLayoutContainer()));
112       LayoutUtils.setCurrentSize(orientation, getLayoutContainer(), size);
113     }
114 */
115   }
116 
117   public void mainProcessing(final Orientation orientation) {
118 
119     // find *
120     if (orientation == Orientation.HORIZONTAL && !horizontalAuto 
121         || orientation == Orientation.VERTICAL && !verticalAuto) {
122 
123       final FacesContext facesContext = FacesContext.getCurrentInstance();
124       final LayoutContainer container = getLayoutContainer();
125       final AbstractUISheet sheet = (AbstractUISheet) container;
126 
127       if (orientation == Orientation.HORIZONTAL) {
128 
129         ensureColumnWidthList(facesContext, sheet);
130 
131         final List<Integer> widthList = sheet.getWidthList();
132 
133         int index = 0;
134         for (final LayoutComponent component : sheet.getComponents()) {
135           if (component == null) {
136             if (LOG.isDebugEnabled()) {
137               LOG.debug("todo: UIColumnSelector must be a LayoutComponent!"); // fixme
138             }
139             index++;
140             continue;
141           }
142           final UIColumn column = component instanceof AbstractUIColumnNode
143               ? (UIColumn) component
144               : (UIColumn) ((UIComponent) component).getParent();
145           if (!column.isRendered()) {
146             // XXX here not index++, because the widthList has only the rendered=true, todo: change it.
147             continue;
148           }
149           if (column instanceof LayoutBox) {
150             final LayoutBox box = (LayoutBox) column;
151             Measure width = Measure.valueOf(widthList.get(index));
152             width = width.subtractNotNegative(LayoutUtils.getBorderBegin(orientation, box));
153             width = width.subtractNotNegative(LayoutUtils.getPaddingBegin(orientation, box));
154             width = width.subtractNotNegative(LayoutUtils.getPaddingEnd(orientation, box));
155             width = width.subtractNotNegative(LayoutUtils.getBorderEnd(orientation, box));
156             final LayoutComponentRenderer renderer = sheet.getLayoutComponentRenderer(facesContext);
157             width = width.subtractNotNegative(renderer.getCustomMeasure(facesContext, sheet, "columnSeparator"));
158             LayoutUtils.setCurrentSize(orientation, component, width);
159             // call sub layout manager
160             if (component instanceof LayoutContainer) {
161               ((LayoutContainer) component).getLayoutManager().mainProcessing(orientation);
162             }
163           }
164           index++;
165         }
166       }
167     }
168   }
169 
170   public void postProcessing(final Orientation orientation) {
171 
172     final AbstractUISheet sheet = (AbstractUISheet) getLayoutContainer();
173 
174     // set positions to all sub-layout-managers
175 
176     for (final LayoutComponent component : sheet.getComponents()) {
177 
178       if (component != null) {
179         // compute the position of the cell
180         final Measure position = LayoutUtils.getBorderBegin(orientation, sheet);
181         if (orientation == Orientation.HORIZONTAL) {
182           component.setLeft(position);
183         } else {
184           component.setTop(position);
185         }
186 
187         // call sub layout manager
188         if (component instanceof LayoutContainer && component.isRendered()) {
189           ((LayoutContainer) component).getLayoutManager().postProcessing(orientation);
190         }
191 
192         // todo: optimize: the AutoLayoutTokens with columnSpan=1 are already called
193       }
194     }
195   }
196 
197   private LayoutContainer getLayoutContainer() {
198     // todo: check with instanceof and do something in the error case
199     return ((LayoutContainer) getParent());
200   }
201 
202   @Override
203   public boolean getRendersChildren() {
204     return false;
205   }
206 
207   private void ensureColumnWidthList(final FacesContext facesContext, final AbstractUISheet data) {
208     List<Integer> currentWidthList = null;
209     // TODO: Refactor: here be should use "getColumns()" instead of "getRenderedColumns()"
210     final List<AbstractUIColumn> renderedColumns = data.getRenderedColumns();
211 
212     final Map attributes = data.getAttributes();
213     String widthListString = null;
214     final SheetState state = data.getSheetState(facesContext);
215     if (state != null) {
216       widthListString = state.getColumnWidths();
217     }
218     if (widthListString == null) {
219       widthListString = (String) attributes.get(Attributes.WIDTH_LIST_STRING);
220     }
221 
222     if (widthListString != null) {
223       try {
224         currentWidthList = StringUtils.parseIntegerList(widthListString);
225       } catch (final NumberFormatException e) {
226         LOG.warn("Unexpected value for column width list: '" + widthListString + "'");
227       }
228     }
229     if (currentWidthList != null && currentWidthList.size() != renderedColumns.size() + 1) {
230       currentWidthList = null;
231     }
232 
233     Measure space = data.getCurrentWidth();
234     final LayoutComponentRenderer renderer = data.getLayoutComponentRenderer(facesContext);
235     space = space.subtractNotNegative(renderer.getBorderLeft(facesContext, data));
236     space = space.subtractNotNegative(renderer.getBorderRight(facesContext, data));
237     Measure verticalScrollbarWeight = renderer.getVerticalScrollbarWeight(facesContext, data);
238     boolean needVerticalScrollbar = needVerticalScrollbar(facesContext, data);
239     if (needVerticalScrollbar) {
240       space = space.subtractNotNegative(verticalScrollbarWeight);
241     }
242 
243     if (currentWidthList == null) {
244       final LayoutTokens tokens = data.getColumnLayout();
245       final List<AbstractUIColumn> allColumns = data.getAllColumns();
246       final LayoutTokens newTokens = new LayoutTokens();
247       for (int i = 0; i < allColumns.size(); i++) {
248         final AbstractUIColumn column = allColumns.get(i);
249         if (column.isRendered()) {
250           if (tokens == null) {
251             if (column instanceof AbstractUIColumn && column.getWidth() != null) {
252               newTokens.addToken(LayoutTokens.parseToken(column.getWidth().serialize()));
253             } else {
254               newTokens.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
255             }
256           } else {
257             if (i < tokens.getSize()) {
258               newTokens.addToken(tokens.get(i));
259             } else {
260               newTokens.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
261             }
262           }
263         }
264       }
265 
266 /*
267       // todo: not nice: 1 left + 1 right border
268       space = space.subtract(renderedColumns.size() * 2);
269 */
270       final LayoutInfo layoutInfo =
271           new LayoutInfo(newTokens.getSize(), space.getPixel(), newTokens, data.getClientId(facesContext), false);
272       final Measure columnSelectorWidth
273           = data.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, data, "columnSelectorWidth");
274       parseFixedWidth(layoutInfo, renderedColumns, columnSelectorWidth);
275       layoutInfo.parseColumnLayout(space.getPixel());
276       currentWidthList = layoutInfo.getSpaceList();
277       if (needVerticalScrollbar) {
278         currentWidthList.add(verticalScrollbarWeight.getPixel()); // filler column
279       } else {
280         currentWidthList.add(0); // empty filler column
281       }
282     }
283 
284     if (renderedColumns.size() + 1 != currentWidthList.size()) {
285       LOG.warn("widthList.size() = " + currentWidthList.size()
286           + " != columns.size() = " + renderedColumns.size() + " + 1. The widthList: "
287           + LayoutInfo.listToTokenString(currentWidthList));
288     } else {
289       data.setWidthList(currentWidthList);
290     }
291   }
292 
293   public boolean needVerticalScrollbar(final FacesContext facesContext, final AbstractUISheet sheet) {
294     // estimate need of height-scrollbar on client, if yes we have to consider
295     // this when calculating column width's
296 
297     if (sheet.getNeedVerticalScrollbar() != null) {
298       return sheet.getNeedVerticalScrollbar();
299     }
300 
301     Boolean result = null;
302 
303     final Object forceScrollbar = sheet.getAttributes().get(Attributes.FORCE_VERTICAL_SCROLLBAR);
304     if (forceScrollbar != null) {
305       if ("true".equals(forceScrollbar)) {
306         result = true;
307       } else if ("false".equals(forceScrollbar)) {
308         result = false;
309       } else if (!"auto".equals(forceScrollbar)) {
310         LOG.warn("Illegal value for attribute 'forceVerticalScrollbar': '" + forceScrollbar + "'");
311       }
312     }
313 
314     if (result == null && !sheet.hasRowCount()) {
315       result = true;
316     }
317 
318     if (result == null) {
319       if (sheet.getCurrentHeight() != null) {
320         final int first = sheet.getFirst();
321         final int rows = sheet.isRowsUnlimited()
322             ? sheet.getRowCount()
323             : Math.min(sheet.getRowCount(), first + sheet.getRows()) - first;
324         Measure heightNeeded = getRowHeight(facesContext, sheet).multiply(rows);
325         if (sheet.isShowHeader()) {
326           heightNeeded = heightNeeded.add(getHeaderHeight(facesContext, sheet));
327         }
328         if (sheet.isPagingVisible()) {
329           heightNeeded = heightNeeded.add(getFooterHeight(facesContext, sheet));
330         }
331         result = heightNeeded.greaterThan(sheet.getCurrentHeight());
332       } else {
333         result = false;
334       }
335     }
336     sheet.setNeedVerticalScrollbar(result);
337     return result;
338   }
339 
340   private void parseFixedWidth(
341       final LayoutInfo layoutInfo, final List<AbstractUIColumn> renderedColumns, final Measure columnSelectorWidth) {
342     final LayoutTokens tokens = layoutInfo.getLayoutTokens();
343     for (int i = 0; i < tokens.getSize(); i++) {
344       final LayoutToken token = tokens.get(i);
345       if (token instanceof AutoLayoutToken) {
346         int width = 0;
347         if (!renderedColumns.isEmpty()) {
348           if (i < renderedColumns.size()) {
349             final AbstractUIColumn column = renderedColumns.get(i);
350             if (column instanceof AbstractUIColumnSelector) {
351               width = columnSelectorWidth.getPixel();
352             } else {
353               for (final UIComponent component : column.getChildren()) {
354                 width += 100; // FIXME: make dynamic (was removed by changing the layout
355                 LOG.error("100; // FIXME: make dynamic (was removed by changing the layout");
356               }
357             }
358             layoutInfo.update(width, i);
359           } else {
360             layoutInfo.update(0, i);
361             if (LOG.isWarnEnabled()) {
362               LOG.warn("More LayoutTokens found than rows! skipping!");
363             }
364           }
365         }
366         if (LOG.isDebugEnabled()) {
367           LOG.debug("set column " + i + " from 'auto' to width " + width);
368         }
369       }
370     }
371   }
372 
373   private Measure getHeaderHeight(final FacesContext facesContext, final AbstractUISheet sheet) {
374     return sheet.isShowHeader()
375         ? sheet.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, sheet, "headerHeight")
376         : Measure.ZERO;
377   }
378 
379   private Measure getRowHeight(final FacesContext facesContext, final AbstractUISheet sheet) {
380     return sheet.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, sheet, "rowHeight");
381   }
382 
383   private Measure getFooterHeight(final FacesContext facesContext, final AbstractUISheet sheet) {
384     return sheet.isPagingVisible()
385         ? sheet.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, sheet, "footerHeight")
386         : Measure.ZERO;
387   }
388 
389   private void layoutHeader() {
390     final AbstractUISheet sheet = (AbstractUISheet) getLayoutContainer();
391     final UIComponent header = sheet.getHeader();
392     if (header == null) {
393       LOG.warn("This should not happen. Please file a bug in the issue tracker to reproduce this case.");
394       return;
395     }
396     final LayoutTokens columns = new LayoutTokens();
397     final List<AbstractUIColumn> renderedColumns = sheet.getRenderedColumns();
398     for (final AbstractUIColumn ignored : renderedColumns) {
399       columns.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
400     }
401     final LayoutTokens rows = new LayoutTokens();
402     rows.addToken(AutoLayoutToken.INSTANCE);
403     final Grid grid = new Grid(columns, rows);
404 
405     for(final UIComponent child : header.getChildren()) {
406       if (child instanceof LayoutComponent) {
407         if (child.isRendered()) {
408           final LayoutComponent c = (LayoutComponent) child;
409           grid.add(new OriginCell(c), c.getColumnSpan(), c.getRowSpan());
410         }
411       } else {
412         if (LOG.isDebugEnabled()) {
413           LOG.debug("Found unknown component in header.");
414         }
415       }
416     }
417     sheet.setHeaderGrid(grid);
418   }
419 }