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