001    package org.apache.myfaces.tobago.util;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HEIGHT;
023    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INLINE;
024    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_HEIGHT;
025    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
026    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
027    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
028    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MINIMUM_SIZE;
029    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH;
030    import static org.apache.myfaces.tobago.TobagoConstants.FACET_LABEL;
031    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
032    import org.apache.myfaces.tobago.component.ComponentUtil;
033    import org.apache.myfaces.tobago.component.UICell;
034    import org.apache.myfaces.tobago.component.UIForm;
035    import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
036    
037    import javax.faces.component.UIComponent;
038    import javax.faces.component.UINamingContainer;
039    import javax.faces.context.FacesContext;
040    import java.awt.Dimension;
041    import java.util.ArrayList;
042    import java.util.List;
043    import java.util.StringTokenizer;
044    import java.util.regex.Pattern;
045    
046    public final class LayoutUtil {
047    
048      private static final Log LOG = LogFactory.getLog(LayoutUtil.class);
049    
050      private static final Pattern TOKEN_PATTERN
051          = Pattern.compile("^(\\d*px|\\d*\\*|\\d*%|fixed)$");
052    
053      private LayoutUtil() {
054        // to prevent instantiation
055      }
056    
057      public static int getInnerSpace(FacesContext facesContext,
058          UIComponent component, boolean width) {
059        String attribute;
060        if (width) {
061          attribute = ATTR_INNER_WIDTH;
062        } else {
063          attribute = ATTR_INNER_HEIGHT;
064        }
065        Integer innerSpace = (Integer) component.getAttributes().get(attribute);
066    
067        if (innerSpace == null) {
068          int space = -1;
069    
070          Integer  spaceInteger;
071          if (width) {
072            spaceInteger = getLayoutWidth(component);
073          } else {
074            spaceInteger = getLayoutHeight(component);
075          }
076          if (spaceInteger != null) {
077            space = spaceInteger;
078          }
079    
080    //      if (space < 0 && component.getParent() instanceof UIComponentBase) {
081          if (space < 0 && component.getParent() != null) {
082            space = getInnerSpace(facesContext, component.getParent(), width);
083          }
084    
085          if (space != -1) {
086            innerSpace = getInnerSpace(facesContext, component, space, width);
087          } else {
088            innerSpace = -1;
089          }
090    
091          component.getAttributes().put(attribute, innerSpace);
092        }
093    
094        return innerSpace;
095      }
096    
097      public static int getInnerSpace(FacesContext facesContext, UIComponent component,
098          int outerSpace, boolean width) {
099        int margin = 0;
100        if (component.getRendererType() != null) {
101          try {
102    
103            LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
104    
105            if (width) {
106              margin += renderer.getPaddingWidth(facesContext, component);
107              margin += renderer.getComponentExtraWidth(facesContext, component);
108            } else {
109              margin += renderer.getHeaderHeight(facesContext, component);
110              margin += renderer.getPaddingHeight(facesContext, component);
111              margin += renderer.getComponentExtraHeight(facesContext, component);
112            }
113          } catch (Exception e) {
114            if (LOG.isDebugEnabled()) {
115              LOG.debug("cant find margin", e);
116            }
117          }
118        } else {
119          if (LOG.isDebugEnabled()) {
120            LOG.debug("renderertype = null, component: " + component);
121          }
122        }
123        return outerSpace - margin;
124      }
125    
126    
127      public static int getLabelWidth(UIComponent component) {
128        if (component != null) {
129          UIComponent label = component.getFacet(FACET_LABEL);
130          if (label != null) {
131            String labelWidth = (String) label.getAttributes().get(ATTR_WIDTH);
132            if (labelWidth != null) {
133              try {
134                return Integer.parseInt(labelWidth.replaceAll("\\D", ""));
135              } catch (NumberFormatException e) {
136                LOG.warn("Can't parse label width, using default value", e);
137              }
138            }
139          }
140        }
141        return 0;
142      }
143    
144      //TODO Change this to DimensionUtils.getWidth?
145      public static Integer getLayoutWidth(UIComponent component) {
146        return getLayoutSpace(component, ATTR_WIDTH, ATTR_LAYOUT_WIDTH);
147      }
148      //TODO Change this to DimensionUtils.getHeight?
149      public static Integer getLayoutHeight(UIComponent component) {
150        return getLayoutSpace(component, ATTR_HEIGHT, ATTR_LAYOUT_HEIGHT);
151      }
152    
153      public static Integer getLayoutSpace(UIComponent component,
154          String sizeAttribute, String layoutAttribute) {
155        Object value = ComponentUtil.getAttribute(component, sizeAttribute);
156        if (value != null) {
157          if (value instanceof String) {
158            return new Integer(((String) value).replaceAll("\\D", ""));
159          } else {
160            return (Integer) value;
161          }
162        } else if (!ComponentUtil.getBooleanAttribute(component, ATTR_INLINE)) {
163    
164          value = ComponentUtil.getAttribute(component, layoutAttribute);
165          return (Integer) value;
166        }
167        return null;
168      }
169    
170      public static List<UIComponent> addChildren(List<UIComponent> children, UIComponent panel) {
171        for (Object o : panel.getChildren()) {
172          UIComponent child = (UIComponent) o;
173          if (isTransparentForLayout(child)) {
174            addChildren(children, child);
175          } else {
176            children.add(child);
177          }
178        }
179        return children;
180      }
181    
182      public static boolean isTransparentForLayout(UIComponent component) {
183    
184    //    SubViewTag's component is UINamingContainer with 'null' rendererType
185    //    is transparent for layouting
186    
187        if (component instanceof UINamingContainer
188            && component.getRendererType() == null) {
189          return true;
190        }
191        // TODO find a better way
192        if ("facelets".equals(component.getFamily())) {
193          return !"com.sun.facelets.tag.UIDebug".equals(component.getClass().getName());
194        }
195        /* TODO disable layouting of facelet stuff
196        if (component.getClass().getPackage().getName().equals("com.sun.facelets.compiler")) {
197          return true;
198        } */
199    //  also Forms are transparent for layouting
200    
201        return component instanceof UIForm;
202      }
203    
204      public static UIComponent getLayoutParent(UIComponent component) {
205        UIComponent parent = component.getParent();
206        while (parent != null && isTransparentForLayout(parent)) {
207          parent = parent.getParent();
208        }
209        return parent;
210      }
211    
212      public static void maybeSetLayoutAttribute(UIComponent cell, String attribute,
213          Integer value) {
214        if (RENDERER_TYPE_OUT.equals(cell.getRendererType())) {
215          return;
216        }
217        if (LOG.isDebugEnabled()) {
218          LOG.debug("set " + value + " to " + cell.getRendererType());
219        }
220        cell.getAttributes().put(attribute, value);
221        if (ATTR_LAYOUT_WIDTH.equals(attribute)) {
222          cell.getAttributes().remove(ATTR_INNER_WIDTH);
223        } else if (ATTR_LAYOUT_HEIGHT.equals(attribute)) {
224          cell.getAttributes().remove(ATTR_INNER_HEIGHT);
225        }
226        if (cell instanceof UICell) {
227          List<UIComponent> children = addChildren(new ArrayList<UIComponent>(), cell);
228          for (UIComponent component : children) {
229            maybeSetLayoutAttribute(component, attribute, value);
230          }
231        }
232      }
233    
234      public static int calculateFixedHeightForChildren(FacesContext facesContext, UIComponent component) {
235        int height = 0;
236        for (Object o : component.getChildren()) {
237          UIComponent child = (UIComponent) o;
238          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, child);
239          if (renderer == null
240              && child instanceof UINamingContainer
241              && child.getChildren().size() > 0) {
242            // this is a subview component ??
243            renderer = ComponentUtil.getRenderer(facesContext, (UIComponent) child.getChildren().get(0));
244          }
245          if (renderer != null) {
246            int h = renderer.getFixedHeight(facesContext, child);
247            if (h > 0) {
248              height += h;
249            }
250          }
251        }
252        return height;
253      }
254    
255      public static Dimension getMinimumSize(
256          FacesContext facesContext, UIComponent component) {
257        Dimension dimension
258            = (Dimension) component.getAttributes().get(ATTR_MINIMUM_SIZE);
259        if (dimension == null) {
260          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
261          if (renderer != null) {
262            dimension = renderer.getMinimumSize(facesContext, component);
263          }
264        }
265        if (dimension == null) {
266          dimension = new Dimension(-1, -1);
267        }
268        return dimension;
269      }
270    
271      public static boolean checkTokens(String columns) {
272        StringTokenizer st = new StringTokenizer(columns, ";");
273        while (st.hasMoreTokens()) {
274          String token = st.nextToken();
275          if (!TOKEN_PATTERN.matcher(token).matches()) {
276            return false;
277          }
278        }
279        return true;
280      }
281    }
282