001/* 002 * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved. 003 * 004 * Redistribution and use in source and binary forms, with or without 005 * modification, are permitted provided that the following conditions are met: 006 * 007 * o Redistributions of source code must retain the above copyright notice, 008 * this list of conditions and the following disclaimer. 009 * 010 * o Redistributions in binary form must reproduce the above copyright notice, 011 * this list of conditions and the following disclaimer in the documentation 012 * and/or other materials provided with the distribution. 013 * 014 * o Neither the name of JGoodies Karsten Lentzsch nor the names of 015 * its contributors may be used to endorse or promote products derived 016 * from this software without specific prior written permission. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 027 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030 031package com.jgoodies.forms.layout; 032 033import java.awt.Container; 034import java.io.Serializable; 035import java.util.List; 036import java.util.Locale; 037import java.util.StringTokenizer; 038 039/** 040 * An abstract class that specifies columns and rows in FormLayout 041 * by their default alignment, start size and resizing behavior. 042 * API users will use the subclasses {@link ColumnSpec} and {@link RowSpec}. 043 * 044 * @author Karsten Lentzsch 045 * @version $Revision$ 046 * 047 * @see ColumnSpec 048 * @see RowSpec 049 * @see FormLayout 050 * @see CellConstraints 051 */ 052@SuppressWarnings("serial") 053public abstract class FormSpec implements Serializable { 054 055 // Horizontal and Vertical Default Alignments *************************** 056 057 /** 058 * By default put components in the left. 059 */ 060 static final DefaultAlignment LEFT_ALIGN = new DefaultAlignment("left"); 061 062 /** 063 * By default put components in the right. 064 */ 065 static final DefaultAlignment RIGHT_ALIGN = new DefaultAlignment("right"); 066 067 /** 068 * By default put the components in the top. 069 */ 070 static final DefaultAlignment TOP_ALIGN = new DefaultAlignment("top"); 071 072 /** 073 * By default put the components in the bottom. 074 */ 075 static final DefaultAlignment BOTTOM_ALIGN = new DefaultAlignment("bottom"); 076 077 /** 078 * By default put the components in the center. 079 */ 080 static final DefaultAlignment CENTER_ALIGN = new DefaultAlignment("center"); 081 082 /** 083 * By default fill the column or row. 084 */ 085 static final DefaultAlignment FILL_ALIGN = new DefaultAlignment("fill"); 086 087 /** 088 * An array of all enumeration values used to canonicalize 089 * deserialized default alignments. 090 */ 091 private static final DefaultAlignment[] VALUES = { LEFT_ALIGN, RIGHT_ALIGN, 092 TOP_ALIGN, BOTTOM_ALIGN, CENTER_ALIGN, FILL_ALIGN }; 093 094 // Resizing Weights ***************************************************** 095 096 /** 097 * Gives a column or row a fixed size. 098 */ 099 public static final double NO_GROW = 0.0d; 100 101 /** 102 * The default resize weight. 103 */ 104 public static final double DEFAULT_GROW = 1.0d; 105 106 // Fields *************************************************************** 107 108 /** 109 * Holds the default alignment that will be used if a cell does not 110 * override this default. 111 */ 112 private DefaultAlignment defaultAlignment; 113 114 /** 115 * Holds the size that describes how to size this column or row. 116 */ 117 private Size size; 118 119 /** 120 * Holds the resize weight; is 0 if not used. 121 */ 122 private double resizeWeight; 123 124 // Instance Creation **************************************************** 125 126 /** 127 * Constructs a <code>FormSpec</code> for the given default alignment, 128 * size, and resize weight. The resize weight must be a non-negative 129 * double; you can use <code>NONE</code> as a convenience value for no 130 * resize. 131 * 132 * @param defaultAlignment the spec's default alignment 133 * @param size a constant, component or bounded size 134 * @param resizeWeight the spec resize weight 135 * @exception IllegalArgumentException if the resize weight is negative 136 */ 137 protected FormSpec(DefaultAlignment defaultAlignment, Size size, 138 double resizeWeight) { 139 this.defaultAlignment = defaultAlignment; 140 this.size = size; 141 this.resizeWeight = resizeWeight; 142 if (resizeWeight < 0) { 143 throw new IllegalArgumentException( 144 "The resize weight must be non-negative."); 145 } 146 } 147 148 /** 149 * Constructs a <code>FormSpec</code> from the specified encoded 150 * description. The description will be parsed to set initial values. 151 * 152 * @param defaultAlignment the default alignment 153 * @param encodedDescription the encoded description 154 */ 155 protected FormSpec(DefaultAlignment defaultAlignment, 156 String encodedDescription) { 157 this(defaultAlignment, Sizes.DEFAULT, NO_GROW); 158 parseAndInitValues(encodedDescription.toLowerCase(Locale.ENGLISH)); 159 } 160 161 // Public API *********************************************************** 162 163 /** 164 * Returns the default alignment. 165 * 166 * @return the default alignment 167 */ 168 public final DefaultAlignment getDefaultAlignment() { 169 return defaultAlignment; 170 } 171 172 /** 173 * Returns the size. 174 * 175 * @return the size 176 */ 177 public final Size getSize() { 178 return size; 179 } 180 181 /** 182 * Returns the current resize weight. 183 * 184 * @return the resize weight. 185 */ 186 public final double getResizeWeight() { 187 return resizeWeight; 188 } 189 190 /** 191 * Checks and answers whether this spec can grow or not. 192 * That is the case if and only if the resize weight is 193 * != <code>NO_GROW</code>. 194 * 195 * @return true if it can grow, false if it can't grow 196 */ 197 final boolean canGrow() { 198 return getResizeWeight() != NO_GROW; 199 } 200 201 // Parsing ************************************************************** 202 203 /** 204 * Parses an encoded form spec and initializes all required fields. 205 * The encoded description must be in lower case. 206 * 207 * @param encodedDescription the FormSpec in an encoded format 208 * @exception IllegalArgumentException if the string is empty, has no size, 209 * or is otherwise invalid 210 */ 211 private void parseAndInitValues(String encodedDescription) { 212 StringTokenizer tokenizer = new StringTokenizer(encodedDescription, 213 ":"); 214 if (!tokenizer.hasMoreTokens()) { 215 throw new IllegalArgumentException( 216 "The form spec must not be empty."); 217 } 218 String token = tokenizer.nextToken(); 219 220 // Check if the first token is an orientation. 221 DefaultAlignment alignment = DefaultAlignment.valueOf(token, 222 isHorizontal()); 223 if (alignment != null) { 224 defaultAlignment = alignment; 225 if (!tokenizer.hasMoreTokens()) { 226 throw new IllegalArgumentException( 227 "The form spec must provide a size."); 228 } 229 token = tokenizer.nextToken(); 230 } 231 232 parseAndInitSize(token); 233 234 if (tokenizer.hasMoreTokens()) { 235 resizeWeight = decodeResize(tokenizer.nextToken()); 236 } 237 } 238 239 /** 240 * Parses an encoded size spec and initializes the size fields. 241 * 242 * @param token a token that represents a size, either bounded or plain 243 */ 244 private void parseAndInitSize(String token) { 245 if (token.startsWith("max(") && token.endsWith(")")) { 246 size = parseAndInitBoundedSize(token, false); 247 return; 248 } 249 if (token.startsWith("min(") && token.endsWith(")")) { 250 size = parseAndInitBoundedSize(token, true); 251 return; 252 } 253 size = decodeAtomicSize(token); 254 } 255 256 /** 257 * Parses an encoded compound size and sets the size fields. 258 * The compound size has format: 259 * max(<atomic size>;<atomic size2>) | min(<atomic size1>;<atomic size2>) 260 * One of the two atomic sizes must be a logical size, the other must 261 * be a size constant. 262 * 263 * @param token a token for a bounded size, e.g. "max(50dlu; pref)" 264 * @param setMax if true we set a maximum size, otherwise a minimum size 265 * @return a Size that represents the parse result 266 */ 267 private Size parseAndInitBoundedSize(String token, boolean setMax) { 268 int semicolonIndex = token.indexOf(';'); 269 String sizeToken1 = token.substring(4, semicolonIndex); 270 String sizeToken2 = token.substring(semicolonIndex + 1, 271 token.length() - 1); 272 273 Size size1 = decodeAtomicSize(sizeToken1); 274 Size size2 = decodeAtomicSize(sizeToken2); 275 276 // Check valid combinations and set min or max. 277 if (size1 instanceof ConstantSize) { 278 if (size2 instanceof Sizes.ComponentSize) { 279 return new BoundedSize(size2, setMax ? null : size1, 280 setMax ? size1 : null); 281 } 282 throw new IllegalArgumentException( 283 "Bounded sizes must not be both constants."); 284 } 285 if (size2 instanceof ConstantSize) { 286 return new BoundedSize(size1, setMax ? null : size2, 287 setMax ? size2 : null); 288 } 289 throw new IllegalArgumentException( 290 "Bounded sizes must not be both logical."); 291 } 292 293 /** 294 * Decodes and returns an atomic size that is either a constant size or a 295 * component size. 296 * 297 * @param token the encoded size 298 * @return the decoded size either a constant or component size 299 */ 300 private Size decodeAtomicSize(String token) { 301 Sizes.ComponentSize componentSize = Sizes.ComponentSize.valueOf(token); 302 if (componentSize != null) { 303 return componentSize; 304 } 305 return ConstantSize.valueOf(token, isHorizontal()); 306 } 307 308 /** 309 * Decodes an encoded resize mode and resize weight and answers 310 * the resize weight. 311 * 312 * @param token the encoded resize weight 313 * @return the decoded resize weight 314 * @exception IllegalArgumentException if the string description is an 315 * invalid string representation 316 */ 317 private double decodeResize(String token) { 318 if (token.equals("g") || token.equals("grow")) { 319 return DEFAULT_GROW; 320 } 321 if (token.equals("n") || token.equals("nogrow") 322 || token.equals("none")) { 323 return NO_GROW; 324 } 325 // Must have format: grow(<double>) 326 if ((token.startsWith("grow(") || token.startsWith("g(")) 327 && token.endsWith(")")) { 328 int leftParen = token.indexOf('('); 329 int rightParen = token.indexOf(')'); 330 String substring = token.substring(leftParen + 1, rightParen); 331 return Double.parseDouble(substring); 332 } 333 throw new IllegalArgumentException("The resize argument '" + token 334 + "' is invalid. " 335 + " Must be one of: grow, g, none, n, grow(<double>), g(<double>)"); 336 } 337 338 // Misc ***************************************************************** 339 340 /** 341 * Returns a string representation of this form specification. 342 * The string representation consists of three elements separated by 343 * a colon (<tt>":"</tt>), first the alignment, second the size, 344 * and third the resize spec.<p> 345 * 346 * This method does <em>not</em> return a decoded version 347 * of this object; the contrary is the case. Many instances 348 * will return a string that cannot be parsed.<p> 349 * 350 * <strong>Note:</strong> The string representation may change 351 * at any time. It is strongly recommended to not use this string 352 * for parsing purposes. 353 * 354 * @return a string representation of the form specification. 355 */ 356 @Override 357 public final String toString() { 358 StringBuffer buffer = new StringBuffer(); 359 buffer.append(defaultAlignment); 360 361 buffer.append(":"); 362 buffer.append(size.toString()); 363 buffer.append(':'); 364 if (resizeWeight == NO_GROW) { 365 buffer.append("noGrow"); 366 } else if (resizeWeight == DEFAULT_GROW) { 367 buffer.append("grow"); 368 } else { 369 buffer.append("grow("); 370 buffer.append(resizeWeight); 371 buffer.append(')'); 372 } 373 return buffer.toString(); 374 } 375 376 /** 377 * Returns a string representation of this form specification. 378 * The string representation consists of three elements separated by 379 * a colon (<tt>":"</tt>), first the alignment, second the size, 380 * and third the resize spec.<p> 381 * 382 * This method does <em>not</em> return a decoded version 383 * of this object; the contrary is the case. Many instances 384 * will return a string that cannot be parsed.<p> 385 * 386 * <strong>Note:</strong> The string representation may change 387 * at any time. It is strongly recommended to not use this string 388 * for parsing purposes. 389 * 390 * @return a string representation of the form specification. 391 */ 392 public final String toShortString() { 393 StringBuffer buffer = new StringBuffer(); 394 buffer.append(defaultAlignment.abbreviation()); 395 396 buffer.append(":"); 397 buffer.append(size.toString()); 398 buffer.append(':'); 399 if (resizeWeight == NO_GROW) { 400 buffer.append("n"); 401 } else if (resizeWeight == DEFAULT_GROW) { 402 buffer.append("g"); 403 } else { 404 buffer.append("g("); 405 buffer.append(resizeWeight); 406 buffer.append(')'); 407 } 408 return buffer.toString(); 409 } 410 411 // Abstract Behavior **************************************************** 412 413 /** 414 * Returns if this is a horizontal specification (vs. vertical). 415 * Used to distinct between horizontal and vertical dialog units, 416 * which have different conversion factors. 417 * @return true for horizontal, false for vertical 418 */ 419 abstract boolean isHorizontal(); 420 421 // Helper Code ********************************************************** 422 423 /** 424 * Computes the maximum size for the given list of components, using 425 * this form spec and the specified measure.<p> 426 * 427 * Invoked by FormLayout to determine the size of one of my elements 428 * 429 * @param container the layout container 430 * @param components the list of components to measure 431 * @param minMeasure the measure used to determine the minimum size 432 * @param prefMeasure the measure used to determine the preferred size 433 * @param defaultMeasure the measure used to determine the default size 434 * @return the maximum size in pixels 435 */ 436 final int maximumSize(Container container, List components, 437 FormLayout.Measure minMeasure, FormLayout.Measure prefMeasure, 438 FormLayout.Measure defaultMeasure) { 439 return size.maximumSize(container, components, minMeasure, prefMeasure, 440 defaultMeasure); 441 } 442 443 /** 444 * An ordinal-based serializable typesafe enumeration for the 445 * column and row default alignment types. 446 */ 447 public static final class DefaultAlignment implements Serializable { 448 449 private final transient String name; 450 451 private DefaultAlignment(String name) { 452 this.name = name; 453 } 454 455 /** 456 * Returns a DefaultAlignment that corresponds to the specified 457 * string, null if no such alignment exists. 458 * 459 * @param str the encoded alignment 460 * @param isHorizontal indicates the values orientation 461 * @return the corresponding DefaultAlignment or null 462 */ 463 private static DefaultAlignment valueOf(String str, 464 boolean isHorizontal) { 465 if (str.equals("f") || str.equals("fill")) { 466 return FILL_ALIGN; 467 } else if (str.equals("c") || str.equals("center")) { 468 return CENTER_ALIGN; 469 } else if (isHorizontal) { 470 if (str.equals("r") || str.equals("right")) { 471 return RIGHT_ALIGN; 472 } else if (str.equals("l") || str.equals("left")) { 473 return LEFT_ALIGN; 474 } else { 475 return null; 476 } 477 } else { 478 if (str.equals("t") || str.equals("top")) { 479 return TOP_ALIGN; 480 } else if (str.equals("b") || str.equals("bottom")) { 481 return BOTTOM_ALIGN; 482 } else { 483 return null; 484 } 485 } 486 } 487 488 /** 489 * Returns this Alignment's name. 490 * 491 * @return this alignment's name. 492 */ 493 @Override 494 public String toString() { 495 return name; 496 } 497 498 /** 499 * Returns the first character of this Alignment's name. 500 * Used to identify it in short format strings. 501 * 502 * @return the name's first character. 503 */ 504 public char abbreviation() { 505 return name.charAt(0); 506 } 507 508 // Serialization ***************************************************** 509 510 private static int nextOrdinal = 0; 511 512 private final int ordinal = nextOrdinal++; 513 514 private Object readResolve() { 515 return VALUES[ordinal]; // Canonicalize 516 } 517 518 } 519 520}