00001 package edu.stanford.hci.r3.util.layout;
00002
00003 import java.awt.*;
00004 import java.util.*;
00005
00037 public class RiverLayout extends FlowLayout implements LayoutManager, java.io.Serializable {
00038
00039 public static final String CENTER_ALIGN = "center";
00040
00041 public static final String HFILL = "hfill";
00042
00043 public static final String LEFT_ALIGN = "left";
00044
00045 public static final String LINE_BREAK = "br";
00046
00047 public static final String PARAGRAPH_BREAK = "p";
00048
00049 public static final String RIGHT_ALIGN = "right";
00050
00051 public static final String TAB_STOP = "tab";
00052
00053 public static final String VCENTER = "vcenter";
00054
00055 public static final String VFILL = "vfill";
00056
00057 public static final String VTOP = "vtop";
00058
00059 final Map<Component, String> constraints = new HashMap<Component, String>();
00060
00061 Insets extraInsets;
00062
00063 int hgap;
00064
00065 final Insets totalInsets = new Insets(0, 0, 0, 0);
00066
00067 String valign = VCENTER;
00068
00069 int vgap;
00070
00071 public RiverLayout() {
00072 this(10, 5);
00073 }
00074
00075 public RiverLayout(int hgap, int vgap) {
00076 this.hgap = hgap;
00077 this.vgap = vgap;
00078 setExtraInsets(new Insets(0, hgap, hgap, hgap));
00079 }
00080
00087 public void addLayoutComponent(String name, Component comp) {
00088 constraints.put(comp, name);
00089 }
00090
00091 protected void adjustAlignment(Component m) {
00092 if (hasConstraint(m, RiverLayout.LEFT_ALIGN))
00093 setAlignment(FlowLayout.LEFT);
00094 else if (hasConstraint(m, RiverLayout.RIGHT_ALIGN))
00095 setAlignment(FlowLayout.RIGHT);
00096 else if (hasConstraint(m, RiverLayout.CENTER_ALIGN))
00097 setAlignment(FlowLayout.CENTER);
00098 if (hasConstraint(m, RiverLayout.VTOP))
00099 valign = VTOP;
00100 else if (hasConstraint(m, RiverLayout.VCENTER))
00101 valign = VCENTER;
00102
00103 }
00104
00108 protected Ruler calcTabs(Container target) {
00109 Ruler ruler = new Ruler();
00110 int nmembers = target.getComponentCount();
00111
00112 int x = 0;
00113 int tabIndex = 0;
00114 for (int i = 0; i < nmembers; i++) {
00115 Component m = target.getComponent(i);
00116
00117 if (isFirstInRow(m) || i == 0) {
00118 x = 0;
00119 tabIndex = 0;
00120 } else
00121 x += hgap;
00122 if (hasConstraint(m, TAB_STOP)) {
00123 ruler.setTab(tabIndex, x);
00124 x = ruler.getTab(tabIndex++);
00125 }
00126 Dimension d = m.getPreferredSize();
00127 x += d.width;
00128 }
00129
00130 return ruler;
00131 }
00132
00133 public Insets getExtraInsets() {
00134 return extraInsets;
00135 }
00136
00140 public int getHgap() {
00141 return hgap;
00142 }
00143
00144 protected Insets getInsets(Container target) {
00145 Insets insets = target.getInsets();
00146 totalInsets.top = insets.top + extraInsets.top;
00147 totalInsets.left = insets.left + extraInsets.left;
00148 totalInsets.bottom = insets.bottom + extraInsets.bottom;
00149 totalInsets.right = insets.right + extraInsets.right;
00150 return totalInsets;
00151 }
00152
00156 public int getVgap() {
00157 return vgap;
00158 }
00159
00160 boolean hasConstraint(Component comp, String test) {
00161 String cons = (String) constraints.get(comp);
00162 if (cons == null)
00163 return false;
00164 StringTokenizer tokens = new StringTokenizer(cons);
00165 while (tokens.hasMoreTokens())
00166 if (tokens.nextToken().equals(test))
00167 return true;
00168 return false;
00169 }
00170
00171 boolean hasHfill(Component comp) {
00172 return hasConstraint(comp, RiverLayout.HFILL);
00173 }
00174
00175 boolean hasVfill(Component comp) {
00176 return hasConstraint(comp, RiverLayout.VFILL);
00177 }
00178
00179 boolean isFirstInRow(Component comp) {
00180 String cons = (String) constraints.get(comp);
00181 return cons != null
00182 && (cons.indexOf(RiverLayout.LINE_BREAK) != -1 || cons
00183 .indexOf(RiverLayout.PARAGRAPH_BREAK) != -1);
00184 }
00185
00196 public void layoutContainer(Container target) {
00197 setAlignment(FlowLayout.LEFT);
00198 synchronized (target.getTreeLock()) {
00199 Insets insets = getInsets(target);
00200 int maxwidth = target.getWidth() - (insets.left + insets.right);
00201 int maxheight = target.getHeight() - (insets.top + insets.bottom);
00202
00203 int nmembers = target.getComponentCount();
00204 int x = 0, y = insets.top + vgap;
00205 int rowh = 0, start = 0, moveDownStart = 0;
00206
00207 boolean ltr = target.getComponentOrientation().isLeftToRight();
00208 Component toHfill = null;
00209 Component toVfill = null;
00210 Ruler ruler = calcTabs(target);
00211 int tabIndex = 0;
00212
00213 for (int i = 0; i < nmembers; i++) {
00214 Component m = target.getComponent(i);
00215
00216 Dimension d = m.getPreferredSize();
00217 m.setSize(d.width, d.height);
00218
00219 if (isFirstInRow(m))
00220 tabIndex = 0;
00221 if (hasConstraint(m, TAB_STOP))
00222 x = ruler.getTab(tabIndex++);
00223 if (!isFirstInRow(m)) {
00224 if (i > 0 && !hasConstraint(m, TAB_STOP)) {
00225 x += hgap;
00226 }
00227 x += d.width;
00228 rowh = Math.max(rowh, d.height);
00229 } else {
00230 if (toVfill != null && moveDownStart == 0) {
00231 moveDownStart = i;
00232 }
00233 if (toHfill != null) {
00234 toHfill.setSize(toHfill.getWidth() + maxwidth - x, toHfill.getHeight());
00235 x = maxwidth;
00236 }
00237 moveComponents(target, insets.left, y, maxwidth - x, rowh, start, i, ltr, ruler);
00238 x = d.width;
00239 y += vgap + rowh;
00240 if (hasConstraint(m, PARAGRAPH_BREAK))
00241 y += 2 * vgap;
00242 rowh = d.height;
00243 start = i;
00244 toHfill = null;
00245 }
00246
00247 if (hasHfill(m)) {
00248 toHfill = m;
00249 }
00250 if (hasVfill(m)) {
00251 toVfill = m;
00252 }
00253 adjustAlignment(m);
00254 }
00255
00256 if (toVfill != null && moveDownStart == 0) {
00257
00258 moveDownStart = nmembers;
00259 }
00260 if (toHfill != null) {
00261 toHfill.setSize(toHfill.getWidth() + maxwidth - x, toHfill.getHeight());
00262 x = maxwidth;
00263 }
00264 moveComponents(target, insets.left, y, maxwidth - x, rowh, start, nmembers, ltr, ruler);
00265 int yslack = maxheight - (y + rowh);
00266 if (yslack != 0 && toVfill != null) {
00267 toVfill.setSize(toVfill.getWidth(), yslack + toVfill.getHeight());
00268 relMove(target, 0, yslack, moveDownStart, nmembers);
00269 }
00270 }
00271 }
00272
00284 public Dimension minimumLayoutSize(Container target) {
00285 synchronized (target.getTreeLock()) {
00286 Dimension dim = new Dimension(0, 0);
00287 Dimension rowDim = new Dimension(0, 0);
00288 int nmembers = target.getComponentCount();
00289 boolean firstVisibleComponent = true;
00290 int tabIndex = 0;
00291 Ruler ruler = calcTabs(target);
00292
00293 for (int i = 0; i < nmembers; i++) {
00294 Component m = target.getComponent(i);
00295
00296 if (isFirstInRow(m)) {
00297 tabIndex = 0;
00298 dim.width = Math.max(dim.width, rowDim.width);
00299 dim.height += rowDim.height + vgap;
00300 if (hasConstraint(m, PARAGRAPH_BREAK))
00301 dim.height += 2 * vgap;
00302 rowDim = new Dimension(0, 0);
00303 }
00304 if (hasConstraint(m, TAB_STOP))
00305 rowDim.width = ruler.getTab(tabIndex++);
00306 Dimension d = m.getMinimumSize();
00307 rowDim.height = Math.max(rowDim.height, d.height);
00308 if (firstVisibleComponent) {
00309 firstVisibleComponent = false;
00310 } else {
00311 rowDim.width += hgap;
00312 }
00313 rowDim.width += d.width;
00314
00315 }
00316 dim.width = Math.max(dim.width, rowDim.width);
00317 dim.height += rowDim.height;
00318
00319 Insets insets = getInsets(target);
00320 dim.width += insets.left + insets.right;
00321 dim.height += insets.top + insets.bottom;
00322 return dim;
00323 }
00324 }
00325
00344 protected void moveComponents(Container target, int x, int y, int width, int height,
00345 int rowStart, int rowEnd, boolean ltr, Ruler ruler) {
00346 synchronized (target.getTreeLock()) {
00347 switch (getAlignment()) {
00348 case FlowLayout.LEFT:
00349 x += ltr ? 0 : width;
00350 break;
00351 case FlowLayout.CENTER:
00352 x += width / 2;
00353 break;
00354 case FlowLayout.RIGHT:
00355 x += ltr ? width : 0;
00356 break;
00357 case LEADING:
00358 break;
00359 case TRAILING:
00360 x += width;
00361 break;
00362 }
00363 int tabIndex = 0;
00364 for (int i = rowStart; i < rowEnd; i++) {
00365 Component m = target.getComponent(i);
00366
00367 if (hasConstraint(m, TAB_STOP))
00368 x = getInsets(target).left + ruler.getTab(tabIndex++);
00369 int dy = (valign == VTOP) ? 0 : (height - m.getHeight()) / 2;
00370 if (ltr) {
00371 m.setLocation(x, y + dy);
00372 } else {
00373 m.setLocation(target.getWidth() - x - m.getWidth(), y + dy);
00374 }
00375 x += m.getWidth() + hgap;
00376
00377 }
00378 }
00379 }
00380
00392 public Dimension preferredLayoutSize(Container target) {
00393 synchronized (target.getTreeLock()) {
00394 Dimension dim = new Dimension(0, 0);
00395 Dimension rowDim = new Dimension(0, 0);
00396 int nmembers = target.getComponentCount();
00397 boolean firstVisibleComponent = true;
00398 int tabIndex = 0;
00399 Ruler ruler = calcTabs(target);
00400
00401 for (int i = 0; i < nmembers; i++) {
00402 Component m = target.getComponent(i);
00403
00404 if (isFirstInRow(m)) {
00405 tabIndex = 0;
00406 dim.width = Math.max(dim.width, rowDim.width);
00407 dim.height += rowDim.height + vgap;
00408 if (hasConstraint(m, PARAGRAPH_BREAK))
00409 dim.height += 2 * vgap;
00410 rowDim = new Dimension(0, 0);
00411 }
00412 if (hasConstraint(m, TAB_STOP))
00413 rowDim.width = ruler.getTab(tabIndex++);
00414 Dimension d = m.getPreferredSize();
00415 rowDim.height = Math.max(rowDim.height, d.height);
00416 if (firstVisibleComponent) {
00417 firstVisibleComponent = false;
00418 } else {
00419 rowDim.width += hgap;
00420 }
00421 rowDim.width += d.width;
00422
00423 }
00424 dim.width = Math.max(dim.width, rowDim.width);
00425 dim.height += rowDim.height;
00426
00427 Insets insets = getInsets(target);
00428 dim.width += insets.left + insets.right;
00429 dim.height += insets.top + insets.bottom;
00430 return dim;
00431 }
00432 }
00433
00434 protected void relMove(Container target, int dx, int dy, int rowStart, int rowEnd) {
00435 synchronized (target.getTreeLock()) {
00436 for (int i = rowStart; i < rowEnd; i++) {
00437 Component m = target.getComponent(i);
00438
00439 m.setLocation(m.getX() + dx, m.getY() + dy);
00440
00441 }
00442
00443 }
00444 }
00445
00453 public void removeLayoutComponent(Component comp) {
00454 constraints.remove(comp);
00455 }
00456
00457 public void setExtraInsets(Insets newExtraInsets) {
00458 extraInsets = newExtraInsets;
00459 }
00460
00464 public void setHgap(int hgap) {
00465 this.hgap = hgap;
00466 }
00467
00471 public void setVgap(int vgap) {
00472 this.vgap = vgap;
00473 }
00474
00475 }
00476
00477 class Ruler {
00478 public static void main(String[] args) {
00479 Ruler r = new Ruler();
00480 r.setTab(0, 10);
00481 r.setTab(1, 20);
00482 r.setTab(2, 30);
00483 System.out.println(r);
00484 r.setTab(1, 25);
00485 System.out.println(r);
00486 System.out.println(r.getTab(0));
00487 }
00488
00489 private final Vector<Integer> tabs = new Vector<Integer>();
00490
00491 public int getTab(int num) {
00492 return ((Integer) tabs.get(num)).intValue();
00493 }
00494
00495 public void setTab(int num, int xpos) {
00496 if (num >= tabs.size())
00497 tabs.add(num, new Integer(xpos));
00498 else {
00499
00500 int delta = xpos - getTab(num);
00501 if (delta > 0) {
00502 for (int i = num; i < tabs.size(); i++) {
00503 tabs.set(i, new Integer(getTab(i) + delta));
00504 }
00505 }
00506 }
00507 }
00508
00509 public String toString() {
00510 StringBuffer ret = new StringBuffer(getClass().getName() + " {");
00511 for (int i = 0; i < tabs.size(); i++) {
00512 ret.append(tabs.get(i));
00513 if (i < tabs.size() - 1)
00514 ret.append(',');
00515 }
00516 ret.append('}');
00517 return ret.toString();
00518 }
00519 }