Coverage Report - org.argouml.uml.ui.LabelledLayout
 
Classes in this File Line Coverage Branch Coverage Complexity
LabelledLayout
60%
100/165
56%
35/62
2.52
Seperator
100%
3/3
N/A
2.52
 
 1  
 /* $Id: LabelledLayout.java 19034 2011-02-13 18:12:44Z bobtarling $
 2  
  *****************************************************************************
 3  
  * Copyright (c) 2009 Contributors - see below
 4  
  * All rights reserved. This program and the accompanying materials
 5  
  * are made available under the terms of the Eclipse Public License v1.0
 6  
  * which accompanies this distribution, and is available at
 7  
  * http://www.eclipse.org/legal/epl-v10.html
 8  
  *
 9  
  * Contributors:
 10  
  *    bobtarling
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 2008-2009 The Regents of the University of California. All
 17  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 18  
 // software and its documentation without fee, and without a written
 19  
 // agreement is hereby granted, provided that the above copyright notice
 20  
 // and this paragraph appear in all copies. This software program and
 21  
 // documentation are copyrighted by The Regents of the University of
 22  
 // California. The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Regents. The Regents
 24  
 // does not warrant that the operation of the program will be
 25  
 // uninterrupted or error-free. The end-user understands that the program
 26  
 // was developed for research purposes and is advised not to rely
 27  
 // exclusively on the program for any reason. IN NO EVENT SHALL THE
 28  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 29  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 30  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 31  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 33  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 34  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 35  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 36  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.uml.ui;
 40  
 
 41  
 import java.awt.Component;
 42  
 import java.awt.Container;
 43  
 import java.awt.Dimension;
 44  
 import java.awt.Insets;
 45  
 import java.awt.LayoutManager;
 46  
 import java.util.ArrayList;
 47  
 import javax.swing.JComboBox;
 48  
 import javax.swing.JLabel;
 49  
 import javax.swing.JPanel;
 50  
 import javax.swing.JToolBar;
 51  
 import javax.swing.UIManager;
 52  
 
 53  
 /**
 54  
  * This layout manager lines up components in 2 columns. All JLabels
 55  
  * are the first column and any component the JLabel is registered
 56  
  * with is in a second column next to the label. <p>
 57  
  *
 58  
  * Components are sized automatically to fill available space in the container
 59  
  * when it is resized. <p>
 60  
  * 
 61  
  * All JLabel widths will be the largest of the JLabel preferred widths (unless
 62  
  * the container is too narrow). <p>
 63  
  * 
 64  
  * The components will take up any left over width unless they are
 65  
  * restricted themselves by a maximum width. <p>
 66  
  * 
 67  
  * The height of each component is either fixed or will resize to use up any
 68  
  * available space in the container. Whether a components height is resizable
 69  
  * is determined by checking whether the preferred height of that component is
 70  
  * greater then its minimum height. This is the case for components such as
 71  
  * JList which would require to expand to show the maximum number or items. <p>
 72  
  * 
 73  
  * If a component is not to have its height resized then its preferred
 74  
  * height and minimum height should be the same. This is the case for
 75  
  * components such as JTextField and JComboBox* which should always stay the
 76  
  * same height. <p>
 77  
  * 
 78  
  * [There is known bug in JRE5 where the prefered height and minimum height of
 79  
  * a JComboBox can differ. LabelledLayout has coded a workaround for this bug.
 80  
  * See - http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6255154 ] <p>
 81  
  * 
 82  
  * LabelledLayout can show multiple panels of label/component
 83  
  * pairs. The seperation of these panels is indicated by adding a
 84  
  * Seperator component to the container. Labelled layout starts
 85  
  * a new panel when detecting this Seperator. <p>
 86  
  * 
 87  
  * When there are multiple panels, each panel is given equal width.
 88  
  * The width restriction of JLabels and components described above are then
 89  
  * dependent on panel width rather than container width.
 90  
  *
 91  
  * @author Bob Tarling
 92  
  */
 93  
 public class LabelledLayout implements LayoutManager, java.io.Serializable {
 94  
 
 95  
     private static final long serialVersionUID = -5596655602155151443L;
 96  
 
 97  
     /**
 98  
      * This is the horizontal gap (in pixels) which specifies the space
 99  
      * between sections.  They can be changed at any time.
 100  
      * This should be a non negative integer.
 101  
      *
 102  
      * @serial
 103  
      * @see #getHgap()
 104  
      * @see #setHgap(int)
 105  
      */
 106  
     private int hgap;
 107  
     
 108  
     /**
 109  
      * This is the vertical gap (in pixels) which specifies the space
 110  
      * between rows.  They can be changed at any time.
 111  
      * This should be a non negative integer.
 112  
      *
 113  
      * @serial
 114  
      * @see #getVgap()
 115  
      * @see #setVgap(int)
 116  
      */
 117  
     private int vgap;
 118  
 
 119  
     private boolean ignoreSplitters;
 120  
 
 121  
     /**
 122  
      * Construct a new LabelledLayout.
 123  
      */
 124  4089
     public LabelledLayout() {
 125  4089
         ignoreSplitters = false;
 126  4089
         hgap = 0;
 127  4089
         vgap = 0;
 128  4089
     }
 129  
 
 130  
     /**
 131  
      * Construct a new LabelledLayout.
 132  
      */
 133  0
     public LabelledLayout(boolean ignoreSplitters) {
 134  0
         this.ignoreSplitters = ignoreSplitters;
 135  0
         this.hgap = 0;
 136  0
         this.vgap = 0;
 137  0
     }
 138  
 
 139  
     /**
 140  
      * Construct a new horizontal LabelledLayout with the specified
 141  
      * cell spacing.
 142  
      * @param hgap The horizontal gap between components
 143  
      * @param vgap The vertical gap between components
 144  
      */
 145  0
     public LabelledLayout(int hgap, int vgap) {
 146  0
         this.ignoreSplitters = false;
 147  0
         this.hgap = hgap;
 148  0
         this.vgap = vgap;
 149  0
     }
 150  
 
 151  
     /** 
 152  
      * Adds the specified component with the specified name to the
 153  
      * layout. This is included to satisfy the LayoutManager interface
 154  
      * but is not actually used in this layout implementation.
 155  
      *
 156  
      * @param name the name of the component
 157  
      * @param comp the component to be added
 158  
      */
 159  
     public void addLayoutComponent(String name, Component comp) {
 160  0
     }
 161  
 
 162  
     /** 
 163  
      * Removes the specified component from
 164  
      * the layout. This is included to satisfy the LayoutManager
 165  
      * interface but is not actually used in this layout
 166  
      * implementation.
 167  
      *
 168  
      * @param comp the component
 169  
      */
 170  
     public void removeLayoutComponent(Component comp) {
 171  0
     }
 172  
 
 173  
     /**
 174  
      * Determines the preferred size of the container argument using
 175  
      * this labelled layout.  The preferred size is that all child
 176  
      * components are in one section at their own preferred size with
 177  
      * gaps and border indents.
 178  
      */
 179  
     public Dimension preferredLayoutSize(Container parent) {
 180  0
         synchronized (parent.getTreeLock()) {
 181  0
             final Insets insets = parent.getInsets();
 182  0
             int preferredWidth = 0;
 183  0
             int preferredHeight = 0;
 184  0
             int widestLabel = 0;
 185  
 
 186  0
             final int componentCount = parent.getComponentCount();
 187  0
             for (int i = 0; i < componentCount; ++i) {
 188  0
                 Component childComp = parent.getComponent(i);
 189  0
                 if (childComp.isVisible()
 190  
                         && !(childComp instanceof Seperator)) {
 191  0
                     int childHeight = getPreferredHeight(childComp);
 192  0
                     if (childComp instanceof JLabel) {
 193  0
                         final JLabel jlabel = (JLabel) childComp;
 194  0
                         widestLabel = 
 195  
                             Math.max(widestLabel, getPreferredWidth(jlabel));
 196  0
                         childComp = jlabel.getLabelFor();
 197  0
                         final int childWidth = getPreferredWidth(childComp);
 198  0
                         preferredWidth = 
 199  
                             Math.max(preferredWidth, childWidth);
 200  
 
 201  0
                         childHeight =
 202  
                             Math.min(childHeight, getPreferredHeight(jlabel));
 203  
                     }
 204  0
                     preferredHeight += childHeight + this.vgap;
 205  
                 }
 206  
             }
 207  0
             preferredWidth += insets.left + widestLabel + insets.right;
 208  0
             preferredHeight += insets.top + insets.bottom;
 209  0
             return new Dimension(
 210  
                     insets.left + widestLabel + preferredWidth + insets.right,
 211  
                     preferredHeight);
 212  0
         }
 213  
     }
 214  
     
 215  
     /** 
 216  
      * Required by LayoutManager.
 217  
      * 
 218  
      * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
 219  
      */
 220  
     public Dimension minimumLayoutSize(Container parent) {
 221  0
         synchronized (parent.getTreeLock()) {
 222  0
             final Insets insets = parent.getInsets();
 223  0
             int minimumHeight = insets.top + insets.bottom;
 224  
 
 225  0
             final int componentCount = parent.getComponentCount();
 226  0
             for (int i = 0; i < componentCount; ++i) {
 227  0
                 Component childComp = parent.getComponent(i);
 228  0
                 if (childComp instanceof JLabel) {
 229  0
                     final JLabel jlabel = (JLabel) childComp;
 230  0
                     childComp = jlabel.getLabelFor();
 231  
                     
 232  0
                     final int childHeight = Math.max(
 233  
                             getMinimumHeight(childComp),
 234  
                             getMinimumHeight(jlabel));
 235  0
                     minimumHeight += childHeight + this.vgap;
 236  
                 }
 237  
             }
 238  0
             return new Dimension(0, minimumHeight);
 239  0
         }
 240  
     }
 241  
 
 242  
     /**
 243  
      * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
 244  
      */
 245  
     public void layoutContainer(Container parent) {
 246  3804
         synchronized (parent.getTreeLock()) {
 247  3804
             int sectionX = parent.getInsets().left;
 248  
 
 249  3804
             final ArrayList<Component> components = new ArrayList<Component>();
 250  3804
             final int sectionCount = getSectionCount(parent);
 251  3804
             final int sectionWidth = getSectionWidth(parent, sectionCount);
 252  3804
             int sectionNo = 0;
 253  50028
             for (int i = 0; i < parent.getComponentCount(); ++i) {
 254  46224
                 final Component childComp = parent.getComponent(i);
 255  46224
                 if (childComp instanceof Seperator) {
 256  1800
                     if (!this.ignoreSplitters) {
 257  1800
                         layoutSection(
 258  
                                 parent, 
 259  
                                 sectionX, 
 260  
                                 sectionWidth, 
 261  
                                 components, 
 262  
                                 sectionNo++);
 263  1800
                         sectionX += sectionWidth + this.hgap;
 264  1800
                         components.clear();
 265  
                     }
 266  
                 } else {
 267  44424
                     components.add(parent.getComponent(i));
 268  
                 }
 269  
             }
 270  3804
             layoutSection(
 271  
                     parent, 
 272  
                     sectionX, 
 273  
                     sectionWidth, 
 274  
                     components, 
 275  
                     sectionNo);
 276  3804
         }
 277  3804
     }
 278  
 
 279  
     /** Determine the number of sections.  There is only ever one
 280  
      * section if oriented vertically.  If oriented horizontally the
 281  
      * number of sections is deduced from the number of Splitters in
 282  
      * the parent container.
 283  
      */
 284  
     private int getSectionCount(Container parent) {
 285  3804
         int sectionCount = 1;
 286  3804
         final int componentCount = parent.getComponentCount();
 287  3804
         if (!ignoreSplitters) {
 288  50028
             for (int i = 0; i < componentCount; ++i) {
 289  46224
                 if (parent.getComponent(i) instanceof Seperator) {
 290  1800
                     ++sectionCount;
 291  
                 }
 292  
             }
 293  
         }
 294  3804
         return sectionCount;
 295  
     }
 296  
     
 297  
     /**
 298  
      * Determine the width of each section from the section count.
 299  
      * This is the working width minus the gaps between sections. This
 300  
      * result is then divided equally by the section count.
 301  
      */
 302  
     private int getSectionWidth(Container parent, int sectionCount) {
 303  3804
         return (getUsableWidth(parent) - (sectionCount - 1) * this.hgap)
 304  
                 / sectionCount;
 305  
     }
 306  
 
 307  
     /**
 308  
      * Determine the usable width of the parent.
 309  
      * This is the full width minus any borders.
 310  
      */
 311  
     private int getUsableWidth(Container parent) {
 312  3804
         final Insets insets = parent.getInsets();
 313  3804
         return parent.getWidth() - (insets.left + insets.right);
 314  
     }
 315  
     
 316  
     /**
 317  
      * Layout a single section
 318  
      */
 319  
     private void layoutSection(
 320  
             final Container parent,
 321  
             final int sectionX,
 322  
             final int sectionWidth,
 323  
             final ArrayList components,
 324  
             final int sectionNo) {
 325  5604
         final ArrayList<Integer> rowHeights = new ArrayList<Integer>();
 326  
 
 327  5604
         final int componentCount = components.size();
 328  5604
         if (componentCount == 0) {
 329  0
             return;
 330  
         }
 331  
 
 332  5604
         int labelWidth = 0;
 333  5604
         int unknownHeightCount = 0;
 334  5604
         int totalHeight = 0;
 335  
 
 336  
         // Build up an array list of the heights of each label/component pair.
 337  
         // Heights of zero indicate a proportional height.
 338  5604
         Component previousComp = null;
 339  27816
         for (int i = 0; i < componentCount; ++i) {
 340  22212
             final Component childComp = (Component) components.get(i);
 341  
             final int childHeight;
 342  22212
             if (childComp instanceof JLabel) {
 343  22212
                 final JLabel jlabel = (JLabel) childComp;
 344  22212
                 final Component labelledComp = jlabel.getLabelFor();
 345  
                 
 346  22212
                 labelWidth = Math.max(labelWidth, getPreferredWidth(jlabel));
 347  
                 
 348  22212
                 if (labelledComp != null) {
 349  22212
                     ++i;
 350  22212
                     childHeight = getChildHeight(labelledComp);
 351  22212
                     if (childHeight == 0) {
 352  7689
                         ++unknownHeightCount;
 353  
                     }
 354  
                 } else {
 355  0
                     childHeight = getPreferredHeight(jlabel);
 356  
                 }
 357  
                 
 358  22212
                 totalHeight += childHeight + this.vgap;
 359  22212
                 rowHeights.add(new Integer(childHeight));
 360  22212
             } else {
 361  
                 // to manage the case there are no label/component
 362  
                 // pairs but just one component
 363  0
                 childHeight = getChildHeight(childComp);
 364  0
                 if (childHeight == 0) {
 365  0
                     ++unknownHeightCount;
 366  
                 }
 367  
                 
 368  0
                 totalHeight += childHeight + this.vgap;
 369  0
                 rowHeights.add(new Integer(childHeight));
 370  
             }
 371  
             
 372  22212
             previousComp = childComp;
 373  
         }
 374  5604
         totalHeight -= this.vgap;
 375  
         
 376  5604
         final Insets insets = parent.getInsets();
 377  5604
         final int parentHeight = 
 378  
             parent.getHeight() - (insets.top + insets.bottom);
 379  
         // Set the child components to the heights in the array list
 380  
         // calculating the height of any proportional component on the
 381  
         // fly.  FIXME - This assumes that the JLabel and the
 382  
         // component it labels have been added to the parent component
 383  
         // consecutively.
 384  5604
         int y = insets.top;
 385  5604
         int row = 0;
 386  5604
         previousComp = null;
 387  27816
         for (int i = 0; i < componentCount; ++i) {
 388  22212
             Component childComp = (Component) components.get(i);
 389  22212
             if (childComp.isVisible()) {
 390  
                 int rowHeight;
 391  22212
                 int componentWidth = sectionWidth;
 392  22212
                 int componentX = sectionX;
 393  
                 // If the component is a JLabel which has another
 394  
                 // component assigned then position/size the label and
 395  
                 // calculate the size of the registered component
 396  22212
                 if (childComp instanceof JLabel
 397  
                         && ((JLabel) childComp).getLabelFor() != null) {
 398  22212
                     i++; // Assumes the next child is the labelled component
 399  22212
                     final JLabel jlabel = (JLabel) childComp;
 400  22212
                     childComp = jlabel.getLabelFor();
 401  22212
                     jlabel.setBounds(sectionX, y, labelWidth,
 402  
                                      getPreferredHeight(jlabel));
 403  22212
                     componentWidth = sectionWidth - (labelWidth);
 404  22212
                     componentX = sectionX + labelWidth;
 405  
                 }
 406  22212
                 rowHeight = rowHeights.get(row).intValue();
 407  22212
                 if (rowHeight == 0) {
 408  
                     try {
 409  7689
                         rowHeight = calculateHeight(
 410  
                                 parentHeight, 
 411  
                                 totalHeight, 
 412  
                                 unknownHeightCount--, 
 413  
                                 childComp);
 414  0
                     } catch (ArithmeticException e) {
 415  0
                         String lookAndFeel = 
 416  
                             UIManager.getLookAndFeel().getClass().getName();
 417  0
                         throw new IllegalStateException(
 418  
                                 "Division by zero laying out "
 419  
                                 + childComp.getClass().getName()
 420  
                                 + " on " + parent.getClass().getName()
 421  
                                 + " in section " + sectionNo
 422  
                                 + " using "
 423  
                                 + lookAndFeel,
 424  
                                 e);
 425  7689
                     }
 426  7689
                     totalHeight += rowHeight;
 427  
                 }
 428  
                 // Make sure the component width isn't any greater
 429  
                 // than its maximum allowed width
 430  22212
                 if (childComp.getMaximumSize() != null
 431  
                         && getMaximumWidth(childComp) < componentWidth) {
 432  1800
                     componentWidth = getMaximumWidth(childComp);
 433  
                 }
 434  22212
                 childComp.setBounds(componentX, y, componentWidth, rowHeight);
 435  22212
                 y += rowHeight + this.vgap;
 436  22212
                 ++row;
 437  22212
                 previousComp = childComp;
 438  
             }
 439  
         }
 440  5604
     }
 441  
     
 442  
     /**
 443  
      * @param childComp a component
 444  
      * @return 0 for a resizable component or a positive value for its fixed
 445  
      * height
 446  
      */
 447  
     private int getChildHeight(Component childComp) {
 448  22212
         if (isResizable(childComp)) {
 449  
             // If the child component is resizable then
 450  
             // we don't know it's actual size yet.
 451  
             // It will be calculated later as a
 452  
             // proportion of the available left over
 453  
             // space.  For now this is flagged as zero.
 454  7200
             return 0;
 455  
         } else {
 456  
             // If a preferred height is not given or is
 457  
             // the same as the minimum height then fix the
 458  
             // height of this row.
 459  15012
             return getMinimumHeight(childComp);
 460  
         }
 461  
     }
 462  
 
 463  
     /**
 464  
      * A component is resizable if its minimum size is less than
 465  
      * its preferred size.
 466  
      * There is a workaround here for a bug introduced in JRE5
 467  
      * where JComboBox minimum and preferred size now differ.
 468  
      * JComboBox is not resizable.
 469  
      * Anything in a JScrollPane is considered resizable
 470  
      * @param comp the component to check for resizability.
 471  
      * @return true if the given component should be resized to take u[p empty
 472  
      * space.
 473  
      */
 474  
     private boolean isResizable(Component comp) {
 475  22212
         if (comp == null) {
 476  0
             return false;
 477  
         }
 478  22212
         if (comp instanceof JComboBox) {
 479  0
             return false;
 480  
         }
 481  22212
         if (comp.getPreferredSize() == null) {
 482  0
             return false;
 483  
         }
 484  22212
         if (comp.getMinimumSize() == null) {
 485  0
             return false;
 486  
         }
 487  22212
         return (getMinimumHeight(comp) < getPreferredHeight(comp));
 488  
     }
 489  
 
 490  
     private final int calculateHeight(
 491  
             final int parentHeight, 
 492  
             final int totalHeight,
 493  
             final int unknownHeightsLeft, 
 494  
             final Component childComp) {
 495  7689
         return Math.max(
 496  
                 (parentHeight - totalHeight) / unknownHeightsLeft,
 497  
                 getMinimumHeight(childComp));
 498  
     }
 499  
     
 500  
     private int getPreferredHeight(final Component comp) {
 501  44424
         return (int) comp.getPreferredSize().getHeight();
 502  
     }
 503  
     
 504  
     private int getPreferredWidth(final Component comp) {
 505  22212
         return (int) comp.getPreferredSize().getWidth();
 506  
     }
 507  
 
 508  
     private int getMinimumHeight(final Component comp) {
 509  44913
         return (int) comp.getMinimumSize().getHeight();
 510  
     }
 511  
     
 512  
     private int getMaximumWidth(final Component comp) {
 513  24012
         return (int) comp.getMaximumSize().getWidth();
 514  
     }
 515  
     
 516  
     /**
 517  
      * Create a new instance of the Separator that splits the layout in columns
 518  
      * @return the separator
 519  
      */
 520  
     public static Seperator getSeparator() {
 521  900
         return new Seperator();
 522  
     }
 523  
 
 524  
     /**
 525  
      * @return the horizontal gaps between components
 526  
      */
 527  
     public int getHgap() {
 528  0
         return this.hgap;
 529  
     }
 530  
 
 531  
     /**
 532  
      * Set the horizontal gaps between components
 533  
      * @param hgap the horizontal gap
 534  
      */
 535  
     public void setHgap(int hgap) {
 536  4089
         this.hgap = hgap;
 537  4089
     }
 538  
 
 539  
     /**
 540  
      * @return the vertical gaps between components
 541  
      */
 542  
     public int getVgap() {
 543  0
         return this.vgap;
 544  
     }
 545  
 
 546  
     /**
 547  
      * Set the vertical gaps between components
 548  
      * @param vgap the horizontal gap
 549  
      */
 550  
     public void setVgap(int vgap) {
 551  0
         this.vgap = vgap;
 552  0
     }
 553  
 }
 554  
 
 555  
 class Seperator extends JPanel {
 556  
     
 557  
     private static final long serialVersionUID = -4143634500959911688L;
 558  
 
 559  900
     Seperator() {
 560  900
         super.setVisible(false);
 561  900
     }
 562  
 }