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