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.lang.math.NumberUtils;
021 import org.apache.commons.logging.Log;
022 import org.apache.commons.logging.LogFactory;
023 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_HEIGHT;
024 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INLINE;
025 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_HEIGHT;
026 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
027 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
028 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
029 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_MINIMUM_SIZE;
030 import static org.apache.myfaces.tobago.TobagoConstants.ATTR_WIDTH;
031 import static org.apache.myfaces.tobago.TobagoConstants.FACET_LABEL;
032 import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
033 import org.apache.myfaces.tobago.component.ComponentUtil;
034 import org.apache.myfaces.tobago.component.LayoutTokens;
035 import org.apache.myfaces.tobago.component.UICell;
036 import org.apache.myfaces.tobago.component.UIForm;
037 import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
038
039 import javax.faces.component.UIComponent;
040 import javax.faces.component.UINamingContainer;
041 import javax.faces.context.FacesContext;
042 import java.awt.Dimension;
043 import java.util.ArrayList;
044 import java.util.List;
045 import java.util.StringTokenizer;
046
047 public final class LayoutUtil {
048
049 private static final Log LOG = LogFactory.getLog(LayoutUtil.class);
050
051 private LayoutUtil() {
052 // to prevent instantiation
053 }
054
055 public static int getInnerSpace(FacesContext facesContext,
056 UIComponent component, boolean width) {
057 String attribute;
058 if (width) {
059 attribute = ATTR_INNER_WIDTH;
060 } else {
061 attribute = ATTR_INNER_HEIGHT;
062 }
063 Integer innerSpace = (Integer) component.getAttributes().get(attribute);
064
065 if (innerSpace == null) {
066 int space = -1;
067
068 Integer spaceInteger;
069 if (width) {
070 spaceInteger = getLayoutWidth(component);
071 } else {
072 spaceInteger = getLayoutHeight(component);
073 }
074 if (spaceInteger != null) {
075 space = spaceInteger;
076 }
077
078 // if (space < 0 && component.getParent() instanceof UIComponentBase) {
079 if (space < 0 && component.getParent() != null) {
080 space = getInnerSpace(facesContext, component.getParent(), width);
081 }
082
083 if (space != -1) {
084 innerSpace = getInnerSpace(facesContext, component, space, width);
085 } else {
086 innerSpace = -1;
087 }
088
089 component.getAttributes().put(attribute, innerSpace);
090 }
091
092 return innerSpace;
093 }
094
095 public static int getInnerSpace(FacesContext facesContext, UIComponent component,
096 int outerSpace, boolean width) {
097 int margin = 0;
098 if (component.getRendererType() != null) {
099 try {
100
101 LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
102
103 if (width) {
104 margin += renderer.getPaddingWidth(facesContext, component);
105 margin += renderer.getComponentExtraWidth(facesContext, component);
106 } else {
107 margin += renderer.getHeaderHeight(facesContext, component);
108 margin += renderer.getPaddingHeight(facesContext, component);
109 margin += renderer.getComponentExtraHeight(facesContext, component);
110 }
111 } catch (Exception e) {
112 if (LOG.isDebugEnabled()) {
113 LOG.debug("cannot find margin", e);
114 }
115 }
116 } else {
117 if (LOG.isDebugEnabled()) {
118 LOG.debug("renderertype = null, component: " + component);
119 }
120 }
121 return outerSpace - margin;
122 }
123
124
125 public static int getLabelWidth(UIComponent component) {
126 if (component != null) {
127 UIComponent label = component.getFacet(FACET_LABEL);
128 if (label != null) {
129 String labelWidth = (String) label.getAttributes().get(ATTR_WIDTH);
130 if (labelWidth != null) {
131 try {
132 return Integer.parseInt(stripNonNumericChars(labelWidth));
133 } catch (NumberFormatException e) {
134 LOG.warn("Can't parse label width, using default value", e);
135 }
136 }
137 }
138 }
139 return 0;
140 }
141
142 // TODO Change this to DimensionUtils.getWidth?
143 public static Integer getLayoutWidth(UIComponent component) {
144 return getLayoutSpace(component, ATTR_WIDTH, ATTR_LAYOUT_WIDTH);
145 }
146
147 // TODO Change this to DimensionUtils.getHeight?
148 public static Integer getLayoutHeight(UIComponent component) {
149 return getLayoutSpace(component, ATTR_HEIGHT, ATTR_LAYOUT_HEIGHT);
150 }
151
152 public static Integer getLayoutSpace(UIComponent component,
153 String sizeAttribute, String layoutAttribute) {
154 Object value = ComponentUtil.getAttribute(component, sizeAttribute);
155 if (value != null) {
156 if (value instanceof String) {
157 return new Integer(stripNonNumericChars((String) value));
158 } else {
159 return (Integer) value;
160 }
161 } else if (!ComponentUtil.getBooleanAttribute(component, ATTR_INLINE)) {
162 value = ComponentUtil.getAttribute(component, layoutAttribute);
163 return (Integer) value;
164 }
165 return null;
166 }
167
168 public static List<UIComponent> addChildren(List<UIComponent> children, UIComponent panel) {
169 for (Object o : panel.getChildren()) {
170 UIComponent child = (UIComponent) o;
171 if (isTransparentForLayout(child)) {
172 addChildren(children, child);
173 } else {
174 children.add(child);
175 }
176 }
177 return children;
178 }
179
180 public static boolean isTransparentForLayout(UIComponent component) {
181
182 // SubViewTag's component is UINamingContainer with 'null' rendererType
183 // is transparent for layouting
184
185 if (component instanceof UINamingContainer
186 && component.getRendererType() == null) {
187 return true;
188 }
189 // TODO find a better way
190 if ("facelets".equals(component.getFamily())) {
191 return !"com.sun.facelets.tag.UIDebug".equals(component.getClass().getName());
192 }
193 /* TODO disable layouting of facelet stuff
194 if (component.getClass().getPackage().getName().equals("com.sun.facelets.compiler")) {
195 return true;
196 } */
197 // also Forms are transparent for layouting
198
199 return component instanceof UIForm;
200 }
201
202 public static UIComponent getLayoutParent(UIComponent component) {
203 UIComponent parent = component.getParent();
204 while (parent != null && isTransparentForLayout(parent)) {
205 parent = parent.getParent();
206 }
207 return parent;
208 }
209
210 public static void maybeSetLayoutAttribute(UIComponent cell, String attribute,
211 Integer value) {
212 if (RENDERER_TYPE_OUT.equals(cell.getRendererType())) {
213 return;
214 }
215 if (LOG.isDebugEnabled()) {
216 LOG.debug("set " + value + " to " + cell.getRendererType());
217 }
218 cell.getAttributes().put(attribute, value);
219 if (ATTR_LAYOUT_WIDTH.equals(attribute)) {
220 cell.getAttributes().remove(ATTR_INNER_WIDTH);
221 } else if (ATTR_LAYOUT_HEIGHT.equals(attribute)) {
222 cell.getAttributes().remove(ATTR_INNER_HEIGHT);
223 }
224 if (cell instanceof UICell) {
225 List<UIComponent> children = addChildren(new ArrayList<UIComponent>(), cell);
226 for (UIComponent component : children) {
227 maybeSetLayoutAttribute(component, attribute, value);
228 }
229 }
230 }
231
232 public static int calculateFixedHeightForChildren(FacesContext facesContext, UIComponent component) {
233 int height = 0;
234 for (Object o : component.getChildren()) {
235 UIComponent child = (UIComponent) o;
236 LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, child);
237 if (renderer == null
238 && child instanceof UINamingContainer
239 && child.getChildren().size() > 0) {
240 // this is a subview component ??
241 renderer = ComponentUtil.getRenderer(facesContext, (UIComponent) child.getChildren().get(0));
242 }
243 if (renderer != null) {
244 int h = renderer.getFixedHeight(facesContext, child);
245 if (h > 0) {
246 height += h;
247 }
248 }
249 }
250 return height;
251 }
252
253 public static Dimension getMinimumSize(
254 FacesContext facesContext, UIComponent component) {
255 Dimension dimension = (Dimension) component.getAttributes().get(ATTR_MINIMUM_SIZE);
256 if (dimension == null) {
257 LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
258 if (renderer != null) {
259 dimension = renderer.getMinimumSize(facesContext, component);
260 }
261 }
262 if (dimension == null) {
263 dimension = new Dimension(-1, -1);
264 }
265 return dimension;
266 }
267
268 public static boolean checkTokens(String columns) {
269 StringTokenizer st = new StringTokenizer(columns, ";");
270 while (st.hasMoreTokens()) {
271 String token = st.nextToken();
272 if (!checkToken(token)) {
273 return false;
274 }
275 }
276 return true;
277 }
278
279 public static boolean checkToken(String columnToken) {
280 return LayoutTokens.parseToken(columnToken) != null;
281 }
282
283 // XXX perhaps move to StringUtils
284 public static String stripNonNumericChars(String token) {
285 if (token == null || token.length() == 0) {
286 return token;
287 }
288 StringBuilder builder = new StringBuilder(token.length());
289 for (int i = 0; i < token.length(); ++i) {
290 char c = token.charAt(i);
291 if (Character.isDigit(c)) {
292 builder.append(c);
293 }
294 }
295 return builder.toString();
296 }
297
298 public static boolean isNumberAndSuffix(String token, String suffix) {
299 return token.endsWith(suffix)
300 && NumberUtils.isDigits(removeSuffix(token, suffix));
301 }
302
303 public static String removeSuffix(String token, String suffix) {
304 return token.substring(0, token.length() - suffix.length());
305 }
306
307 }