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 
278       int freeWidth = space.getPixel();
279       for (Integer width : currentWidthList) {
280         freeWidth -= width;
281       }
282 
283       if (needVerticalScrollbar) {
284         if (freeWidth > 0) {
285           currentWidthList.add(freeWidth + verticalScrollbarWeight.getPixel()); // filler column
286         } else {
287           currentWidthList.add(verticalScrollbarWeight.getPixel()); // filler column
288         }
289       } else {
290         currentWidthList.add(Math.max(freeWidth, 0)); // empty filler column
291       }
292     }
293 
294     if (renderedColumns.size() + 1 != currentWidthList.size()) {
295       LOG.warn("widthList.size() = " + currentWidthList.size()
296           + " != columns.size() = " + renderedColumns.size() + " + 1. The widthList: "
297           + LayoutInfo.listToTokenString(currentWidthList));
298     } else {
299       data.setWidthList(currentWidthList);
300     }
301   }
302 
303   public boolean needVerticalScrollbar(final FacesContext facesContext, final AbstractUISheet sheet) {
304     // estimate need of height-scrollbar on client, if yes we have to consider
305     // this when calculating column width's
306 
307     if (sheet.getNeedVerticalScrollbar() != null) {
308       return sheet.getNeedVerticalScrollbar();
309     }
310 
311     Boolean result = null;
312 
313     final Object forceScrollbar = sheet.getAttributes().get(Attributes.FORCE_VERTICAL_SCROLLBAR);
314     if (forceScrollbar != null) {
315       if ("true".equals(forceScrollbar)) {
316         result = true;
317       } else if ("false".equals(forceScrollbar)) {
318         result = false;
319       } else if (!"auto".equals(forceScrollbar)) {
320         LOG.warn("Illegal value for attribute 'forceVerticalScrollbar': '" + forceScrollbar + "'");
321       }
322     }
323 
324     if (result == null && !sheet.hasRowCount()) {
325       result = true;
326     }
327 
328     if (result == null) {
329       if (sheet.getCurrentHeight() != null) {
330         final int first = sheet.getFirst();
331         final int rows = sheet.isRowsUnlimited()
332             ? sheet.getRowCount()
333             : Math.min(sheet.getRowCount(), first + sheet.getRows()) - first;
334         Measure heightNeeded = getRowHeight(facesContext, sheet).multiply(rows);
335         if (sheet.isShowHeader()) {
336           heightNeeded = heightNeeded.add(getHeaderHeight(facesContext, sheet));
337         }
338         if (sheet.isPagingVisible()) {
339           heightNeeded = heightNeeded.add(getFooterHeight(facesContext, sheet));
340         }
341         result = heightNeeded.greaterThan(sheet.getCurrentHeight());
342       } else {
343         result = false;
344       }
345     }
346     sheet.setNeedVerticalScrollbar(result);
347     return result;
348   }
349 
350   private void parseFixedWidth(
351       final LayoutInfo layoutInfo, final List<AbstractUIColumn> renderedColumns, final Measure columnSelectorWidth) {
352     final LayoutTokens tokens = layoutInfo.getLayoutTokens();
353     for (int i = 0; i < tokens.getSize(); i++) {
354       final LayoutToken token = tokens.get(i);
355       if (token instanceof AutoLayoutToken) {
356         int width = 0;
357         if (!renderedColumns.isEmpty()) {
358           if (i < renderedColumns.size()) {
359             final AbstractUIColumn column = renderedColumns.get(i);
360             if (column instanceof AbstractUIColumnSelector) {
361               width = columnSelectorWidth.getPixel();
362             } else {
363               for (final UIComponent component : column.getChildren()) {
364                 width += 100; // FIXME: make dynamic (was removed by changing the layout
365                 LOG.error("100; // FIXME: make dynamic (was removed by changing the layout");
366               }
367             }
368             layoutInfo.update(width, i);
369           } else {
370             layoutInfo.update(0, i);
371             if (LOG.isWarnEnabled()) {
372               LOG.warn("More LayoutTokens found than rows! skipping!");
373             }
374           }
375         }
376         if (LOG.isDebugEnabled()) {
377           LOG.debug("set column " + i + " from 'auto' to width " + width);
378         }
379       }
380     }
381   }
382 
383   private Measure getHeaderHeight(final FacesContext facesContext, final AbstractUISheet sheet) {
384     return sheet.isShowHeader()
385         ? sheet.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, sheet, "headerHeight")
386         : Measure.ZERO;
387   }
388 
389   private Measure getRowHeight(final FacesContext facesContext, final AbstractUISheet sheet) {
390     return sheet.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, sheet, "rowHeight");
391   }
392 
393   private Measure getFooterHeight(final FacesContext facesContext, final AbstractUISheet sheet) {
394     return sheet.isPagingVisible()
395         ? sheet.getLayoutComponentRenderer(facesContext).getCustomMeasure(facesContext, sheet, "footerHeight")
396         : Measure.ZERO;
397   }
398 
399   private void layoutHeader() {
400     final AbstractUISheet sheet = (AbstractUISheet) getLayoutContainer();
401     final UIComponent header = sheet.getHeader();
402     if (header == null) {
403       LOG.warn("This should not happen. Please file a bug in the issue tracker to reproduce this case.");
404       return;
405     }
406     final LayoutTokens columns = new LayoutTokens();
407     final List<AbstractUIColumn> renderedColumns = sheet.getRenderedColumns();
408     for (final AbstractUIColumn ignored : renderedColumns) {
409       columns.addToken(RelativeLayoutToken.DEFAULT_INSTANCE);
410     }
411     final LayoutTokens rows = new LayoutTokens();
412     rows.addToken(AutoLayoutToken.INSTANCE);
413     final Grid grid = new Grid(columns, rows);
414 
415     for(final UIComponent child : header.getChildren()) {
416       if (child instanceof LayoutComponent) {
417         if (child.isRendered()) {
418           final LayoutComponent c = (LayoutComponent) child;
419           grid.add(new OriginCell(c), c.getColumnSpan(), c.getRowSpan());
420         }
421       } else {
422         if (LOG.isDebugEnabled()) {
423           LOG.debug("Found unknown component in header.");
424         }
425       }
426     }
427     sheet.setHeaderGrid(grid);
428   }
429 }