StackedLayout.java

00001 package edu.stanford.hci.r3.util.layout;
00002 
00003 /*****************************************************************************
00004  Simple layout manager for filling a panel horizontally or vertically.
00005 
00006  bruce.miller@nist.gov
00007  Contribution of the National Institute of Standards and Technology,
00008  not subject to copyright.
00009  *****************************************************************************/
00010 import java.awt.*;
00011 import java.util.*;
00012 
00013 /***************************************************************************************************
00014  * StackLayout is a LayoutManager that arranges components in a vertical (or horizontal) strip
00015  * aligning them at right, left or centered, and/or filling them to take up any extra vertical or
00016  * horizontal space. Arrangement tags are provided by using the add(tag,component) form to add
00017  * components to the container.
00018  * 
00019  * The tag consists of one or more of the following, with the two forms applying to horizontal or
00020  * vertical dimension.
00021  * 
00022  * <pre>
00023  *    == Positioning ==
00024  *    Center              : centered horizontally &amp; vertically (the default)
00025  *    Left    or Top      : pushed at the left|top edge.
00026  *    Right   or Bottom   : pushed against the right|top edge
00027  *    
00028  *    == Sizing ==
00029  *    Wide    or Tall     : filled to use available space.
00030  *    Wide*#  or Tall*#   : filled but weighted by the number #.
00031  *    Fill (or Fill*#)    : filled in both directions.
00032  *    Width=# or Height=# : given explicit width|height
00033  *    
00034  *    == Margins ==
00035  *    Flush               : margins are not added around this component.
00036  * </pre>
00037  * 
00038  * By default, a component is centered in both directions. The available space along the orientation
00039  * is divided between the filled components. A common idiom is to build a complicated panel out of,
00040  * say, a vertical stack of horizontal stacks (both using StackLayout). In that case, it would
00041  * usually be good to add the horizontal panels using the tag "Wide Flush", so that spacing comes
00042  * out evenly.
00043  * <p>
00044  * 
00045  * Much of what can be done with GridBagLayout can be achieved by combining a set of subpanels using
00046  * StackLayout, but typically more concisely. On the other hand, with StackLayout there is less
00047  * compile time checking of the layout.
00048  * 
00049  * @author Bruce R. Miller (bruce.miller@nist.gov)
00050  * @author Contribution of the National Institute of Standards and Technology,
00051  * @author not subject to copyright.
00052  */
00053 
00054 public class StackedLayout implements LayoutManager {
00055         static final int ABS = 0x08;
00056 
00057         static final int BACK = 0x02;
00058 
00059         /* Layout codes */
00060         static final int CENTER = 0x00;
00061 
00062         static final int FILL = 0x04;
00063 
00064         static final int FLUSH = 0x10;
00065 
00066         static final int FRONT = 0x01;
00067 
00069         public static final int HORIZONTAL = 0;
00070 
00071         static final int POSMASK = 0x03;
00072 
00073         static final int SIZEMASK = 0x0C;
00074 
00076         public static final int VERTICAL = 1;
00077 
00078         final Map<Component, int[]> codeTable = new Hashtable<Component, int[]>();
00079 
00080         int defaultCode[] = { CENTER, CENTER, 0, 0 };
00081 
00082         int margin = 2;
00083 
00084         int orientation = HORIZONTAL;
00085 
00086         /***********************************************************************************************
00087          * Creators.
00088          */
00089 
00091         public StackedLayout() {
00092         }
00093 
00095         public StackedLayout(int orientation) {
00096                 this.orientation = orientation;
00097         }
00098 
00102         public StackedLayout(int orientation, int margin) {
00103                 this.orientation = orientation;
00104                 this.margin = margin;
00105         }
00106 
00107         /***********************************************************************************************
00108          * Note any named components .
00109          */
00110 
00113         public void addLayoutComponent(String tag, Component comp) {
00114                 tag = tag.toUpperCase().trim();
00115                 int hcode = CENTER, vcode = CENTER, harg = 0, varg = 0;
00116                 int i, l = tag.length(), n;
00117                 for (i = 0; i < l;) {
00118                         if (tag.startsWith("CENTER", i)) {
00119                                 i += 6;
00120                         } else if (tag.startsWith("LEFT", i)) {
00121                                 i += 4;
00122                                 hcode |= FRONT;
00123                         } else if (tag.startsWith("TOP", i)) {
00124                                 i += 3;
00125                                 vcode |= FRONT;
00126                         } else if (tag.startsWith("RIGHT", i)) {
00127                                 i += 5;
00128                                 hcode |= BACK;
00129                         } else if (tag.startsWith("BOTTOM", i)) {
00130                                 i += 6;
00131                                 vcode |= BACK;
00132                         } else if (tag.startsWith("WIDE", i)) {
00133                                 i += 4;
00134                                 hcode |= FILL;
00135                                 if (tag.startsWith("*", i)) {
00136                                         i++;
00137                                         n = countDigits(tag, i);
00138                                         harg = parseArg(tag, i, n);
00139                                         i += n;
00140                                 } else
00141                                         harg = 1;
00142                         } else if (tag.startsWith("TALL", i)) {
00143                                 i += 4;
00144                                 vcode |= FILL;
00145                                 if (tag.startsWith("*", i)) {
00146                                         i++;
00147                                         n = countDigits(tag, i);
00148                                         varg = parseArg(tag, i, n);
00149                                         i += n;
00150                                 } else
00151                                         varg = 1;
00152                         } else if (tag.startsWith("FILL", i)) {
00153                                 i += 4;
00154                                 hcode |= FILL;
00155                                 vcode |= FILL;
00156                                 if (tag.startsWith("*", i)) {
00157                                         i++;
00158                                         n = countDigits(tag, i);
00159                                         harg = varg = parseArg(tag, i, n);
00160                                         i += n;
00161                                 } else
00162                                         harg = varg = 1;
00163                         } else if (tag.startsWith("WIDTH", i)) {
00164                                 i += 5;
00165                                 hcode |= ABS;
00166                                 if (tag.startsWith("=", i)) {
00167                                         i++;
00168                                         n = countDigits(tag, i);
00169                                         harg = parseArg(tag, i, n);
00170                                         i += n;
00171                                 } else {
00172                                         harg = -1;
00173                                         break;
00174                                 }
00175                         } else if (tag.startsWith("HEIGHT", i)) {
00176                                 i += 6;
00177                                 vcode |= ABS;
00178                                 if (tag.startsWith("=", i)) {
00179                                         i++;
00180                                         n = countDigits(tag, i);
00181                                         varg = parseArg(tag, i, n);
00182                                         i += n;
00183                                 } else {
00184                                         varg = -1;
00185                                         break;
00186                                 }
00187                         } else if (tag.startsWith("FLUSH", i)) {
00188                                 i += 5;
00189                                 hcode |= FLUSH;
00190                                 vcode |= FLUSH;
00191                         } else {
00192                                 harg = -1;
00193                                 break;
00194                         }
00195                         for (; (i < l) && Character.isWhitespace(tag.charAt(i)); i++)
00196                                 ; // skip whitesp.
00197                 }
00198                 if ((harg == -1) || (varg == -1))
00199                         System.out.println("StackLayout: can't understand \"" + tag + "\"");
00200                 else {
00201                         int codes[] = { hcode, vcode, harg, varg };
00202                         codeTable.put(comp, codes);
00203                 }
00204         }
00205 
00206         Dimension computeLayoutSize(Container parent, boolean preferred) {
00207                 Insets in = parent.getInsets();
00208                 int inW = in.left + in.right, inH = in.top + in.bottom;
00209                 int n = parent.getComponentCount();
00210                 if (orientation == HORIZONTAL) {
00211                         int maxH = 0, totW = 0, m;
00212                         for (int i = 0; i < n; i++) {
00213                                 Component comp = parent.getComponent(i);
00214                                 if (comp.isVisible()) {
00215                                         int code = getCode(comp)[orientation];
00216                                         m = ((code & FLUSH) == 0 ? margin : 0);
00217                                         Dimension d = (preferred && ((code & SIZEMASK) == FILL) ? comp
00218                                                         .getPreferredSize() : comp.getMinimumSize());
00219                                         maxH = Math.max(maxH, d.height + 2 * m);
00220                                         totW += d.width + 2 * m;
00221                                 }
00222                         }
00223                         return new Dimension(totW + inW, maxH + inH);
00224                 } else {
00225                         int maxW = 0, totH = 0, m;
00226                         for (int i = 0; i < n; i++) {
00227                                 Component comp = parent.getComponent(i);
00228                                 if (comp.isVisible()) {
00229                                         int code = getCode(comp)[orientation];
00230                                         m = ((code & FLUSH) == 0 ? margin : 0);
00231                                         Dimension d = (preferred && ((code & SIZEMASK) == FILL) ? comp
00232                                                         .getPreferredSize() : comp.getMinimumSize());
00233                                         maxW = Math.max(maxW, d.width + 2 * m);
00234                                         totH += d.height + 2 * m;
00235                                 }
00236                         }
00237                         return new Dimension(maxW + inW, totH + inH);
00238                 }
00239         }
00240 
00241         int countDigits(String tag, int i) {
00242                 int j, l = tag.length();
00243                 for (j = i; (j < l) && Character.isDigit(tag.charAt(j)); j++)
00244                         ;
00245                 return j - i;
00246         }
00247 
00248         int[] getCode(Component comp) {
00249                 int code[] = (int[]) codeTable.get(comp);
00250                 return (code == null ? defaultCode : code);
00251         }
00252 
00254         public void layoutContainer(Container parent) {
00255                 int along = orientation, across = (orientation + 1) % 2;
00256                 int n = parent.getComponentCount();
00257                 Insets in = parent.getInsets();
00258                 Dimension sz = parent.getSize();
00259                 int W = sz.width - in.left - in.right, H = sz.height - in.top - in.bottom;
00260                 int L = (orientation == HORIZONTAL ? W : H), // total running Length
00261                 D = (orientation == HORIZONTAL ? H : W); // sideways Depth.
00262 
00263                 // First pass: find visible components, record min. sizes,
00264                 // find out how much leftover space there is.
00265                 int nFills = 0, nRubber = 0;
00266                 int sum = 0, prev;
00267                 prev = FRONT;
00268                 int codes[][] = new int[n][];
00269                 int sizes[][] = new int[n][2];
00270                 for (int i = 0; i < n; i++) { // determine # of fills & remaining space.
00271                         Component comp = parent.getComponent(i);
00272                         if (comp.isVisible()) {
00273                                 Dimension d = comp.getMinimumSize();
00274                                 int code[] = getCode(comp);
00275                                 int size[] = sizes[i];
00276                                 codes[i] = code;
00277                                 size[0] = d.width;
00278                                 size[1] = d.height;
00279                                 int l = size[along], c = code[along];
00280                                 switch (c & SIZEMASK) {
00281                                 case FILL:
00282                                         nFills += code[along + 2];
00283                                         break;
00284                                 case ABS:
00285                                         sum += code[along + 2];
00286                                         break;
00287                                 default:
00288                                         sum += l;
00289                                         break;
00290                                 }
00291                                 switch (c & POSMASK) {
00292                                 case CENTER:
00293                                         nRubber++;
00294                                         break;
00295                                 case BACK:
00296                                         if (prev != BACK)
00297                                                 nRubber++;
00298                                         break;
00299                                 }
00300                                 if ((c & FLUSH) == 0)
00301                                         sum += 2 * margin;
00302                                 prev = (c & POSMASK);
00303                         }
00304                 }
00305                 if (prev == CENTER)
00306                         nRubber++;
00307                 // Divide up the leftover space among filled components (if any)
00308                 // else as filler between centered or justified components.
00309                 int rubber = ((nFills != 0) || (nRubber == 0) ? 0 : Math.max(0, (L - sum) / nRubber)), fill = (nFills == 0 ? 0
00310                                 : Math.max(0, (L - sum) / nFills));
00311 
00312                 // Second pass: layout the components.
00313                 int r = (orientation == HORIZONTAL ? in.left : in.top), // running pos.
00314                 s0 = (orientation == HORIZONTAL ? in.top : in.left), // side pos.
00315                 s, l, d, m;
00316                 prev = FRONT;
00317                 for (int i = 0; i < n; i++) {
00318                         int code[] = codes[i];
00319                         int size[] = sizes[i];
00320                         if (code != null) {
00321                                 int c = code[along], ca = code[across];
00322                                 m = ((c & FLUSH) == 0 ? margin : 0);
00323                                 r += m;
00324                                 s = s0 + m;
00325                                 l = size[along];
00326                                 d = size[across];
00327                                 switch (c & SIZEMASK) {
00328                                 case FILL:
00329                                         if (fill > 0)
00330                                                 l = fill * code[along + 2];
00331                                         break;
00332                                 case ABS:
00333                                         l = code[along + 2];
00334                                         break;
00335                                 }
00336                                 switch (c & POSMASK) {
00337                                 case CENTER:
00338                                         r += rubber;
00339                                         break;
00340                                 case BACK:
00341                                         if (prev != BACK)
00342                                                 r += rubber;
00343                                         break;
00344                                 }
00345                                 prev = (c & POSMASK);
00346                                 switch (ca & SIZEMASK) {
00347                                 case FILL:
00348                                         d = D - 2 * m;
00349                                         break;
00350                                 case ABS:
00351                                         d = code[across + 2];
00352                                         break;
00353                                 }
00354                                 switch (ca & POSMASK) {
00355                                 case BACK:
00356                                         s += D - d;
00357                                         break;
00358                                 case CENTER:
00359                                         s += (D - d) / 2;
00360                                         break;
00361                                 }
00362                                 Component comp = parent.getComponent(i);
00363                                 if (orientation == HORIZONTAL)
00364                                         comp.setBounds(r, s, l, d);
00365                                 else
00366                                         comp.setBounds(s, r, d, l);
00367                                 r += l + m;
00368                         }
00369                 }
00370         }
00371 
00373         public Dimension minimumLayoutSize(Container parent) {
00374                 return computeLayoutSize(parent, false);
00375         }
00376 
00377         int parseArg(String tag, int i, int n) {
00378                 int num = -1;
00379                 try {
00380                         num = Integer.parseInt(tag.substring(i, i + n));
00381                 } catch (Exception e) {
00382                 }
00383                 return num;
00384         }
00385 
00387         public Dimension preferredLayoutSize(Container parent) {
00388                 return computeLayoutSize(parent, true);
00389         }
00390 
00392         public void removeLayoutComponent(Component comp) {
00393                 codeTable.remove(comp);
00394         }
00395 
00396         boolean stretches(Component comp) {
00397                 int c[] = getCode(comp);
00398                 return c[orientation] == FILL;
00399         }
00400 
00401 }

Generated on Sat Apr 14 18:21:38 2007 for R3 Paper Toolkit by  doxygen 1.4.7