001 package util.swing; 002 003 import java.awt.Component; 004 import java.awt.Container; 005 import java.awt.Dimension; 006 import java.awt.Insets; 007 import java.awt.LayoutManager; 008 009 import javax.swing.BorderFactory; 010 import javax.swing.JComponent; 011 import javax.swing.JLabel; 012 import javax.swing.JPanel; 013 014 /** 015 * A Swing Panel realising a standard option panel layout. That means, option 016 * titles are displayed right in front of their corresponding components. All 017 * alignment is done automatically. 018 * 019 * @author Thomas Ryssel 020 * @since v3.3 021 */ 022 public class JOptionPanel extends JPanel { 023 024 /** 025 * ID for Serialization. 026 */ 027 private static final long serialVersionUID = -7494273537708600891L; 028 029 /** 030 * The title text (will be shown using TitledBorder). 031 */ 032 private String m_sText = null; 033 034 /** 035 * Whether or not initialization is finished. This is mainly used to 036 * guard the LayoutManager once it is set. 037 */ 038 private boolean m_fInitDone = false; 039 040 /** 041 * A LayoutManager for positioning components in the way wished for. 042 * This could also be done using several components and - for example - 043 * BoxLayout. But there are some disadvantages concerning exact 044 * positioning. Also here we can correctly handle "filling cases", e.g. 045 * components using all the space that is possibly left (both horizontally 046 * and vertically). Alignment (e.g. putting all stuff in the center) can 047 * be done quite easily that way, too. 048 * 049 * @author Thomas Ryssel 050 */ 051 private class OptionLayout implements LayoutManager { 052 053 /** 054 * The horizontal gap (between titles and components). 055 */ 056 private int m_nHgap; 057 058 /** 059 * The vertical gap. 060 */ 061 private int n_nVgap = 0; 062 063 /** 064 * The horizontal alignment. 065 */ 066 private int m_nHorizontalAlign; 067 068 /** 069 * The vertical alignment. 070 */ 071 private int m_nVerticalAlign; 072 073 /** 074 * Create a new OptionLayout. 075 * 076 * @param hgap Horizontal gap. 077 * @param vgap Vertical gap. 078 * @param horiz Horizontal alignment. 079 * @param vert Vertical alignment. 080 */ 081 public OptionLayout(int hgap, int vgap, int horiz, int vert) { 082 this.n_nVgap = vgap; 083 this.m_nHgap = hgap; 084 this.m_nHorizontalAlign = horiz; 085 this.m_nVerticalAlign = vert; 086 } 087 088 /** 089 * Add a special layout component. Not used in this implementation. 090 */ 091 public void addLayoutComponent(String name, Component comp) { 092 } 093 094 /** 095 * Remove a special layout component. Not used in this implementation. 096 */ 097 public void removeLayoutComponent(Component comp) { 098 } 099 100 /** 101 * Get the preferred Dimension of the layout. 102 */ 103 public Dimension preferredLayoutSize(Container parent) { 104 synchronized (parent.getTreeLock()) { 105 Insets ins = parent.getInsets(); 106 int n = parent.getComponentCount() / 2; 107 int wl = 0, wr = 0, w; 108 int h = 0; 109 int count = parent.getComponentCount(); 110 for (int i = 0; i < count; i += 2) { 111 int lcw = parent.getComponent(i).getPreferredSize().width; 112 if (lcw > wl) { 113 wl = lcw; 114 } 115 } 116 for (int i = 0; i < count; i += 2) { 117 int rcw = parent.getComponent(i + 1).getPreferredSize().width; 118 if (rcw == Integer.MAX_VALUE && parent.getSize().width > 0) { 119 rcw = parent.getSize().width - wl - m_nHgap; 120 } 121 if (rcw > wr) { 122 wr = rcw; 123 } 124 125 if (parent.getComponent(i + 1).getPreferredSize().height == Integer.MAX_VALUE) { 126 h += parent.getSize().height; 127 } else { 128 h += parent.getComponent(i + 1).getPreferredSize().height; 129 } 130 } 131 132 h += (n - 1) * n_nVgap + ins.top + ins.bottom; 133 w = wl + wr + m_nHgap + ins.left + ins.right; 134 135 return new Dimension(w, h); 136 } 137 } 138 139 /** 140 * Get the minimum Dimension of the layout. 141 */ 142 public Dimension minimumLayoutSize(Container parent) { 143 return preferredLayoutSize(parent); 144 } 145 146 /** 147 * Do the actual layout work. 148 */ 149 public void layoutContainer(Container parent) { 150 synchronized (parent.getTreeLock()) { 151 Insets ins = parent.getInsets(); 152 int topoffset = 0, leftoffset = 0; 153 Dimension pref = preferredLayoutSize(parent); 154 155 156 switch (m_nHorizontalAlign) { 157 case LEFT: 158 leftoffset = 0; 159 break; 160 case CENTER: 161 if (parent.getSize().width > pref.width) { 162 leftoffset = (parent.getSize().width - pref.width - ins.left - ins.right) / 2; 163 } 164 break; 165 case RIGHT: 166 if (parent.getSize().width > pref.width) { 167 leftoffset = parent.getSize().width - pref.width - ins.right - m_nHgap; 168 } 169 break; 170 } 171 switch (m_nVerticalAlign) { 172 case TOP: 173 topoffset = 0; 174 break; 175 case CENTER: 176 if (parent.getSize().height > pref.height) { 177 topoffset = (parent.getSize().height - pref.height - ins.top - ins.bottom) / 2; 178 } 179 break; 180 case BOTTOM: 181 if (parent.getSize().height > pref.height) { 182 topoffset = parent.getSize().height - pref.height - ins.bottom; 183 } 184 break; 185 } 186 187 int leftsize = 0; 188 int top = ins.top + topoffset; 189 int heightleft = parent.getSize().height - ins.top - ins.bottom; 190 for (int i = 0; i < parent.getComponentCount(); i += 2) { 191 Component comp = parent.getComponent(i); 192 if (comp.getPreferredSize().width > leftsize) { 193 leftsize = comp.getPreferredSize().width; 194 } 195 } 196 int rightsize = parent.getSize().width - leftsize - ins.left - 197 ins.right - m_nHgap; 198 for (int i = 0; i < parent.getComponentCount(); i += 2) { 199 Component compleft = parent.getComponent(i); 200 Component compright = parent.getComponent(i + 1); 201 int height = compright.getPreferredSize().height; 202 if (height == Integer.MAX_VALUE) { 203 height = heightleft; 204 heightleft = 0; 205 } else { 206 heightleft -= height + n_nVgap; 207 } 208 compleft.setBounds(ins.left, top, leftsize + leftoffset, compleft.getPreferredSize().height); 209 210 if (compright.getPreferredSize().width == Integer.MAX_VALUE) { 211 compright.setBounds(m_nHgap + ins.left + leftsize + leftoffset, top, rightsize, height); 212 } else { 213 compright.setBounds(m_nHgap + ins.left + leftsize + leftoffset, top, compright.getPreferredSize().width, height); 214 } 215 top += height + n_nVgap; 216 if (heightleft == 0) { 217 break; 218 } 219 } 220 221 222 } 223 } 224 } 225 226 227 /** 228 * Create a new JOptionPanel without title and horizontal and vertical alignment 229 * set to LEFT and TOP, respectively. Horizontal gap will be 16 (pixels) and 230 * vertical gap will be set to 4 (pixels). 231 */ 232 public JOptionPanel() { 233 this(null); 234 } 235 236 /** 237 * Create a new JOptionPanel with horizontal and vertical alignment set to 238 * LEFT and TOP, respectively. Horizontal gap will be 16 (pixels) and vertical 239 * gap will be set to 4 (pixels). 240 * 241 * @param text The title text of the panel. Can be set to <code>null</code> 242 * to indicate that there is no title. 243 */ 244 public JOptionPanel(String text) { 245 this(text, 16, 4); 246 } 247 248 /** 249 * Create a new JOptionPanel with horizontal and vertical alignment set to 250 * LEFT and TOP, respectively. 251 * 252 * @param text The title text of the panel. Can be set to <code>null</code> 253 * to indicate that there is no title. 254 * @param hgap Horizontal gap (between titles and components). 255 * @param vgap Vertical gap. 256 */ 257 public JOptionPanel(String text, int hgap, int vgap) { 258 this(text, hgap, vgap, LEFT, TOP); 259 } 260 261 /** 262 * Create a new JOptionPanel. 263 * 264 * @param text The title text of the panel. Can be set to <code>null</code> 265 * to indicate that there is no title. 266 * @param hgap Horizontal gap (between titles and components). 267 * @param vgap Vertical gap. 268 * @param horizAlign Horizontal aligment. Can be either {@link #LEFT}, {@link #RIGHT} 269 * or {@link #CENTER}. 270 * @param vertAlign Vertical alignment. Can be either {@link #TOP}, {@link #BOTTOM} 271 * or {@link #CENTER}. 272 */ 273 public JOptionPanel(String text, int hgap, int vgap, int horizAlign, int vertAlign) { 274 setText(text); 275 setLayout(new OptionLayout(hgap, vgap, horizAlign, vertAlign)); 276 m_fInitDone = true; 277 } 278 279 /** 280 * Set the option panel's title text. 281 * 282 * @param text The title text of the panel. Can be set to <code>null</code> 283 * to indicate that there is no title. 284 */ 285 public void setText(String text) { 286 this.m_sText = text; 287 if (text != null) { 288 setBorder(BorderFactory.createTitledBorder(this.m_sText)); 289 } else { 290 setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1)); 291 } 292 } 293 294 /** 295 * Get the option panel's title text. 296 * 297 * @return The title text of the panel. 298 */ 299 public String getText() { 300 return m_sText; 301 } 302 303 /** 304 * Add the given component under the given title. The layout will be done 305 * according to the preferred dimensions of the components. 306 * 307 * <p>If the component's preferred width is set to <code>Integer.MAX_VALUE</code>, 308 * it will take up all possibly available horizontal space. If the component's 309 * preferred height is set to <code>Integer.MAX_VALUE</code>, it will take up 310 * all vertical space that is left. Note that there cannot be any other components 311 * underneath such a component. 312 * 313 * <p>Components are always shown in the order they have been added (from top 314 * to bottom).</p> 315 * 316 * @param title The option's title. If it is no empty string, a colon (":") 317 * will be added automatically. 318 * @param component The component representing the option in however way. 319 */ 320 public void addOption(String title, JComponent component) { 321 JLabel label = new JLabel("", JLabel.RIGHT); 322 if (!title.equals("")) { 323 label.setText(" " + title + ":"); 324 } 325 add(label); 326 add(component); 327 } 328 329 /** 330 * Add a "display only" option. the <code>text</code>'s <code>toString()</code> 331 * method will be used to construct a <code>JLabel</code> that will be displayed. 332 * That is merely a convenience method that allows you to simply display information. 333 * 334 * <p>For example, you could do something like: <code>addOption("File", "test.txt");</code>. 335 * 336 * @param title The option's title. If it is no empty string, a colon (":") 337 * will be added automatically. 338 * @param text The object representing the text to show. 339 */ 340 public void addOption(String title, Object text) { 341 addOption(title, new JLabel(text.toString())); 342 } 343 344 345 /** 346 * Get the n-th option component (in the order they were added). 347 * 348 * @param n Number of the component to retrieve. 349 * 350 * @return The component requested. 351 */ 352 public Component getOptionComponent(int n) { 353 return getComponent(n * 2 + 1); 354 } 355 356 /** 357 * Set layout manager. Overridden so that it cannot be used anymore 358 * after initialization. 359 */ 360 public void setLayout(LayoutManager mgr) { 361 if (!m_fInitDone) { 362 super.setLayout(mgr); 363 return; 364 } 365 throw new UnsupportedOperationException("This does not allow to change the" + 366 " layout manager. Choose JPanel instead"); 367 } 368 369 /** 370 * Constant representing center alignment. 371 */ 372 public static final int CENTER = 0; 373 374 /** 375 * Constant representing left alignment. 376 */ 377 public static final int LEFT = 1; 378 379 /** 380 * Constant representing right alignment. 381 */ 382 public static final int RIGHT = 2; 383 384 /** 385 * Constant representing top alignment. 386 */ 387 public static final int TOP = 3; 388 389 /** 390 * Constant representing bottom alignment. 391 */ 392 public static final int BOTTOM = 4; 393 }