001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.layout; 017 018 import java.awt.Component; 019 import java.io.IOException; 020 import java.io.StreamTokenizer; 021 import java.io.StringReader; 022 import java.util.ArrayList; 023 import java.util.Collections; 024 import java.util.HashMap; 025 import java.util.HashSet; 026 import java.util.Iterator; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Set; 030 031 import javax.swing.JComponent; 032 import javax.swing.JPanel; 033 034 import org.springframework.richclient.application.Application; 035 import org.springframework.richclient.application.ApplicationServicesLocator; 036 import org.springframework.richclient.factory.ComponentFactory; 037 import org.springframework.richclient.util.CustomizableFocusTraversalPolicy; 038 import org.springframework.util.Assert; 039 import org.springframework.util.StringUtils; 040 041 import com.jgoodies.forms.factories.FormFactory; 042 import com.jgoodies.forms.layout.CellConstraints; 043 import com.jgoodies.forms.layout.ColumnSpec; 044 import com.jgoodies.forms.layout.FormLayout; 045 import com.jgoodies.forms.layout.RowSpec; 046 047 /** 048 * A panel builder that provides the capability to quickly build grid based 049 * forms. The builder allows for layout to be defined in a way that will be 050 * familiar to anyone used to HTML tables and JGoodies Forms. Key features: 051 * <ul> 052 * <li>unlike HTML, cells automatically span all empty columns to the right. 053 * This can be disabled by setting "colSpan=1"</li> 054 * <li>support for gap rows and columns. You don't need to keep track of gap 055 * rows or columns when specifying row or column spans</li> 056 * <li>need only define colSpec and rowSpec when it varies from the default. 057 * Save you having to work out column specs before you start laying out the form 058 * </li> 059 * <li>rows and columns can be aliased with a group ID which save you having to 060 * keep track of row or column indexes for grouping. This also makes grouping 061 * less fragile when the table layout changes</li> 062 * </ul> 063 * <strong>Example: </strong> <br> 064 * <pre> 065 * TableLayoutBuilder table = new TableLayoutBuilder(); 066 * table 067 * .row() 068 * .separator("General 1") 069 * .row() 070 * .cell(new JLabel("Company"), "colSpec=right:pref colGrId=labels") 071 * .labelGapCol() 072 * .cell(new JFormattedTextField()) 073 * .row() 074 * .cell(new JLabel("Contact")) 075 * .cell(new JFormattedTextField()) 076 * .unrelatedGapRow() 077 * .separator("Propeller") 078 * .row() 079 * .cell(new JLabel("PTI [kW]")).cell(new JFormattedTextField()) 080 * .unrelatedGapCol() 081 * .cell(new JLabel("Description"), "colSpec=right:pref colGrId=labels") 082 * .labelGapCol() 083 * .cell(new JScrollPane(new JTextArea()), "rowspan=3") 084 * .row() 085 * .cell(new JLabel("R [mm]")) 086 * .cell(new JFormattedTextField()) 087 * .cell() 088 * .row() 089 * .cell(new JLabel("D [mm]")) 090 * .cell(new JFormattedTextField()) 091 * .cell(); 092 * table.getPanel(); 093 * </pre> 094 * 095 * @author oliverh 096 */ 097 public class TableLayoutBuilder implements LayoutBuilder { 098 099 public static final String DEFAULT_LABEL_ATTRIBUTES = "colGrId=label colSpec=left:pref"; 100 101 public static final String ALIGN = "align"; 102 103 public static final String VALIGN = "valign"; 104 105 public static final String ROWSPEC = "rowSpec"; 106 107 public static final String COLSPEC = "colSpec"; 108 109 public static final String ROWSPAN = "rowSpan"; 110 111 public static final String COLSPAN = "colSpan"; 112 113 public static final String ROWGROUPID = "rowGrId"; 114 115 public static final String COLGROUPID = "colGrId"; 116 117 /** Constant indicating column major focus traversal order. */ 118 public static final int COLUMN_MAJOR_FOCUS_ORDER = 1; 119 120 /** Constant indicating row major focus traversal order. */ 121 public static final int ROW_MAJOR_FOCUS_ORDER = 2; 122 123 private List rowSpecs = new ArrayList(); 124 125 private List rowOccupiers = new ArrayList(); 126 127 private List columnSpecs = new ArrayList(); 128 129 private Map gapCols = new HashMap(); 130 131 private Map gapRows = new HashMap(); 132 133 private Map rowGroups = new HashMap(); 134 135 private Map colGroups = new HashMap(); 136 137 private int[][] adjustedColGroupIndices; 138 139 private int[][] adjustedRowGroupIndices; 140 141 private Cell lastCC = null; 142 143 private int maxColumns = 0; 144 145 private int currentRow = -1; 146 147 private int currentCol = 0; 148 149 private List items = new ArrayList(); 150 151 private JPanel panel; 152 153 private List focusOrder = null; 154 155 private ComponentFactory componentFactory; 156 157 /** 158 * Creates a new TableLayoutBuilder. 159 */ 160 public TableLayoutBuilder() { 161 // this will cause the panel to be lazily 162 // created in the getPanel method 163 this.panel = null; 164 } 165 166 /** 167 * Creates a new TableLayoutBuilder which will perform it's layout 168 * in the supplied JPanel. Note that any components that are already 169 * contained by the panel will be removed. 170 */ 171 public TableLayoutBuilder(JPanel panel) { 172 Assert.notNull(panel, "panel is required"); 173 this.panel = panel; 174 panel.removeAll(); 175 } 176 177 /** 178 * Returns the {@link ComponentFactory}that this uses to create things like 179 * labels. 180 * 181 * @return if not explicitly set, this uses the {@link Application}'s 182 */ 183 public ComponentFactory getComponentFactory() { 184 if (componentFactory == null) { 185 componentFactory = (ComponentFactory)ApplicationServicesLocator.services().getService(ComponentFactory.class); 186 } 187 return componentFactory; 188 } 189 190 /** 191 * Sets the {@link ComponentFactory}that this uses to create things like 192 * labels. 193 */ 194 public void setComponentFactory(ComponentFactory componentFactory) { 195 this.componentFactory = componentFactory; 196 } 197 198 /** 199 * Returns the current row (zero-based) that the builder is putting 200 * components in. 201 */ 202 public int getCurrentRow() { 203 return currentRow == -1 ? 0 : currentRow; 204 } 205 206 /** 207 * Returns the current column (zero-based) that the builder is putting 208 * components in. 209 */ 210 public int getCurrentCol() { 211 return currentCol; 212 } 213 214 /** 215 * Inserts a new row. No gap row is inserted before this row. 216 */ 217 public TableLayoutBuilder row() { 218 ++currentRow; 219 lastCC = null; 220 maxColumns = Math.max(maxColumns, currentCol); 221 currentCol = 0; 222 return this; 223 } 224 225 /** 226 * Inserts a new row. A gap row with specified RowSpec will be inserted 227 * before this row. 228 */ 229 public TableLayoutBuilder row(String gapRowSpec) { 230 return row(new RowSpec(gapRowSpec)); 231 } 232 233 /** 234 * Inserts a new row. A gap row with specified RowSpec will be inserted 235 * before this row. 236 */ 237 public TableLayoutBuilder row(RowSpec gapRowSpec) { 238 row(); 239 gapRows.put(new Integer(currentRow), gapRowSpec); 240 return this; 241 } 242 243 /** 244 * Inserts a new row. A related component gap row will be inserted before 245 * this row. 246 */ 247 public TableLayoutBuilder relatedGapRow() { 248 return row(FormFactory.RELATED_GAP_ROWSPEC); 249 } 250 251 /** 252 * Inserts a new row. An unrelated component gap row will be inserted before 253 * this row. 254 */ 255 public TableLayoutBuilder unrelatedGapRow() { 256 return row(FormFactory.UNRELATED_GAP_ROWSPEC); 257 } 258 259 /** 260 * Inserts an empty cell at the current row/column. 261 */ 262 public TableLayoutBuilder cell() { 263 return cell(""); 264 } 265 266 /** 267 * Inserts an empty cell at the current row/column. Attributes may be zero or 268 * more of rowSpec, columnSpec, colGrId and rowGrId. 269 */ 270 public TableLayoutBuilder cell(String attributes) { 271 cellInternal(null, attributes); 272 return this; 273 } 274 275 /** 276 * Inserts a component at the current row/column. 277 */ 278 public TableLayoutBuilder cell(JComponent component) { 279 return cell(component, ""); 280 } 281 282 /** 283 * Inserts a component at the current row/column. Attributes may be zero or 284 * more of rowSpec, columnSpec, colGrId, rowGrId, align and valign. 285 */ 286 public TableLayoutBuilder cell(JComponent component, String attributes) { 287 Cell cc = cellInternal(component, attributes); 288 lastCC = cc; 289 items.add(cc); 290 return this; 291 } 292 293 /** 294 * Inserts a related component gap column. 295 */ 296 public TableLayoutBuilder gapCol() { 297 return relatedGapCol(); 298 } 299 300 /** 301 * Inserts a gap column with the specified colSpec. 302 */ 303 public TableLayoutBuilder gapCol(String colSpec) { 304 return gapCol(new ColumnSpec(colSpec)); 305 } 306 307 /** 308 * Inserts a gap column with the specified colSpec. 309 */ 310 public TableLayoutBuilder gapCol(ColumnSpec colSpec) { 311 gapCols.put(new Integer(currentCol), colSpec); 312 return this; 313 } 314 315 /** 316 * Inserts a label component gap column. 317 */ 318 public TableLayoutBuilder labelGapCol() { 319 return gapCol(FormFactory.LABEL_COMPONENT_GAP_COLSPEC); 320 } 321 322 /** 323 * Inserts a unrelated component gap column. 324 */ 325 public TableLayoutBuilder unrelatedGapCol() { 326 return gapCol(FormFactory.UNRELATED_GAP_COLSPEC); 327 } 328 329 /** 330 * Inserts a related component gap column. 331 */ 332 public TableLayoutBuilder relatedGapCol() { 333 return gapCol(FormFactory.RELATED_GAP_COLSPEC); 334 } 335 336 /** 337 * Inserts a separator with the given label. 338 * 339 * @deprecated this is a layout builder, creating components should be done elsewhere, use cell() methods instead 340 */ 341 public TableLayoutBuilder separator(String labelKey) { 342 return separator(labelKey, ""); 343 } 344 345 /** 346 * Inserts a separator with the given label. Attributes my be zero or more of 347 * rowSpec, columnSpec, colGrId, rowGrId, align and valign. 348 * 349 * @deprecated this is a layout builder, creating components should be done elsewhere, use cell() methods instead 350 */ 351 public TableLayoutBuilder separator(String labelKey, String attributes) { 352 Cell cc = cellInternal(getComponentFactory().createLabeledSeparator(labelKey), attributes); 353 lastCC = cc; 354 items.add(cc); 355 return this; 356 } 357 358 /** 359 * Return true if there is a gap column to the left of the current cell 360 */ 361 public boolean hasGapToLeft() { 362 return currentCol == 0 || gapCols.get(new Integer(currentCol)) != null; 363 } 364 365 /** 366 * Return true if there is a gap row above of the current cell 367 */ 368 public boolean hasGapAbove() { 369 return currentRow == 0 || gapRows.get(new Integer(currentRow)) != null; 370 } 371 372 /** 373 * Creates and returns a JPanel with all the given components in it, using 374 * the "hints" that were provided to the builder. 375 * 376 * @return a new JPanel with the components laid-out in it 377 */ 378 public JPanel getPanel() { 379 if (panel == null) { 380 panel = getComponentFactory().createPanel(); 381 } 382 insertMissingSpecs(); 383 fixColSpans(); 384 fillInGaps(); 385 fillPanel(); 386 387 if( focusOrder != null ) { 388 installFocusOrder( focusOrder ); 389 } 390 391 return panel; 392 } 393 394 private Cell cellInternal(JComponent component, String attributes) { 395 nextCol(); 396 Map attributeMap = getAttributes(attributes); 397 RowSpec rowSpec = getRowSpec(getAttribute(ROWSPEC, attributeMap, "")); 398 if (rowSpec != null) { 399 setRowSpec(currentRow, rowSpec); 400 } 401 ColumnSpec columnSpec = getColumnSpec(getAttribute(COLSPEC, attributeMap, "")); 402 if (columnSpec != null) { 403 setColumnSpec(currentCol, columnSpec); 404 } 405 addRowGroup(getAttribute(ROWGROUPID, attributeMap, null)); 406 addColGroup(getAttribute(COLGROUPID, attributeMap, null)); 407 408 Cell cc = createCell(component, attributeMap); 409 currentCol = cc.endCol < cc.startCol ? cc.startCol : cc.endCol; 410 markContained(cc); 411 return cc; 412 } 413 414 private void addRowGroup(String groupId) { 415 if (StringUtils.hasText(groupId)) { 416 Set group = (Set)rowGroups.get(groupId); 417 if (group == null) { 418 group = new HashSet(); 419 rowGroups.put(groupId, group); 420 } 421 group.add(new Integer(getCurrentRow())); 422 } 423 } 424 425 private void addColGroup(String groupId) { 426 if (StringUtils.hasText(groupId)) { 427 Set group = (Set)colGroups.get(groupId); 428 if (group == null) { 429 group = new HashSet(); 430 colGroups.put(groupId, group); 431 } 432 group.add(new Integer(currentCol)); 433 } 434 } 435 436 private void setRowSpec(int row, RowSpec rowSpec) { 437 if (row >= rowSpecs.size()) { 438 int missingSpecs = row - rowSpecs.size() + 1; 439 for (int i = 0; i < missingSpecs; i++) { 440 rowSpecs.add(getDefaultRowSpec()); 441 } 442 } 443 rowSpecs.set(row, rowSpec); 444 } 445 446 private void setColumnSpec(int col, ColumnSpec columnSpec) { 447 col = col - 1; 448 if (col >= columnSpecs.size()) { 449 int missingSpecs = col - columnSpecs.size() + 1; 450 for (int i = 0; i < missingSpecs; i++) { 451 columnSpecs.add(getDefaultColSpec()); 452 } 453 } 454 columnSpecs.set(col, columnSpec); 455 } 456 457 private RowSpec getRowSpec(String rowSpec) { 458 if (StringUtils.hasText(rowSpec)) 459 return new RowSpec(rowSpec); 460 461 return null; 462 } 463 464 private ColumnSpec getColumnSpec(String columnSpec) { 465 if (StringUtils.hasText(columnSpec)) 466 return new ColumnSpec(columnSpec); 467 468 return null; 469 } 470 471 private void nextCol() { 472 if (lastCC != null && lastCC.endCol < lastCC.startCol) { 473 lastCC.endCol = lastCC.startCol; 474 lastCC = null; 475 } 476 // make sure that the first row has been created 477 if (currentRow == -1) { 478 row(); 479 } 480 // now find the first unoccupied column 481 do { 482 ++currentCol; 483 } 484 while (getOccupier(currentRow, currentCol) != null); 485 } 486 487 private Cell getOccupier(int row, int col) { 488 List occupiers = getOccupiers(row); 489 if (col >= occupiers.size()) { 490 return null; 491 } 492 return (Cell)occupiers.get(col); 493 } 494 495 private List getOccupiers(int row) { 496 if (row >= rowOccupiers.size()) { 497 int numMissingRows = (row - rowOccupiers.size()) + 1; 498 for (int i = 0; i < numMissingRows; i++) { 499 rowOccupiers.add(new ArrayList()); 500 } 501 } 502 return (List)rowOccupiers.get(row); 503 } 504 505 private void markContained(Cell cc) { 506 setOccupier(cc, cc.startRow, cc.endRow, cc.startCol, cc.endCol < cc.startCol ? cc.startCol : cc.endCol); 507 } 508 509 private void setOccupier(Cell occupier, int startRow, int endRow, int startCol, int endCol) { 510 for (int row = startRow; row <= endRow; row++) { 511 List occupiers = getOccupiers(row); 512 if (endCol >= occupiers.size()) { 513 int numMissingCols = (endCol - occupiers.size()) + 1; 514 for (int i = 0; i < numMissingCols; i++) { 515 occupiers.add(null); 516 } 517 } 518 for (int i = startCol; i <= endCol; i++) { 519 occupiers.set(i, occupier); 520 } 521 } 522 523 } 524 525 private Cell createCell(JComponent component, Map attributes) { 526 String align = getAttribute(ALIGN, attributes, "default"); 527 String valign = getAttribute(VALIGN, attributes, "default"); 528 int colSpan; 529 try { 530 colSpan = Integer.parseInt(getAttribute(COLSPAN, attributes, "-1")); 531 } 532 catch (NumberFormatException e) { 533 throw new IllegalArgumentException("Attribute 'colspan' must be an integer."); 534 } 535 int rowSpan; 536 try { 537 rowSpan = Integer.parseInt(getAttribute(ROWSPAN, attributes, "1")); 538 } 539 catch (NumberFormatException e) { 540 throw new IllegalArgumentException("Attribute 'rowspan' must be an integer."); 541 } 542 543 return new Cell(component, getCurrentCol(), getCurrentRow(), colSpan, rowSpan, align + "," + valign); 544 } 545 546 private void fixColSpans() { 547 for (Iterator i = items.iterator(); i.hasNext();) { 548 Cell cc = (Cell)i.next(); 549 if (cc.endCol < cc.startCol) { 550 int endCol = cc.startCol; 551 while (endCol < maxColumns && getOccupier(cc.startRow, endCol + 1) == null) { 552 ++endCol; 553 } 554 cc.endCol = endCol; 555 } 556 markContained(cc); 557 } 558 } 559 560 private void fillInGaps() { 561 List adjustedCols = new ArrayList(); 562 int adjustedCol = 0; 563 for (int col = 0; col < maxColumns; col++, adjustedCol++) { 564 ColumnSpec colSpec = (ColumnSpec)gapCols.get(new Integer(col)); 565 if (colSpec != null) { 566 columnSpecs.add(adjustedCol, colSpec); 567 adjustedCol++; 568 } 569 adjustedCols.add(new Integer(adjustedCol + 1)); 570 } 571 List adjustedRows = new ArrayList(); 572 int adjustedRow = 0; 573 int numRows = rowSpecs.size(); 574 for (int row = 0; row < numRows; row++, adjustedRow++) { 575 RowSpec rowSpec = (RowSpec)gapRows.get(new Integer(row)); 576 if (rowSpec != null) { 577 rowSpecs.add(adjustedRow, rowSpec); 578 adjustedRow++; 579 } 580 adjustedRows.add(new Integer(adjustedRow)); 581 } 582 for (Iterator i = items.iterator(); i.hasNext();) { 583 Cell cc = (Cell)i.next(); 584 cc.startCol = ((Integer)adjustedCols.get(cc.startCol - 1)).intValue(); 585 cc.endCol = ((Integer)adjustedCols.get(cc.endCol - 1)).intValue(); 586 cc.startRow = ((Integer)adjustedRows.get(cc.startRow)).intValue(); 587 cc.endRow = ((Integer)adjustedRows.get(cc.endRow)).intValue(); 588 } 589 adjustedColGroupIndices = new int[colGroups.size()][]; 590 int groupsCount = 0; 591 for (Iterator i = colGroups.values().iterator(); i.hasNext();) { 592 Set group = (Set)i.next(); 593 adjustedColGroupIndices[groupsCount] = new int[group.size()]; 594 int groupCount = 0; 595 for (Iterator j = group.iterator(); j.hasNext();) { 596 adjustedColGroupIndices[groupsCount][groupCount++] = ((Integer)adjustedCols.get(((Integer)j.next()).intValue() - 1)).intValue(); 597 } 598 groupsCount++; 599 } 600 601 adjustedRowGroupIndices = new int[rowGroups.size()][]; 602 groupsCount = 0; 603 for (Iterator i = rowGroups.values().iterator(); i.hasNext();) { 604 Set group = (Set)i.next(); 605 adjustedRowGroupIndices[groupsCount] = new int[group.size()]; 606 int groupCount = 0; 607 for (Iterator j = group.iterator(); j.hasNext();) { 608 adjustedRowGroupIndices[groupsCount][groupCount++] = ((Integer)adjustedRows.get(((Integer)j.next()).intValue() - 1)).intValue(); 609 } 610 groupsCount++; 611 } 612 } 613 614 /** 615 * Set the focus traversal order. 616 * @param order forcus traversal order. Must be one of {@link #COLUMN_MAJOR_FOCUS_ORDER} 617 * or {@link #ROW_MAJOR_FOCUS_ORDER}. 618 */ 619 public void setFocusTraversalOrder( int traversalOrder ) { 620 Assert.isTrue( traversalOrder == COLUMN_MAJOR_FOCUS_ORDER || traversalOrder == ROW_MAJOR_FOCUS_ORDER, 621 "traversalOrder must be one of COLUMN_MAJOR_FOCUS_ORDER or ROW_MAJOR_FOCUS_ORDER"); 622 623 List focusOrder = new ArrayList(items.size()); 624 625 if( traversalOrder == ROW_MAJOR_FOCUS_ORDER ) { 626 for( int row=0; row < rowOccupiers.size() - 1; row++ ) { 627 for( int col=0; col < maxColumns; col++ ) { 628 Cell currentCell = getOccupier(row, col); 629 if (currentCell != null && !focusOrder.contains(currentCell.getComponent())) { 630 focusOrder.add(currentCell.getComponent()); 631 } 632 } 633 } 634 } else if( traversalOrder == COLUMN_MAJOR_FOCUS_ORDER ) { 635 for( int col = 0; col < maxColumns; col++ ) { 636 for( int row = 0; row < rowOccupiers.size() - 1; row++ ) { 637 Cell currentCell = getOccupier( row, col ); 638 if( currentCell != null && !focusOrder.contains( currentCell.getComponent() ) ) { 639 focusOrder.add( currentCell.getComponent() ); 640 } 641 } 642 } 643 } 644 645 setCustomFocusTraversalOrder( focusOrder ); 646 } 647 648 /** 649 * Set a custom focus traversal order using the provided list of components. 650 * @param focusOrder List of components in the order that focus should follow. 651 */ 652 public void setCustomFocusTraversalOrder( List focusOrder ) { 653 this.focusOrder = focusOrder; 654 } 655 656 /** 657 * Install the specified focus order. 658 * @param focusOrder List of components in the order that focus should follow. 659 */ 660 protected void installFocusOrder( List focusOrder ) { 661 CustomizableFocusTraversalPolicy.installCustomizableFocusTraversalPolicy(); 662 CustomizableFocusTraversalPolicy.customizeFocusTraversalOrder(panel, focusOrder); 663 } 664 665 private void fillPanel() { 666 panel.setLayout(createLayout()); 667 for (Iterator i = items.iterator(); i.hasNext();) { 668 Cell cc = (Cell)i.next(); 669 panel.add((Component)cc.getComponent(), cc.getCellConstraints()); 670 } 671 } 672 673 private FormLayout createLayout() { 674 ColumnSpec[] columnSpecsArray = (ColumnSpec[])columnSpecs.toArray(new ColumnSpec[columnSpecs.size()]); 675 RowSpec[] rowSpecArray = (RowSpec[])rowSpecs.toArray(new RowSpec[rowSpecs.size()]); 676 FormLayout layout = new FormLayout(columnSpecsArray, rowSpecArray); 677 layout.setColumnGroups(adjustedColGroupIndices); 678 layout.setRowGroups(adjustedRowGroupIndices); 679 return layout; 680 } 681 682 private void insertMissingSpecs() { 683 maxColumns = Math.max(maxColumns, currentCol); 684 if (columnSpecs.size() < maxColumns) { 685 setColumnSpec(maxColumns, getDefaultColSpec()); 686 } 687 688 if (rowSpecs.size() <= getCurrentRow()) { 689 setRowSpec(getCurrentRow(), getDefaultRowSpec()); 690 } 691 } 692 693 private RowSpec getDefaultRowSpec() { 694 return FormFactory.DEFAULT_ROWSPEC; 695 } 696 697 private ColumnSpec getDefaultColSpec() { 698 return new ColumnSpec("default:grow"); 699 } 700 701 private static final Set allowedAttributes; 702 static { 703 allowedAttributes = new HashSet(); 704 allowedAttributes.add(COLSPAN.toLowerCase()); 705 allowedAttributes.add(ROWSPAN.toLowerCase()); 706 allowedAttributes.add(COLSPEC.toLowerCase()); 707 allowedAttributes.add(ROWSPEC.toLowerCase()); 708 allowedAttributes.add(ALIGN.toLowerCase()); 709 allowedAttributes.add(VALIGN.toLowerCase()); 710 allowedAttributes.add(ROWGROUPID.toLowerCase()); 711 allowedAttributes.add(COLGROUPID.toLowerCase()); 712 } 713 714 private String getAttribute(String name, Map attributeMap, String defaultValue) { 715 String value = (String)attributeMap.get(name.toLowerCase()); 716 if (value == null) { 717 value = defaultValue; 718 } 719 return value; 720 } 721 722 private Map getAttributes(String attributes) { 723 if(!StringUtils.hasText(attributes)) 724 return Collections.EMPTY_MAP; 725 726 Map attributeMap = new HashMap(); 727 try { 728 StreamTokenizer st = new StreamTokenizer(new StringReader(attributes)); 729 st.resetSyntax(); 730 st.wordChars(33, 126); 731 st.wordChars(128 + 32, 255); 732 st.whitespaceChars(0, ' '); 733 st.quoteChar('"'); 734 st.quoteChar('\''); 735 st.ordinaryChar('='); 736 737 String name = null; 738 boolean needEquals = false; 739 740 while (st.nextToken() != StreamTokenizer.TT_EOF) { 741 if (name == null && st.ttype == StreamTokenizer.TT_WORD) { 742 name = st.sval; 743 if (!allowedAttributes.contains(name.toLowerCase())) { 744 throw new IllegalArgumentException("Attribute name '" + name + "' not recognised."); 745 } 746 needEquals = true; 747 } 748 else if (needEquals && st.ttype == '=') { 749 needEquals = false; 750 } 751 else if (name != null && (st.ttype == StreamTokenizer.TT_WORD || st.ttype == '\'' | st.ttype == '"')) { 752 attributeMap.put(name.toLowerCase(), st.sval); 753 name = null; 754 } 755 else { 756 throw new IllegalArgumentException("Expecting '=' but found '" + st.sval + "'"); 757 } 758 } 759 if (needEquals || name != null) { 760 throw new IllegalArgumentException("Premature end of string. Expecting " 761 + (needEquals ? " '='." : " value for attribute '" + name + "'.")); 762 } 763 } 764 catch (IOException e) { 765 throw new UnsupportedOperationException("Encountered unexpected IOException. " + e.getMessage()); 766 } 767 768 return attributeMap; 769 } 770 771 private static class Cell { 772 private JComponent component; 773 774 private int startCol; 775 776 private int startRow; 777 778 private int endCol; 779 780 private int endRow; 781 782 private String align; 783 784 public Cell(JComponent component, int x, int y, int w, int h, String align) { 785 this.component = component; 786 this.startCol = x; 787 this.startRow = y; 788 this.endCol = x + w - 1; 789 this.endRow = y + h - 1; 790 this.align = align; 791 } 792 793 public Object getComponent() { 794 return component; 795 } 796 797 public CellConstraints getCellConstraints() { 798 return new CellConstraints().xywh(startCol, startRow + 1, endCol - startCol + 1, endRow - startRow + 1, 799 align); 800 } 801 } 802 }