1 /*
2 * Copyright 2006-2016 The JGUIraffe Team.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License")
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package net.sf.jguiraffe.gui.layout;
17
18 import java.io.Serializable;
19
20 import org.apache.commons.lang.ObjectUtils;
21
22 /**
23 * <p>
24 * A constraints class used by {@link PercentLayout}.
25 * </p>
26 * <p>
27 * This class is used to define data needed by the percent layout manager when
28 * new components are added to the managed container. The main data stored in
29 * instances of this class is information about where in the logic grid
30 * maintained by the layout manager the new component should be placed, i.e. the
31 * coordinates of the cell and the number of cells in X and Y direction that are
32 * spanned.
33 * </p>
34 * <p>
35 * It is also possible to associate a <code>PercentData</code> object with row
36 * and column constraints. If these constraints are defined, they override the
37 * constraints set for the occupied column and row. This makes it possible e.g.
38 * to set a different alignment for a certain component, while the other
39 * components in this row or column use the default alignment.
40 * </p>
41 * <p>
42 * Another information stored in instances of this class is the so-called
43 * <em>target cell</em>. This information is evaluated only if multiple columns
44 * or rows are occupied. In this case the target column determines to which
45 * column the size of the associated component should be assigned. This is
46 * optional; if no target column is set, the associated component's width will
47 * not be taken into account when the minimum layout width is calculated. The
48 * same applies for the target row.
49 * </p>
50 * <p>
51 * Instances of this class are immutable and thus thread-safe. They are not
52 * created directly, but the nested {@code Builder} class is used for this
53 * purpose. A typical sequence for creating {@code PercentData} objects could
54 * look as follows:
55 *
56 * <pre>
57 * PercentData.Builder b = new PercentData.Builder();
58 * PercentData pd1 = b.xy(1, 2).create();
59 * PercentData pd2 = b.xy(1, 3).spanX(2).withColumnConstraints(columnConstr).create();
60 * </pre>
61 *
62 * </p>
63 *
64 * @see net.sf.jguiraffe.gui.layout.CellConstraints
65 * @author Oliver Heger
66 * @version $Id: PercentData.java 205 2012-01-29 18:29:57Z oheger $
67 */
68 public final class PercentData implements Serializable
69 {
70 /** Constant for an undefined cell position. */
71 public static final int POS_UNDEF = -1;
72
73 /**
74 * The serial version UID.
75 */
76 private static final long serialVersionUID = 20090730L;
77
78 /** Constant for the initial buffer size. */
79 private static final int BUF_SIZE = 64;
80
81 /** Stores the column. */
82 private final int column;
83
84 /** Stores the row. */
85 private final int row;
86
87 /** Stores the number of occupied columns. */
88 private final int spanX;
89
90 /** Stores the number of occupied rows. */
91 private final int spanY;
92
93 /** Stores the target column. */
94 private final int targetColumn;
95
96 /** Stores the targetRow. */
97 private final int targetRow;
98
99 /** Stores a reference to a constraints object for the column. */
100 private final CellConstraints columnConstraints;
101
102 /** Stores a reference to a constraints object for the row. */
103 private final CellConstraints rowConstraints;
104
105 /**
106 * Creates a new instance of {@code PercentData} and initializes all
107 * properties. This constructor is called by the builder.
108 *
109 * @param x the column index
110 * @param y the row index
111 * @param w the span in X direction
112 * @param h the span in Y direction
113 * @param tx the target column
114 * @param ty the target row
115 * @param cccol the column constraints
116 * @param ccrow the row constraints
117 */
118 private PercentData(int x, int y, int w, int h, int tx, int ty,
119 CellConstraints cccol, CellConstraints ccrow)
120 {
121 column = x;
122 row = y;
123 spanX = w;
124 spanY = h;
125 targetColumn = tx;
126 targetRow = ty;
127 columnConstraints = cccol;
128 rowConstraints = ccrow;
129 }
130
131 /**
132 * Returns the number of the column, in which the corresponding component is
133 * to be placed.
134 *
135 * @return the column number
136 */
137 public int getColumn()
138 {
139 return column;
140 }
141
142 /**
143 * Returns the number of the row, in which the corresponding component it to
144 * be placed.
145 *
146 * @return the row number
147 */
148 public int getRow()
149 {
150 return row;
151 }
152
153 /**
154 * Returns the number of occupied columns.
155 *
156 * @return the number of occupied columns
157 */
158 public int getSpanX()
159 {
160 return spanX;
161 }
162
163 /**
164 * Returns the number of occupied rows.
165 *
166 * @return the number of occupied rows
167 */
168 public int getSpanY()
169 {
170 return spanY;
171 }
172
173 /**
174 * Returns the target column.
175 *
176 * @return the target column
177 */
178 public int getTargetColumn()
179 {
180 return targetColumn;
181 }
182
183 /**
184 * Returns the target row.
185 *
186 * @return the target row
187 */
188 public int getTargetRow()
189 {
190 return targetRow;
191 }
192
193 /**
194 * Returns the associated constraints object for the column.
195 *
196 * @return the column constraints object (may be <b>null </b>)
197 */
198 public CellConstraints getColumnConstraints()
199 {
200 return columnConstraints;
201 }
202
203 /**
204 * Returns the associated constraints object for the row.
205 *
206 * @return the row constraints object (may be <b>null </b>)
207 */
208 public CellConstraints getRowConstraints()
209 {
210 return rowConstraints;
211 }
212
213 /**
214 * A convenience method for determining the cell constraints object for the
215 * specified orientation. This method is useful for some generic methods in
216 * <code>PercentLayoutBase</code> that can operate either on columns or on
217 * rows.
218 *
219 * @param vert the orientation flag (<b>true</b> for vertical, <b>false</b>
220 * for horizontal)
221 * @return the corresponding constraints object
222 */
223 public CellConstraints getConstraints(boolean vert)
224 {
225 return vert ? getRowConstraints() : getColumnConstraints();
226 }
227
228 /**
229 * Appends a string representation of this object to the specified string
230 * buffer. This string contains the properties of this instance with their
231 * values. No further information (e.g. the class name) is written.
232 *
233 * @param buf the target buffer (must not be <b>null</b>)
234 * @throws IllegalArgumentException if the buffer is <b>null</b>
235 */
236 public void buildString(StringBuilder buf)
237 {
238 if (buf == null)
239 {
240 throw new IllegalArgumentException("Buffer must not be null!");
241 }
242
243 buf.append("COL = ").append(getColumn());
244 buf.append(" ROW = ").append(getRow());
245 buf.append(" SPANX = ").append(getSpanX());
246 buf.append(" SPANY = ").append(getSpanY());
247 if (getTargetColumn() > POS_UNDEF)
248 {
249 buf.append(" TARGETCOL = ").append(getTargetColumn());
250 }
251 if (getTargetRow() > POS_UNDEF)
252 {
253 buf.append(" TARGETROW = ").append(getTargetRow());
254 }
255 if (getColumnConstraints() != null)
256 {
257 buf.append(" COLCONSTR = ").append(
258 getColumnConstraints().toSpecificationString());
259 }
260 if (getRowConstraints() != null)
261 {
262 buf.append(" ROWCONSTR = ").append(
263 getRowConstraints().toSpecificationString());
264 }
265 }
266
267 /**
268 * Returns a string representation of this object.
269 *
270 * @return a string representation
271 */
272 @Override
273 public String toString()
274 {
275 StringBuilder buf = new StringBuilder(BUF_SIZE);
276 buf.append(getClass().getName());
277 buf.append(" [ ");
278 buildString(buf);
279 buf.append(" ]");
280 return buf.toString();
281 }
282
283 /**
284 * Compares this object with another one. Two instances of {@code
285 * PercentData} are considered equal if all of their properties are equal.
286 *
287 * @param obj the object to compare to
288 * @return a flag whether these objects are equal
289 */
290 @Override
291 public boolean equals(Object obj)
292 {
293 if (this == obj)
294 {
295 return true;
296 }
297 if (!(obj instanceof PercentData))
298 {
299 return false;
300 }
301
302 PercentData c = (PercentData) obj;
303 return getColumn() == c.getColumn()
304 && getRow() == c.getRow()
305 && getSpanX() == c.getSpanX()
306 && getSpanY() == c.getSpanY()
307 && getTargetColumn() == c.getTargetColumn()
308 && getTargetRow() == c.getTargetRow()
309 && ObjectUtils.equals(getColumnConstraints(), c
310 .getColumnConstraints())
311 && ObjectUtils.equals(getRowConstraints(), c
312 .getRowConstraints());
313 }
314
315 /**
316 * Returns a hash code for this object.
317 *
318 * @return a hash code
319 */
320 @Override
321 public int hashCode()
322 {
323 final int factor = 31;
324 final int seed = 17;
325
326 int result = seed;
327 result = factor * result + getColumn();
328 result = factor * result + getRow();
329 result = factor * result + getSpanX();
330 result = factor * result + getSpanY();
331 result = factor * result + getTargetColumn();
332 result = factor * result + getTargetRow();
333 if (getColumnConstraints() != null)
334 {
335 result = factor * result + getColumnConstraints().hashCode();
336 }
337 if (getRowConstraints() != null)
338 {
339 result = factor * result + getRowConstraints().hashCode();
340 }
341
342 return result;
343 }
344
345 /**
346 * <p>
347 * A <em>builder</em> implementation for creating instances of {@code
348 * PercentData}. Using this class {@code PercentData} objects with all
349 * properties supported can be created in a convenient way. Refer to the
350 * class comment for {@link PercentData} for example use cases for this
351 * class.
352 * </p>
353 * <p>
354 * Implementation note: This class is not thread-safe.
355 * </p>
356 *
357 * @author Oliver Heger
358 * @version $Id: PercentData.java 205 2012-01-29 18:29:57Z oheger $
359 */
360 public static class Builder implements Serializable
361 {
362 /**
363 * The serial version UID.
364 */
365 private static final long serialVersionUID = 20090730L;
366
367 /** The x position. */
368 private int col;
369
370 /** The y position. */
371 private int row;
372
373 /** The x span. */
374 private int spanX;
375
376 /** The y span. */
377 private int spanY;
378
379 /** The target column. */
380 private int targetCol;
381
382 /** The target row. */
383 private int targetRow;
384
385 /** The column constraints. */
386 private CellConstraints ccCol;
387
388 /** The row constraints. */
389 private CellConstraints ccRow;
390
391 /**
392 * Creates a new instance of {@code Builder}.
393 */
394 public Builder()
395 {
396 initDefaults();
397 }
398
399 /**
400 * Initializes the column and the row index of the {@code PercentData}
401 * instance to be created.
402 *
403 * @param x the column index (must be greater or equal 0)
404 * @param y the row index (must be greater or equal 0)
405 * @return a reference to this builder for method chaining
406 * @throws IllegalArgumentException if a parameter is invalid
407 */
408 public Builder xy(int x, int y)
409 {
410 checkPos(x, "Column");
411 checkPos(y, "Row");
412
413 col = x;
414 row = y;
415 return this;
416 }
417
418 /**
419 * Initializes the number of columns occupied by the component
420 * associated with the {@code PercentData} instance to be created. This
421 * value is used to populate the {@code spanX} property of the {@code
422 * PercentData} object.
423 *
424 * @param w the number of occupied columns (must be greater 0)
425 * @return a reference to this builder for method chaining
426 * @throws IllegalArgumentException if the number of columns is less or
427 * equal 0
428 */
429 public Builder spanX(int w)
430 {
431 if (w < 1)
432 {
433 throw new IllegalArgumentException("X span must be greater 0!");
434 }
435
436 spanX = w;
437 return this;
438 }
439
440 /**
441 * Initializes the number of rows occupied by the component associated
442 * with the {@code PercentData} instance to be created. This value is
443 * used to populate the {@code spanY} property of the {@code
444 * PercentData} object.
445 *
446 * @param h the number of occupied rows (must be greater 0)
447 * @return a reference to this builder for method chaining
448 * @throws IllegalArgumentException if the number of rows is less or
449 * equal 0
450 */
451 public Builder spanY(int h)
452 {
453 if (h < 1)
454 {
455 throw new IllegalArgumentException("Y span must be greater 0!");
456 }
457
458 spanY = h;
459 return this;
460 }
461
462 /**
463 * Initializes the number of columns and rows occupied by the component
464 * association with the {@code PercentData} instance to be created. This
465 * is convenience method that combines calls to {@link #spanX(int)} and
466 * {@link #spanY(int)}.
467 *
468 * @param w the number of occupied columns (must be greater 0)
469 * @param h the number of occupied rows (must be greater 0)
470 * @return a reference to this builder for method chaining
471 * @throws IllegalArgumentException if the number of columns or rows is
472 * less or equal 0
473 */
474 public Builder span(int w, int h)
475 {
476 spanX(w);
477 spanY(h);
478 return this;
479 }
480
481 /**
482 * Initializes the {@code targetColumn} property of the {@code
483 * PercentData} instance to be created. If a component spans multiple
484 * columns, the target column specifies, to which column the size of the
485 * component should be applied. If no target column is set (which is the
486 * default), the width of the component is not taken into account when
487 * determining the width of the layout's columns.
488 *
489 * @param tc the index of the target column (must be greater or equal 0)
490 * @return a reference to this builder for method chaining
491 * @throws IllegalArgumentException if the target column is less than 0
492 */
493 public Builder withTargetColumn(int tc)
494 {
495 checkPos(tc, "Target column");
496 targetCol = tc;
497 return this;
498 }
499
500 /**
501 * Initializes the {@code targetRow} property of the {@code PercentData}
502 * instance to be created. If a component spans multiple rows, the
503 * target row specifies, to which row the size of the component should
504 * be applied. If no target row is set (which is the default), the
505 * height of the component is not taken into account when determining
506 * the height of the layout's rows.
507 *
508 * @param tr the index of the target row (must be greater or equal 0)
509 * @return a reference to this builder for method chaining
510 * @throws IllegalArgumentException if the target row is less than 0
511 */
512 public Builder withTargetRow(int tr)
513 {
514 checkPos(tr, "Target row");
515 targetRow = tr;
516 return this;
517 }
518
519 /**
520 * Sets a {@code CellConstraints} reference for the column for the
521 * {@code PercentData} instance to be created. With this method the
522 * {@code columnConstraints} property of the {@code PercentData} object
523 * can be specified.
524 *
525 * @param cc the {@code CellConstraints} object for the column
526 * @return a reference to this builder for method chaining
527 */
528 public Builder withColumnConstraints(CellConstraints cc)
529 {
530 ccCol = cc;
531 return this;
532 }
533
534 /**
535 * Sets a {@code CellConstraints} reference for the row for the {@code
536 * PercentData} instance to be created. With this method the {@code
537 * rowConstraints} property of the {@code PercentData} object can be
538 * specified.
539 *
540 * @param cc the {@code CellConstraints} object for the row
541 * @return a reference to this builder for method chaining
542 */
543 public Builder withRowConstraints(CellConstraints cc)
544 {
545 ccRow = cc;
546 return this;
547 }
548
549 /**
550 * Returns a {@code PercentData} object with the specified column and
551 * row index. This is a convenience method for the frequent use case
552 * that only the position of a component in the layout needs to be
553 * specified. It has the same effect as calling {@link #xy(int, int)}
554 * followed by {@link #create()}. Properties that have been set before
555 * are not cleared.
556 *
557 * @param x the column index (must be greater or equal 0)
558 * @param y the row index (must be greater or equal 0)
559 * @return the {@code PercentData} object with the given coordinates
560 * @throws IllegalArgumentException if a parameter is invalid
561 */
562 public PercentData pos(int x, int y)
563 {
564 xy(x, y);
565 return create();
566 }
567
568 /**
569 * Creates the {@code PercentData} instance whose properties were
570 * specified by the preceding method calls. This method requires that
571 * the position was set using the {@link #xy(int, int)} method. All
572 * other properties are optional and are initialized with the following
573 * default values:
574 * <ul>
575 * <li>The span is set to (1, 1), i.e. the component covers a single
576 * column and row.</li>
577 * <li>The target column and row are set to -1, which means that they
578 * are undefined.</li>
579 * <li>The column constraints and row constraints are set to
580 * <b>null</b>.</li>
581 * </ul>
582 *
583 * @return a reference to the new {@code PercentData} instance
584 * @throws IllegalStateException if required parameters have not been
585 * set
586 */
587 public PercentData create()
588 {
589 if (col <= POS_UNDEF)
590 {
591 throw new IllegalStateException(
592 "A position must be set before calling create()!");
593 }
594
595 PercentData res = new PercentData(col, row, spanX, spanY,
596 targetCol, targetRow, ccCol, ccRow);
597 reset();
598 return res;
599 }
600
601 /**
602 * Resets all properties of this builder to default values. This method
603 * is automatically called by {@link #create()}, so that the definition
604 * of a new instance can be started. It can be invoked manually to undo
605 * the effects of methods called before.
606 */
607 public void reset()
608 {
609 initDefaults();
610 }
611
612 /**
613 * Sets default values for the builder properties.
614 */
615 private void initDefaults()
616 {
617 col = POS_UNDEF;
618 row = POS_UNDEF;
619 targetCol = POS_UNDEF;
620 targetRow = POS_UNDEF;
621 spanX = 1;
622 spanY = 1;
623 ccCol = null;
624 ccRow = null;
625 }
626
627 /**
628 * Helper method for validating a position. This method checks whether
629 * the given position is valid. If not, an exception is thrown.
630 *
631 * @param p the position to check
632 * @param name the name of the position for generating the exception
633 * message
634 */
635 private static void checkPos(int p, String name)
636 {
637 if (p <= POS_UNDEF)
638 {
639 throw new IllegalArgumentException(name
640 + " must be greater or equal 0!");
641 }
642 }
643 }
644 }