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    
149      //TODO Change this to DimensionUtils.getHeight?
150      public static Integer getLayoutHeight(UIComponent component) {
151        return getLayoutSpace(component, ATTR_HEIGHT, ATTR_LAYOUT_HEIGHT);
152      }
153    
154      public static Integer getLayoutSpace(UIComponent component,
155          String sizeAttribute, String layoutAttribute) {
156        Object value = ComponentUtil.getAttribute(component, sizeAttribute);
157        if (value != null) {
158          if (value instanceof String) {
159            return new Integer(((String) value).replaceAll("\\D", ""));
160          } else {
161            return (Integer) value;
162          }
163        } else if (!ComponentUtil.getBooleanAttribute(component, ATTR_INLINE)) {
164    
165          value = ComponentUtil.getAttribute(component, layoutAttribute);
166          return (Integer) value;
167        }
168        return null;
169      }
170    
171      public static List<UIComponent> addChildren(List<UIComponent> children, UIComponent panel) {
172        for (Object o : panel.getChildren()) {
173          UIComponent child = (UIComponent) o;
174          if (isTransparentForLayout(child)) {
175            addChildren(children, child);
176          } else {
177            children.add(child);
178          }
179        }
180        return children;
181      }
182    
183      public static boolean isTransparentForLayout(UIComponent component) {
184    
185    //    SubViewTag's component is UINamingContainer with 'null' rendererType
186    //    is transparent for layouting
187    
188        if (component instanceof UINamingContainer
189            && component.getRendererType() == null) {
190          return true;
191        }
192        // TODO find a better way
193        if ("facelets".equals(component.getFamily())) {
194          return !"com.sun.facelets.tag.UIDebug".equals(component.getClass().getName());
195        }
196        /* TODO disable layouting of facelet stuff
197        if (component.getClass().getPackage().getName().equals("com.sun.facelets.compiler")) {
198          return true;
199        } */
200    //  also Forms are transparent for layouting
201    
202        return component instanceof UIForm;
203      }
204    
205      public static UIComponent getLayoutParent(UIComponent component) {
206        UIComponent parent = component.getParent();
207        while (parent != null && isTransparentForLayout(parent)) {
208          parent = parent.getParent();
209        }
210        return parent;
211      }
212    
213      public static void maybeSetLayoutAttribute(UIComponent cell, String attribute,
214          Integer value) {
215        if (RENDERER_TYPE_OUT.equals(cell.getRendererType())) {
216          return;
217        }
218        if (LOG.isDebugEnabled()) {
219          LOG.debug("set " + value + " to " + cell.getRendererType());
220        }
221        cell.getAttributes().put(attribute, value);
222        if (ATTR_LAYOUT_WIDTH.equals(attribute)) {
223          cell.getAttributes().remove(ATTR_INNER_WIDTH);
224        } else if (ATTR_LAYOUT_HEIGHT.equals(attribute)) {
225          cell.getAttributes().remove(ATTR_INNER_HEIGHT);
226        }
227        if (cell instanceof UICell) {
228          List<UIComponent> children = addChildren(new ArrayList<UIComponent>(), cell);
229          for (UIComponent component : children) {
230            maybeSetLayoutAttribute(component, attribute, value);
231          }
232        }
233      }
234    
235      public static int calculateFixedHeightForChildren(FacesContext facesContext, UIComponent component) {
236        int height = 0;
237        for (Object o : component.getChildren()) {
238          UIComponent child = (UIComponent) o;
239          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, child);
240          if (renderer == null
241              && child instanceof UINamingContainer
242              && child.getChildren().size() > 0) {
243            // this is a subview component ??
244            renderer = ComponentUtil.getRenderer(facesContext, (UIComponent) child.getChildren().get(0));
245          }
246          if (renderer != null) {
247            int h = renderer.getFixedHeight(facesContext, child);
248            if (h > 0) {
249              height += h;
250            }
251          }
252        }
253        return height;
254      }
255    
256      public static Dimension getMinimumSize(
257          FacesContext facesContext, UIComponent component) {
258        Dimension dimension
259            = (Dimension) component.getAttributes().get(ATTR_MINIMUM_SIZE);
260        if (dimension == null) {
261          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
262          if (renderer != null) {
263            dimension = renderer.getMinimumSize(facesContext, component);
264          }
265        }
266        if (dimension == null) {
267          dimension = new Dimension(-1, -1);
268        }
269        return dimension;
270      }
271    
272      public static boolean checkTokens(String columns) {
273        StringTokenizer st = new StringTokenizer(columns, ";");
274        while (st.hasMoreTokens()) {
275          String token = st.nextToken();
276          if (!TOKEN_PATTERN.matcher(token).matches()) {
277            return false;
278          }
279        }
280        return true;
281      }
282    }
283