PageRenderTime 142ms CodeModel.GetById 82ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 1ms

/bundles/plugins-trunk/CommonControls/ise/java/awt/KappaLayout.java

#
Java | 1356 lines | 773 code | 112 blank | 471 comment | 200 complexity | 3a53cc3465fb2dfea648159d6e03aef1 MD5 | raw file
   1
   2package ise.java.awt;
   3
   4import java.awt.LayoutManager2;
   5import java.awt.Component;
   6import java.awt.Container;
   7import java.awt.Dimension;
   8import java.awt.Insets;
   9import java.awt.Point;
  10import java.io.Serializable;
  11import java.util.BitSet;
  12import java.util.Enumeration;
  13import java.util.Hashtable;
  14import java.util.Vector;
  15
  16/**
  17 * KappaLayout, a Java layout manager.<br>
  18 * Copyright (C) 2000, Dale Anson<br>
  19 *<br>
  20 * This library is free software; you can redistribute it and/or<br>
  21 * modify it under the terms of the GNU Lesser General Public<br>
  22 * License as published by the Free Software Foundation; either<br>
  23 * version 2.1 of the License, or (at your option) any later version.<br>
  24 *<br>
  25 * This library is distributed in the hope that it will be useful,<br>
  26 * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>
  27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU<br>
  28 * Lesser General Public License for more details.<br>
  29 *<br>
  30 * You should have received a copy of the GNU Lesser General Public<br>
  31 * License along with this library; if not, write to the Free Software<br>
  32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA<br>
  33 * <p>
  34 * KappaLayout -- similar to others, but this one's simpler and easier to use.<br>
  35 * Example use:<br>
  36 * This will put a button on a panel in the top of its cell, stretched to
  37 * fill the cell width, with a 3 pixel pad:<br>
  38 * <code>
  39 * Panel p = new Panel(new KappaLayout());
  40 * Button b = new Button("OK");
  41 * p.add(b, "0, 0, 1, 2, 2, w, 3");
  42 * </code>
  43 * <br>
  44 * The constraints string has this layout:<br>
  45 * "x, y, w, h, a, s, p"<br>
  46 * defined as follows:<br>
  47 * <ul>
  48 * <li>'x' is the column to put the component, default is 0<br>
  49 * <li>'y' is the row to put the component, default is 0<br>
  50 * <li>'w' is the width of the component in columns (column span), default is 1.
  51 * Can also be R or r, which means the component will span the remaining cells
  52 * in the row.<br>
  53 * <li>'h' is the height of the component in rows (row span), default is 1.
  54 * Can also be R or r, which means the component will span the remaining cells
  55 * in the column.<br>
  56 * <li>'a' is the alignment within the cell. 'a' can be a value between 0 and 8,
  57 * inclusive, (default is 0) and causes the alignment of the component within the cell to follow
  58 * this pattern:<br>
  59 * 8 1 2<br>
  60 * 7 0 3<br>
  61 * 6 5 4<br>, or<br>
  62 * 0 horizontal center, vertical center,<br>
  63 * 1 horizontal center, vertical top,<br>
  64 * 2 horizontal right, vertical top,<br>
  65 * 3 horizontal right, vertical center,<br>
  66 * 4 horizontal right, vertical bottom,<br>
  67 * 5 horizontal center, vertical bottom,<br>
  68 * 6 horizontal left, vertical bottom,<br>
  69 * 7 horizontal left, vertical center,<br>
  70 * 8 horizontal left, vertical top.<br>
  71 * <p>
  72 * By popular request, the alignment constraint can also be represented as:<br>
  73 * NW N NE<br>
  74 * &nbsp;W 0 E<br>
  75 * SW S SE<br>
  76 * which are compass directions for alignment within the cell.
  77 * <li>'s' is the stretch value. 's' can have these values:<br>
  78 * 'w' stretch to fill cell width<br>
  79 * 'h' stretch to fill cell height<br>
  80 * 'wh' or 'hw' stretch to fill both cell width and cell height<br>
  81 * '0' (character 'zero') no stretch (default)
  82 * <li>'p' is the amount of padding to put around the component. This much blank
  83 * space will be applied on all sides of the component, default is 0.
  84 * </ul>
  85 * Parameters may be omitted (default  values will be used), e.g.,
  86 * <code> p.add(new Button("OK), "1,4,,,w,");</code><br>
  87 * which means put the button at column 1, row 4, default 1 column wide, default
  88 * 1 row tall, stretch to fit width of column, no padding. <br>
  89 * Spaces in the parameter string are ignored, so these are identical:<br>
  90 * <code> p.add(new Button("OK), "1,4,,,w,");</code><br>
  91 * <code> p.add(new Button("OK), " 1, 4,   , , w");</code><p>
  92 * Rather than use a constraints string, a Constraints object may be used
  93 * directly, similar to how GridBag uses a GridBagConstraint. E.g,<br>
  94 * <code>
  95 * Panel p = new Panel();<br>
  96 * KappaLayout tl = new KappaLayout();<br>
  97 * p.setLayout(tl);<br>
  98 * KappaLayout.Constraints con = tl.getConstraint();<br>
  99 * con.x = 1;<br>
 100 * con.y = 2;<br>
 101 * con.w = 2;<br>
 102 * con.h = 2;<br>
 103 * con.s = "wh";<br>
 104 * panel.add(new Button("OK"), con);<br>
 105 * con.x = 3;<br>
 106 * panel.add(new Button("Cancel"), con);<br>
 107 * </code><br>
 108 * Note that the same Constraints can be reused, thereby reducing the number of
 109 * objects created.<p>
 110 * @author Dale Anson
 111 */
 112
 113public class KappaLayout implements LayoutManager2, Serializable {
 114   // overall preferred width of components in container
 115   protected int _preferred_width = 0;
 116
 117   // overall preferred height of components in container
 118   protected int _preferred_height = 0;
 119
 120   protected boolean _size_unknown = true;
 121
 122   protected int _col_count = 0;    // number of columns in the layout
 123   protected int _row_count = 0;    // number of rows in the layout
 124
 125   // storage for component constraints
 126   // key is the component, value is a Constraints
 127   protected Hashtable _constraints = new Hashtable();
 128
 129   // model of the table -- key is a Point with Point.x
 130   // representing the column, Point.y representing the row. As most layouts
 131   // are sparse, this is an efficient way to represent the table without
 132   // creating excess objects. The value is the dimension of the component.
 133   protected Hashtable _table = null;
 134
 135   // a Dimension with no width and no height, used repeatedly to represent
 136   // empty cells.
 137   protected Dimension _0dim = new Dimension( 0, 0 );
 138
 139   // model of the component layout in the table -- key is a Point with Point.x
 140   // representing the column, Point.y representing the row. The value is a
 141   // reference to the component.
 142   protected Hashtable _components = null;
 143
 144   // in both _col_widths and _row_heights, if the number is negative, the column or
 145   // row will be treated as having a fixed width or height. During layout, the negative
 146   // numbers will be treated as positive numbers -- negative width and negative height
 147   // being meaningless concepts.
 148   protected int[] _col_widths;    // stores a width per column which is the widest preferred width of components in each column
 149   protected int[] _row_heights;   // stores a height per row which is the tallest preferred height of components in each row
 150
 151   // storage for columns and rows that want to be the same size. Each element
 152   // in these vectors is an int[], with each item in the array being a column
 153   // or row number.
 154   protected Vector _same_width_cols;
 155   protected Vector _same_height_rows;
 156
 157   // if true, spread extra space equally between components in both directions,
 158   // doesn't stretch the components, just the space between them
 159   protected boolean _stretch = false;
 160
 161   // by user request, the following are alternate compass directions for
 162   // the alignment constraint. Center is still 0.
 163   /**
 164    * For alignment constraint (a), align component to North in cell.
 165    */
 166   public static final int N = 1;  // north
 167   /**
 168    * For alignment constraint (a), align component to NorthEast in cell.
 169    */
 170   public static final int NE = 2;  // northeast
 171   /**
 172    * For alignment constraint (a), align component to East in cell.
 173    */
 174   public static final int E = 3;  // east
 175   /**
 176    * For alignment constraint (a), align component to SouthEast in cell.
 177    */
 178   public static final int SE = 4;  // southeast
 179   /**
 180    * For alignment constraint (a), align component to South in cell.
 181    */
 182   public static final int S = 5;  // south
 183   /**
 184    * For alignment constraint (a), align component to SouthWest in cell.
 185    */
 186   public static final int SW = 6;  // southwest
 187   /**
 188    * For alignment constraint (a), align component to West in cell.
 189    */
 190   public static final int W = 7;  // west
 191   /**
 192    * For alignment constraint (a), align component to NorthWest in cell.
 193    */
 194   public static final int NW = 8;  // northwest
 195
 196   /**
 197    * Convenience setting for width (w) or height (h), causes component to use 
 198    * Remaining cells.
 199    */
 200   public static final int R = Integer.MAX_VALUE;
 201
 202
 203   /**
 204    * Default constructor, no stretching.
 205    */
 206   public KappaLayout() {
 207      this( false );
 208   }
 209
 210   /**
 211    * Constructor, allows stretching.
 212    * @param s if true, stretches layout to fill container by adding extra space
 213    * between components, does not stretch the components, just the space between them.
 214    */
 215   public KappaLayout( boolean s ) {
 216      _stretch = s;
 217   }
 218
 219   /**
 220    * Useful for debugging a layout. Call after components are showing to make
 221    * sure all data is available.
 222    * @return a String with lots of useful info about the layout.
 223    */
 224   public String toString() {
 225      StringBuffer sb = new StringBuffer();
 226      sb.append( "-------------------------------\n" );
 227      sb.append( getClass().getName() + ":\n" );
 228      sb.append( "columns=" + _col_count );
 229      sb.append( ", rows=" + _row_count );
 230      sb.append( ", cells=" + ( _col_count * _row_count ) + "\n" );
 231      sb.append( "preferred width=" + _preferred_width );
 232      sb.append( ", preferred height=" + _preferred_height + "\n" );
 233      if ( _col_widths != null ) {
 234         sb.append( "column widths (left to right):" );
 235         for ( int i = 0; i < _col_widths.length; i++ )
 236            sb.append( _col_widths[ i ] + "," );
 237      }
 238      if ( _row_heights != null ) {
 239         sb.append( "\nrow heights (top to bottom):" );
 240         for ( int i = 0; i < _row_heights.length; i++ )
 241            sb.append( _row_heights[ i ] + "," );
 242      }
 243      if ( _constraints != null ) {
 244         sb.append( "\ncomponent count=" + _constraints.size() );
 245         sb.append( "\ncomponents (no order):\n" );
 246         Enumeration en = _constraints.keys();
 247         while ( en.hasMoreElements() )
 248            sb.append( en.nextElement() + "\n" );
 249      }
 250      sb.append( "-------------------------------\n" );
 251      return sb.toString();
 252   }
 253
 254   /**
 255    * Required by LayoutManager, simply calls <code>addLayoutComponent(Component, Object)</code>.
 256    */
 257   public void addLayoutComponent( String n, Component c ) {
 258      addLayoutComponent( c, n );
 259   }
 260
 261   /**
 262    * Required by LayoutManager.
 263    */
 264   public void removeLayoutComponent( Component c ) {
 265      if (c == null)
 266         return;
 267      synchronized ( c.getTreeLock() ) {
 268         if ( _constraints != null ) {
 269            _constraints.remove( c );
 270            if (_components == null)
 271               return;
 272            Enumeration keys = _components.keys();
 273            while(keys.hasMoreElements()) {
 274               Object key = keys.nextElement();
 275               Object value = _constraints.get(key);
 276               if (value == null)
 277                  continue;
 278               if (value.equals(c)) {
 279                  _components.remove(key);
 280                  break;
 281               }
 282            }
 283            keys = _table.keys();
 284            while(keys.hasMoreElements()) {
 285               Object key = keys.nextElement();
 286               Object value = _constraints.get(key);
 287               if (value == null)
 288                  continue;
 289               if (value.equals(c)) {
 290                  _table.remove(key);
 291                  break;
 292               }
 293            }
 294            _size_unknown = true;
 295            calculateDimensions();
 296         }
 297      }
 298   }
 299
 300   /**
 301    * Required by LayoutManager.
 302    */
 303   public Dimension preferredLayoutSize( Container parent ) {
 304      synchronized ( parent.getTreeLock() ) {
 305         Dimension dim = new Dimension( 0, 0 );
 306         _size_unknown = true;
 307         calculateDimensions();
 308         Insets insets = parent.getInsets();
 309         dim.width = _preferred_width + insets.left + insets.right;
 310         dim.height = _preferred_height + insets.top + insets.bottom;
 311         return dim;
 312      }
 313   }
 314
 315   /**
 316    * Required by LayoutManager.
 317    * @return <code>preferredLayoutSize(parent)</code>
 318    */
 319   public Dimension minimumLayoutSize( Container parent ) {
 320      synchronized ( parent.getTreeLock() ) {
 321         return preferredLayoutSize( parent );
 322      }
 323   }
 324
 325   /**
 326    * Required by LayoutManager, does all the real layout work.
 327    */
 328   public void layoutContainer( Container parent ) {
 329      synchronized ( parent.getTreeLock() ) {
 330         Insets insets = parent.getInsets();
 331         int max_width = parent.getSize().width - ( insets.left + insets.right );
 332         int max_height = parent.getSize().height - ( insets.top + insets.bottom );
 333         int x = insets.left;    // x and y location to put component in pixels
 334         int y = insets.top;
 335         int xfill = 0;          // how much extra space to put between components
 336         int yfill = 0;          // when stretching to fill entire container
 337
 338         // make sure preferred size is known, a side effect is that countColumns
 339         // and countRows are automatically called.
 340         calculateDimensions();
 341
 342         // if necessary, calculate the amount of padding to add between the
 343         // components to fill the container
 344         if ( _stretch ) {
 345            if ( max_width > _preferred_width && _col_count > 1 ) {
 346               xfill = ( max_width - _preferred_width ) / ( _col_count - 1 );
 347            }
 348            if ( max_height > _preferred_height && _row_count > 1 ) {
 349               yfill = ( max_height - _preferred_height ) / ( _row_count - 1 );
 350            }
 351         }
 352
 353         // do the layout. Components are handled by columns, top to bottom,
 354         // left to right. i is current column, j is current row.
 355         Point cell = new Point();
 356         for ( int i = 0; i < _col_count; i++ ) {
 357            cell.x = i;
 358            y = insets.top;
 359            if ( i > 0 ) {
 360               x += Math.abs( _col_widths[ i - 1 ] );
 361               if ( _stretch && i < _col_count ) {
 362                  x += xfill;
 363               }
 364            }
 365
 366            for ( int j = 0; j < _row_count; j++ ) {
 367               cell.y = j;
 368               if ( j > 0 ) {
 369                  y += Math.abs( _row_heights[ j - 1 ] );
 370                  if ( _stretch && j < _row_count ) {
 371                     y += yfill;
 372                  }
 373               }
 374               Component c = ( Component ) _components.get( cell );
 375               if ( c != null && c.isVisible() ) {
 376                  Dimension d = c.getPreferredSize();
 377                  Constraints q = ( Constraints ) _constraints.get( c );
 378
 379                  // calculate width of spanned columns
 380                  int sum_cols = 0;
 381                  if ( q.w == R ) {
 382                     for ( int n = i; n < _col_count; n++ ) {
 383                        sum_cols += Math.abs( _col_widths[ n ] );
 384                     }
 385                  }
 386                  else {
 387                     for ( int n = i; n < i + q.w; n++ ) {
 388                        sum_cols += Math.abs( _col_widths[ n ] );
 389                     }
 390                  }
 391
 392                  // calculate height of spanned rows
 393                  int sum_rows = 0;
 394                  if ( q.h == R ) {
 395                     for ( int n = i; n < _row_count; n++ ) {
 396                        sum_rows += Math.abs( _row_heights[ n ] );
 397                     }
 398                  }
 399                  else {
 400                     for ( int n = j; n < j + q.h; n++ ) {
 401                        sum_rows += Math.abs( _row_heights[ n ] );
 402                     }
 403                  }
 404
 405                  // stretch and pad if required
 406                  if ( q.s.indexOf( "w" ) != -1 ) {
 407                     d.width = sum_cols - q.p * 2;
 408                  }
 409                  if ( q.s.indexOf( "h" ) != -1 ) {
 410                     d.height = sum_rows - q.p * 2;
 411                  }
 412
 413                  // calculate adjustment
 414                  int x_adj = sum_cols - d.width;  // max amount to put component at right edge of spanned cell(s)
 415                  int y_adj = sum_rows - d.height; // max amount to put component at bottom edge of spanned cell(s)
 416
 417                  // in each case, add the correction for the cell, then subtract
 418                  // the correction after applying it.  This prevents the corrections
 419                  // from improperly accumulating across cells. Padding must be handled
 420                  // explicitly for each case.
 421                  // Alignment follows this pattern within the spanned cells:
 422                  // 8 1 2    new pattern: NW N NE
 423                  // 7 0 3                  W 0 E
 424                  // 6 5 4                 SW S SE
 425                  switch ( q.a ) {
 426                     case N:      // top center
 427                        x += x_adj / 2 ;
 428                        y += q.p;
 429                        c.setBounds( x, y, d.width, d.height );
 430                        x -= x_adj / 2;
 431                        y -= q.p;
 432                        break;
 433                     case NE:     // top right
 434                        x += x_adj - q.p;
 435                        y += q.p;
 436                        c.setBounds( x, y, d.width, d.height );
 437                        x -= x_adj - q.p;
 438                        y -= q.p;
 439                        break;
 440                     case E:      // center right
 441                        x += x_adj - q.p;
 442                        y += y_adj / 2;
 443                        c.setBounds( x, y, d.width, d.height );
 444                        x -= x_adj - q.p;
 445                        y -= y_adj / 2;
 446                        break;
 447                     case SE:     // bottom right
 448                        x += x_adj - q.p;
 449                        y += y_adj - q.p;
 450                        c.setBounds( x, y, d.width, d.height );
 451                        x -= x_adj - q.p;
 452                        y -= y_adj - q.p;
 453                        break;
 454                     case S:      // bottom center
 455                        x += x_adj / 2;
 456                        y += y_adj - q.p;
 457                        c.setBounds( x, y, d.width, d.height );
 458                        x -= x_adj / 2;
 459                        y -= y_adj - q.p;
 460                        break;
 461                     case SW:     // bottom left
 462                        x += q.p;
 463                        y += y_adj - q.p;
 464                        c.setBounds( x, y, d.width, d.height );
 465                        x -= q.p;
 466                        y -= y_adj - q.p;
 467                        break;
 468                     case W:      // center left
 469                        x += q.p;
 470                        y += y_adj / 2;
 471                        c.setBounds( x, y, d.width, d.height );
 472                        x -= q.p;
 473                        y -= y_adj / 2;
 474                        break;
 475                     case NW:     // top left
 476                        x += q.p;
 477                        y += q.p;
 478                        c.setBounds( x, y, d.width, d.height );
 479                        x -= q.p;
 480                        y -= q.p;
 481                        break;
 482                     case 0:      // dead center
 483                     default:
 484                        x += x_adj / 2;
 485                        y += y_adj / 2;
 486                        c.setBounds( x, y, d.width, d.height );
 487                        x -= x_adj / 2;
 488                        y -= y_adj / 2;
 489                        break;
 490                  }
 491               }
 492            }
 493         }
 494      }
 495   }
 496
 497   /**
 498    * Required by LayoutManager2. Will NOT add component if either component or 
 499    * constraints are null, or if constraints is not a String or 
 500    * Kappa/LambdaLayout.Constraint.
 501    */
 502   public void addLayoutComponent( Component comp, Object constraint ) {
 503      synchronized ( comp.getTreeLock() ) {
 504         if ( comp == null ) {
 505            throw new IllegalArgumentException( "No component." );
 506         }
 507         if ( constraint == null ) {
 508            throw new IllegalArgumentException( "No constraint." );
 509         }
 510
 511         if ( constraint instanceof Constraints ) {
 512            // clone and store a Constraint so user can reuse his original
 513            Constraints q = ( Constraints ) constraint;
 514            _constraints.put( comp, q.clone() );
 515
 516            // if component is a rigid strut, set the column and/or row size
 517            if ( comp instanceof Strut ) {
 518               Strut strut = ( Strut ) comp;
 519               if ( strut.isRigid() ) {
 520                  Dimension d = strut.getSize();
 521                  if ( d.width > 0 ) {
 522                     setColumnWidth( q.x, d.width );
 523                  }
 524                  if ( d.height > 0 ) {
 525                     setRowHeight( q.y, d.height );
 526                  }
 527               }
 528            }
 529            _size_unknown = true;
 530
 531            // that's all that needs to be done for this case (Constraint and
 532            // Component), so return now
 533            return ;
 534         }
 535
 536         // already dealt with Constraint, so check for constraint
 537         // String, if not a String, bail out
 538         if ( !( constraint instanceof String ) )
 539            throw new IllegalArgumentException( "Illegal constraint object." );
 540
 541         // parse constraint string into tokens. There may be as few as 0 tokens,
 542         // or as many as 7.
 543         Vector tokens = new Vector();
 544         String token;
 545         String c = constraint.toString();
 546         while ( c.length() > 0 ) {
 547            int comma = c.indexOf( ',' );
 548
 549            // find a token
 550            if ( comma != -1 ) {
 551               token = c.substring( 0, comma );
 552               c = c.substring( comma + 1 );
 553            }
 554            else {
 555               token = c;
 556               c = "";
 557            }
 558
 559            // clean it up
 560            if ( token != null )
 561               token = token.trim();
 562
 563            // if there's something left, store it, otherwise, mark missing
 564            // token with -1 (example of constraint string with missing tokens:
 565            // "1,1,,,,w", width, height, alignment are missing)
 566            if ( token != null && token.length() > 0 )
 567               tokens.addElement( token );
 568            else
 569               tokens.addElement( "-1" );
 570         }
 571
 572         // turn tokens into a Constraints. Default Constraints values are used
 573         // for missing or non-specified values
 574         Constraints q = new Constraints();
 575         Enumeration en = tokens.elements();
 576
 577         // get column
 578         if ( en.hasMoreElements() ) {
 579            token = en.nextElement().toString();
 580            try {
 581               q.x = Integer.parseInt( token );
 582               if ( q.x < 0 )
 583                  q.x = 0;
 584            }
 585            catch ( Exception e ) {
 586               q.x = 0;
 587            }
 588         }
 589
 590         // get row
 591         if ( en.hasMoreElements() ) {
 592            token = en.nextElement().toString();
 593            try {
 594               q.y = Integer.parseInt( token );
 595               if ( q.y < 0 )
 596                  q.y = 0;
 597            }
 598            catch ( Exception e ) {
 599               q.y = 0;
 600            }
 601         }
 602
 603         // get column span (width)
 604         if ( en.hasMoreElements() ) {
 605            token = en.nextElement().toString();
 606            if ( token.equalsIgnoreCase( "R" ) ) {
 607               q.w = R;
 608            }
 609            else {
 610               try {
 611                  q.w = Integer.parseInt( token );
 612                  if ( q.w < 1 )
 613                     q.w = 1;
 614               }
 615               catch ( Exception e ) {
 616                  q.w = 1;
 617               }
 618            }
 619         }
 620
 621         // get row span (height)
 622         if ( en.hasMoreElements() ) {
 623            token = en.nextElement().toString();
 624            if ( token.equalsIgnoreCase( "R" ) ) {
 625               q.h = R;
 626            }
 627            else {
 628               try {
 629                  q.h = Integer.parseInt( token );
 630                  if ( q.h < 1 )
 631                     q.h = 1;
 632               }
 633               catch ( Exception e ) {
 634                  q.h = 1;
 635               }
 636            }
 637         }
 638
 639         // get alignment
 640         if ( en.hasMoreElements() ) {
 641            token = en.nextElement().toString().trim();
 642            if ( token.equalsIgnoreCase( "N" ) || token.equals( "1" ) )
 643               q.a = N;
 644            else if ( token.equalsIgnoreCase( "NE" ) || token.equals( "2" ) )
 645               q.a = NE;
 646            else if ( token.equalsIgnoreCase( "E" ) || token.equals( "3" ) )
 647               q.a = E;
 648            else if ( token.equalsIgnoreCase( "SE" ) || token.equals( "4" ) )
 649               q.a = SE;
 650            else if ( token.equalsIgnoreCase( "S" ) || token.equals( "5" ) )
 651               q.a = S;
 652            else if ( token.equalsIgnoreCase( "SW" ) || token.equals( "6" ) )
 653               q.a = SW;
 654            else if ( token.equalsIgnoreCase( "W" ) || token.equals( "7" ) )
 655               q.a = W;
 656            else if ( token.equalsIgnoreCase( "NW" ) || token.equals( "8" ) )
 657               q.a = NW;
 658            else
 659               q.a = 0;
 660         }
 661
 662         // get component stretch
 663         if ( en.hasMoreElements() ) {
 664            token = en.nextElement().toString().trim().toLowerCase();
 665            if ( token.equals( "w" ) || token.equals( "h" ) || token.equals( "wh" ) || token.equals( "hw" ) ) {
 666               q.s = token;
 667            }
 668            else
 669               q.s = "0";
 670         }
 671
 672         // get component padding
 673         if ( en.hasMoreElements() ) {
 674            token = en.nextElement().toString();
 675            try {
 676               q.p = Integer.parseInt( token );
 677               if ( q.p < 0 )
 678                  q.p = 0;
 679            }
 680            catch ( Exception e ) {
 681               q.p = 0;
 682            }
 683         }
 684
 685         // save the component and its constraints for later use
 686         _constraints.put( comp, q );
 687
 688         // if component is a rigid strut, set the column and/or row size
 689         if ( comp instanceof Strut ) {
 690            Strut strut = ( Strut ) comp;
 691            if ( strut.isRigid() ) {
 692               Dimension d = strut.getSize();
 693               if ( d.width > 0 ) {
 694                  setColumnWidth( q.x, d.width );
 695               }
 696               if ( d.height > 0 ) {
 697                  setRowHeight( q.y, d.height );
 698               }
 699            }
 700         }
 701         _size_unknown = true;
 702      }
 703   }
 704
 705   /**
 706    * Required by LayoutManager2.
 707    * @return <code>preferredLayoutSize(parent)</code>
 708    */
 709   public Dimension maximumLayoutSize( Container c ) {
 710      synchronized ( c.getTreeLock() ) {
 711         return preferredLayoutSize( c );
 712      }
 713   }
 714
 715   /**
 716    * Required by LayoutManager2.
 717    * @return 0.5f
 718    */
 719   public float getLayoutAlignmentX( Container c ) {
 720      return 0.5f;   // default to centered.
 721   }
 722
 723   /**
 724    * Required by LayoutManager2.
 725    * @return 0.5f
 726    */
 727   public float getLayoutAlignmentY( Container c ) {
 728      return 0.5f;   // default to centered.
 729   }
 730
 731   /**
 732    * Required by LayoutManager2.
 733    */
 734   public void invalidateLayout( Container c ) {
 735      /* I would think this is the right thing to do, but doing this causes
 736      the layout to be wrong every time.  Also causes the stretch option to
 737      fail.  
 738      _size_unknown = true;
 739      */
 740   }
 741
 742   /**
 743    * Calculate preferred size and other dimensions.
 744    */
 745   protected void calculateDimensions() {
 746      if ( !_size_unknown )
 747         return ;
 748      _preferred_width = 0;
 749      _preferred_height = 0;
 750      Dimension dim = null;
 751
 752      // count columns and rows
 753      countColumns();
 754      countRows();
 755
 756      // set up table and component maps
 757      if ( _table == null )
 758         _table = new Hashtable( 23, 0.75f );
 759      else
 760         _table.clear();
 761      if ( _components == null )
 762         _components = new Hashtable( 23, 0.75f );
 763      else
 764         _components.clear();
 765
 766      // set up storage for max col width and max row height.  These arrays
 767      // have an entry per column and row and will hold the largest width or
 768      // height of components in each column or row respectively.
 769      int[] temp;
 770      if ( _col_widths != null ) {
 771         // if column count has changed, need to preserve existing column widths
 772         // in case one or more remaining columns has been set to a fixed width.
 773         temp = new int[ _col_widths.length ];
 774         System.arraycopy( _col_widths, 0, temp, 0, _col_widths.length );
 775         _col_widths = new int[ _col_count ];
 776         System.arraycopy( temp, 0, _col_widths, 0, Math.min( temp.length, _col_widths.length ) );
 777      }
 778      else {
 779         _col_widths = new int[ _col_count ];
 780      }
 781      if ( _row_heights != null ) {
 782         // if row count has changed, need to preserve existing row heights
 783         // in case one or more remaining rows has been set to a fixed height.
 784         temp = new int[ _row_heights.length ];
 785         System.arraycopy( _row_heights, 0, temp, 0, _row_heights.length );
 786         _row_heights = new int[ _row_count ];
 787         System.arraycopy( temp, 0, _row_heights, 0, Math.min( temp.length, _row_heights.length ) );
 788      }
 789      else {
 790         _row_heights = new int[ _row_count ];
 791      }
 792
 793      // get the constraints
 794      Enumeration en = _constraints.keys();
 795      while ( en.hasMoreElements() ) {
 796         Component c = ( Component ) en.nextElement();
 797         Constraints q = ( Constraints ) _constraints.get( c );
 798         if ( q.w == R )
 799            q.w = _col_count - q.x;
 800         if ( q.h == R )
 801            q.h = _row_count - q.y;
 802
 803         // store the component in its (x, y) location
 804         _components.put( new Point( q.x, q.y ), c );
 805
 806         // as components can span columns and rows, store the maximum dimension
 807         // of the component that could be in the spanned cells. Note that it
 808         // may happen that none of the component is actually in the cell.
 809         dim = new Dimension(c.getPreferredSize().width, c.getPreferredSize().height);
 810         dim.width += q.p * 2;   // adjust for padding if necessary
 811         dim.height += q.p * 2;
 812         dim.width /= q.w;
 813         dim.height /= q.h;
 814         for ( int i = q.x; i < q.x + q.w; i++ ) {
 815            for ( int j = q.y; j < q.y + q.h; j++ ) {
 816               _table.put( new Point( i, j ), dim );
 817            }
 818         }
 819      }
 820
 821      // calculate preferred width
 822      int col_width = 0;
 823      for ( int i = 0; i < _col_count; i++ ) {
 824         for ( int j = 0; j < _row_count; j++ ) {
 825            Dimension p = ( Dimension ) _table.get( new Point( i, j ) );
 826            if ( p == null )
 827               p = _0dim;
 828            col_width = Math.max( p.width, col_width );
 829         }
 830
 831         // store max width of each column
 832         if ( _col_widths[ i ] >= 0 ) {
 833            _col_widths[ i ] = col_width;
 834            _preferred_width += col_width;
 835         }
 836         else {
 837            _preferred_width += Math.abs( _col_widths[ i ] );
 838         }
 839         col_width = 0;
 840      }
 841
 842      // adjust for same width columns
 843      if ( _same_width_cols != null ) {
 844         en = _same_width_cols.elements();
 845         while ( en.hasMoreElements() ) {
 846            int[] same = ( int[] ) en.nextElement();
 847            // find widest column of this group
 848            int widest = same[ 0 ];
 849            for ( int i = 0; i < same.length; i++ ) {
 850               if ( same[ i ] < _col_widths.length )
 851                  widest = Math.max( widest, _col_widths[ same[ i ] ] );
 852            }
 853            // now set all columns to this widest width
 854            for ( int i = 0; i < same.length; i++ ) {
 855               if ( same[ i ] < _col_widths.length ) {
 856                  _preferred_width += widest - _col_widths[ same[ i ] ];
 857                  _col_widths[ same[ i ] ] = widest;
 858               }
 859            }
 860         }
 861      }
 862
 863      // calculate preferred height
 864      int row_height = 0;
 865      for ( int j = 0; j < _row_count; j++ ) {
 866         for ( int i = 0; i < _col_count; i++ ) {
 867            Dimension p = ( Dimension ) _table.get( new Point( i, j ) );
 868            if ( p == null )
 869               p = _0dim;
 870            row_height = Math.max( p.height, row_height );
 871         }
 872
 873         // store max height of each row
 874         if ( _row_heights[ j ] >= 0 ) {
 875            _row_heights[ j ] = row_height;
 876            _preferred_height += row_height;
 877         }
 878         else {
 879            _preferred_height += Math.abs( _row_heights[ j ] );
 880         }
 881         row_height = 0;
 882      }
 883
 884      // adjust for same height rows
 885      if ( _same_height_rows != null ) {
 886         en = _same_height_rows.elements();
 887         while ( en.hasMoreElements() ) {
 888            int[] same = ( int[] ) en.nextElement();
 889            // find tallest row of this group
 890            int tallest = same[ 0 ];
 891            for ( int i = 0; i < same.length; i++ ) {
 892               if ( same[ i ] < _row_heights.length )
 893                  tallest = Math.max( tallest, _row_heights[ same[ i ] ] );
 894            }
 895            // now set all rows to this tallest height
 896            for ( int i = 0; i < same.length; i++ ) {
 897               if ( same[ i ] < _row_heights.length ) {
 898                  _preferred_height += tallest - _row_heights[ same[ i ] ];
 899                  _row_heights[ same[ i ] ] = tallest;
 900               }
 901            }
 902         }
 903      }
 904      _size_unknown = false;
 905   }
 906
 907   /**
 908    * Calculate number of columns in table.
 909    */
 910   private void countColumns() {
 911      _col_count = 0;
 912      Hashtable rows = new Hashtable();
 913
 914      // get the constraints
 915      Enumeration en = _constraints.elements();
 916      while ( en.hasMoreElements() ) {
 917         Constraints q = ( Constraints ) en.nextElement();
 918
 919         // figure out which columns this component spans. The BitSet represents
 920         // a row, and for each non-empty column in the row, a bit is set. The
 921         // BitSets representing each row are stored in the 'rows' Hashtable.
 922         BitSet row = null;
 923         String y = String.valueOf( q.y );
 924         if ( !rows.containsKey( y ) ) {
 925            row = new BitSet();
 926            rows.put( y, row );
 927         }
 928         row = ( BitSet ) rows.get( y );
 929         int last_col = ( q.w == R ? q.x + 1 : q.x + q.w );
 930         for ( int i = q.x; i < last_col; i++ )
 931            row.set( i );
 932      }
 933
 934      // calculate the number of columns by going through each BitSet and
 935      // counting the number of set bits. The highest bit is the number of
 936      // columns.
 937      en = rows.elements();
 938      while ( en.hasMoreElements() ) {
 939         BitSet row = ( BitSet ) en.nextElement();
 940         for ( int i = 0; i < row.size(); i++ ) {
 941            if ( row.get( i ) )
 942               _col_count = Math.max( _col_count, i + 1 ); // add 1 as column index is 0-based
 943         }
 944      }
 945   }
 946
 947   /**
 948    * Calculate number of rows in table.
 949    */
 950   private void countRows() {
 951      // this is done exactly in the same manner as countColumns, see the comments
 952      // there for details on the counting algorithm
 953      _row_count = 0;
 954      Hashtable cols = new Hashtable();
 955
 956      Enumeration en = _constraints.elements();
 957      while ( en.hasMoreElements() ) {
 958         Constraints q = ( Constraints ) en.nextElement();
 959         BitSet col = null;
 960         String x = String.valueOf( q.x );
 961         if ( !cols.containsKey( x ) ) {
 962            col = new BitSet();
 963            cols.put( x, col );
 964         }
 965         col = ( BitSet ) cols.get( x );
 966         int last_row = ( q.h == R ? q.y + 1 : q.y + q.h );
 967         for ( int i = q.y; i < last_row; i++ ) {
 968            col.set( i );
 969         }
 970      }
 971      en = cols.elements();
 972      while ( en.hasMoreElements() ) {
 973         BitSet col = ( BitSet ) en.nextElement();
 974         for ( int i = 0; i < col.size(); i++ ) {
 975            if ( col.get( i ) ) {
 976               _row_count = Math.max( _row_count, i + 1 );
 977            }
 978         }
 979      }
 980   }
 981
 982   /**
 983    * Makes two columns be the same width.  The actual width will be the larger
 984    * of the preferred widths of these columns.
 985    * @param column1 column number
 986    * @param column2 column number
 987    */
 988   public void makeColumnsSameWidth( int column1, int column2 ) {
 989      makeColumnsSameWidth( new int[] {column1, column2} );
 990   }
 991
 992   /**
 993    * Makes several columns be the same width.  The actual width will be the largest
 994    * preferred width of these columns.
 995    * @param columns array of column numbers to make the same width.
 996    */
 997   public void makeColumnsSameWidth( int[] columns ) {
 998      if ( columns.length <= 1 )
 999         return ;
1000      for ( int i = 0; i < columns.length; i++ ) {
1001         if ( columns[ i ] < 0 )
1002            throw new IllegalArgumentException( "Column parameter must be greater than 0." );
1003      }
1004      if ( _same_width_cols == null )
1005         _same_width_cols = new Vector();
1006      _same_width_cols.addElement( columns );
1007      _size_unknown = true;
1008   }
1009
1010   /**
1011    * Makes all columns be the same width.  The actual width will be the largest
1012    * preferred width of these columns.
1013    */
1014   public void makeColumnsSameWidth() {
1015      countColumns();
1016      int[] columns = new int[ _col_count ];
1017      for ( int i = 0; i < _col_count; i++ ) {
1018         columns[ i ] = i;
1019      }
1020      makeColumnsSameWidth( columns );
1021   }
1022
1023   /**
1024    * Makes two rows be the same height.  The actual height will be the larger
1025    * of the preferred heights of these rows.
1026    * @param row1 row number
1027    * @param row2 row number
1028    */
1029   public void makeRowsSameHeight( int row1, int row2 ) {
1030      makeRowsSameHeight( new int[] {row1, row2} );
1031   }
1032
1033   /**
1034    * Makes several rows be the same height.  The actual height will be the largest
1035    * preferred height of these rows.
1036    * @param rows  array of row numbers to make the same height.
1037    */
1038   public void makeRowsSameHeight( int[] rows ) {
1039      if ( rows.length <= 1 )
1040         return ;
1041      for ( int i = 0; i < rows.length; i++ ) {
1042         if ( rows[ i ] < 0 )
1043            throw new IllegalArgumentException( "Row parameter must be greater than 0." );
1044      }
1045      if ( _same_height_rows == null )  // laziness pays off now
1046         _same_height_rows = new Vector();
1047      _same_height_rows.addElement( rows );
1048      _size_unknown = true;
1049   }
1050
1051   /**
1052    * Makes all rows be the same height.  The actual height will be the largest
1053    * preferred height of these rows.
1054    */
1055   public void makeRowsSameHeight() {
1056      countRows();
1057      int[] rows = new int[ _row_count ];
1058      for ( int i = 0; i < rows.length; i++ ) {
1059         rows[ i ] = i;
1060      }
1061      makeRowsSameHeight( rows );
1062   }
1063
1064   /**
1065    * Sets a column to a specific width.  Use care with this method, components
1066    * wider than the set width will be truncated.
1067    * @param column column number
1068    * @param width width in pixels
1069    */
1070   public void setColumnWidth( int column, int width ) {
1071      if ( column < 0 )
1072         throw new IllegalArgumentException( "Column must be >= 0." );
1073      if ( _col_widths == null )
1074         _col_widths = new int[ column + 1 ];
1075      if ( _col_widths.length <= column ) {
1076         int[] tmp = new int[ _col_widths.length ];
1077         System.arraycopy( _col_widths, 0, tmp, 0, _col_widths.length );
1078         _col_widths = new int[ column + 1 ];
1079         System.arraycopy( tmp, 0, _col_widths, 0, tmp.length );
1080      }
1081      // store fixed width columns as a negative number
1082      _col_widths[ column ] = -1 * width;
1083      _size_unknown = true;
1084   }
1085
1086   /**
1087    * Sets a row to a specific height.  Use care with this method, components
1088    * taller than the set height will be truncated.
1089    * @param row row number
1090    * @param height height in pixels
1091    */
1092   public void setRowHeight( int row, int height ) {
1093      if ( row < 0 )
1094         throw new IllegalArgumentException( "Row must be >= 0." );
1095      if ( _row_heights == null )
1096         _row_heights = new int[ row + 1 ];
1097      if ( _row_heights.length <= row ) {
1098         int[] tmp = new int[ _row_heights.length ];
1099         System.arraycopy( _row_heights, 0, tmp, 0, _row_heights.length );
1100         _row_heights = new int[ row + 1 ];
1101         System.arraycopy( tmp, 0, _row_heights, 0, tmp.length );
1102      }
1103      // store fixed height rows as a negative number
1104      _row_heights[ row ] = -1 * height;
1105      _size_unknown = true;
1106   }
1107
1108   /**
1109    * Creates a Constraints for direct manipulation.
1110    * @return a Constraints object for direct manipulation.
1111    */
1112   public static Constraints createConstraint() {
1113      return new Constraints();
1114   }
1115
1116   /**
1117    * Useful for holding an otherwise empty column to a minimum width.
1118    * @param width desired width of component
1119    * @return a component with some width but no height
1120    */
1121   public static Component createHorizontalStrut( int width ) {
1122      return new Strut( width, 0 );
1123   }
1124
1125   /**
1126    * Useful for holding a column to a fixed width.
1127    * @param width desired width of component
1128    * @param rigid if true, will not stretch
1129    * @return a component with some width but no height
1130    */
1131   public static Component createHorizontalStrut( int width, boolean rigid ) {
1132      return new Strut( width, 0, rigid );
1133   }
1134
1135   /**
1136    * Useful for holding an otherwise empty row to a minimum height.
1137    * @param height desired height of component
1138    * @return a component with some height but no width
1139    */
1140   public static Component createVerticalStrut( int height ) {
1141      return new Strut( 0, height );
1142   }
1143
1144   /**
1145    * Useful for holding a  row to a fixed height.
1146    * @param height desired height of component
1147    * @param rigid if true, will not stretch
1148    * @return a component with some height but no width
1149    */
1150   public static Component createVerticalStrut( int height, boolean rigid ) {
1151      return new Strut( 0, height, rigid );
1152   }
1153
1154   /**
1155    * Useful for setting an otherwise blank cell to a minimum width and height.
1156    * @param width desired width of component
1157    * @param height desired height of component
1158    * @return a component with some height and width
1159    */
1160   public static Component createStrut( int width, int height ) {
1161      return new Strut( width, height );
1162   }
1163
1164   /**
1165    * Useful for setting a row and column to a fixed width and height.
1166    * @param width desired width of component
1167    * @param height desired height of component
1168    * @return a component with some height and width
1169    */
1170   public static Component createStrut( int width, int height, boolean rigid ) {
1171      return new Strut( width, height, rigid );
1172   }
1173
1174   /**
1175    * Simple component that is invisible. Struts can be either rigid or non-
1176    * rigid. While this component could be used with other layout managers,
1177    * it special properties are intended for use with KappaLayout. In particular,
1178    * when the strut is set to rigid, it will lock the column or row (depends on
1179    * the orientation of the strut) to the width or height of the strut. A non-
1180    * rigid strut sets a minimum width or height on a column or row, but does
1181    * not set a maximum like a rigid strut does.
1182    */
1183   public static class Strut extends Component implements Serializable {
1184      private Dimension _dim;
1185      private boolean _rigid;
1186
1187      /**
1188       * @param w width
1189       * @param h height
1190       */
1191      public Strut( int w, int h ) {
1192         this( w, h, false );
1193      }
1194
1195      /**
1196       * @param w width
1197       * @param h height
1198       * @param rigid rigid
1199       */
1200      public Strut( int w, int h, boolean rigid ) {
1201         _dim = new Dimension( w, h );
1202         _rigid = rigid;
1203      }
1204
1205      /**
1206       * Overrides <code>getPreferredSize</code> from Component.
1207       */
1208      public Dimension getPreferredSize() {
1209         return _dim;
1210      }
1211
1212      /**
1213       * Overrides <code>getSize</code> from Component.
1214       */
1215      public Dimension getSize() {
1216         return _dim;
1217      }
1218
1219      /**
1220       * @return true if this strut is rigid.
1221       */
1222      public boolean isRigid() {
1223         return _rigid;
1224      }
1225
1226      /**
1227       * @param rigid if true, this strut will act as a rigid strut
1228       */
1229      public void setRigid( boolean rigid ) {
1230         _rigid = rigid;
1231      }
1232   }
1233
1234
1235   /**
1236    * This class is cloneable so that users may create and reuse a Constraints object
1237    * similar to how one would use a GridBagConstraints rather than the string parameters.
1238    */
1239   public static class Constraints extends Object implements Cloneable, Serializable {
1240      /**
1241       * start column
1242       */
1243      public int x = 0;
1244
1245      /**
1246       * start row
1247       */
1248      public int y = 0;
1249
1250      /**
1251       * # columns wide
1252       */
1253      public int w = 1;
1254
1255      /**
1256       * # rows high
1257       */
1258      public int h = 1;
1259
1260      /**
1261       * alignment within cell, see comments in KappaLayout
1262       */
1263      public int a = 0;
1264
1265      /**
1266       * stretch: default is 0 (character zero, no stretch), w = width of cell, h = height of cell, wh = both width and height
1267       */
1268      public String s = "0";
1269
1270      /**
1271       * padding, same amount of blank space on all four sides of component
1272       */
1273      public int p = 0;
1274
1275      /**
1276       * Plain String representation of this Constraints, suitable for using as a
1277       * constraints string if needed.
1278       */
1279      public String toString() {
1280         StringBuffer sb = new StringBuffer();
1281         sb.append( String.valueOf( x ) + "," )
1282         .append( String.valueOf( y ) + "," )
1283         .append( String.valueOf( w ) + "," );
1284         if ( w == R )
1285            sb.append( "R," );
1286         else
1287            sb.append( String.valueOf( w ) + "," );
1288         if ( h == R )
1289            sb.append( "R," );
1290         else
1291            sb.append( String.valueOf( h ) + "," );
1292         sb.append( String.valueOf( a ) + "," )
1293         .append( String.valueOf( s ) + "," )
1294         .append( String.valueOf( p ) )
1295         .toString();
1296         return sb.toString();
1297      }
1298
1299
1300      /**
1301       * @return a clone of this object.
1302       */
1303      public Object clone() {
1304         try {
1305            return super.clone();
1306         }
1307         catch ( Exception e ) {
1308            return null;
1309         }
1310      }
1311   }
1312
1313   /*
1314   public Component getDebugPanel() {
1315      java.awt.Panel p = new java.awt.Panel() {
1316         public Dimension getPreferredSize() {
1317            int width = 0;
1318            for ( int i = 0; i < _col_widths.length; i++ ) {
1319               width += Math.abs(_col_widths[i]);
1320            }
1321
1322            int height = 0;
1323            for ( int i = 0; i < _row_heights.length; i++ ) {
1324               height += Math.abs(_row_heights[i]);
1325            }
1326
1327            return new Dimension(width, height);
1328         }
1329
1330         public void paint(Graphics g) {
1331            g.setColor(Color.black);
1332            Dimension d = getPreferredSize();
1333
1334            int x = 0;
1335            g.drawLine(x, 0, x, d.height);
1336            for ( int i = 0; i < _col_widths.length; i++ ) {
1337               x = Math.abs(_col_widths[i]);
1338               g.drawLine(x, 0, x, d.height);
1339            }
1340            g.drawLine(d.width, 0, d.width, d.height);
1341
1342            int y = 0;
1343            g.drawLine(0, y, d.width, y);
1344            for ( int i = 0; i < _row_heights.length; i++ ) {
1345               y = Math.abs(_row_heights[i]);
1346               g.drawLine(0, y, d.width, y);
1347            }
1348            g.drawLine(0, d.height, d.width, d.height);
1349         }
1350      };
1351      return p;
1352}
1353   */
1354}
1355
1356