001/* 002 * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved. 003 * 004 * Redistribution and use in source and binary forms, with or without 005 * modification, are permitted provided that the following conditions are met: 006 * 007 * o Redistributions of source code must retain the above copyright notice, 008 * this list of conditions and the following disclaimer. 009 * 010 * o Redistributions in binary form must reproduce the above copyright notice, 011 * this list of conditions and the following disclaimer in the documentation 012 * and/or other materials provided with the distribution. 013 * 014 * o Neither the name of JGoodies Karsten Lentzsch nor the names of 015 * its contributors may be used to endorse or promote products derived 016 * from this software without specific prior written permission. 017 * 018 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 019 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 020 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 021 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 022 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 023 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 024 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 025 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 027 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030 031package com.jgoodies.forms.factories; 032 033import java.awt.Color; 034import java.awt.Component; 035import java.awt.Container; 036import java.awt.Dimension; 037import java.awt.Font; 038import java.awt.FontMetrics; 039import java.awt.Insets; 040import java.awt.LayoutManager; 041 042import javax.swing.JComponent; 043import javax.swing.JLabel; 044import javax.swing.JPanel; 045import javax.swing.JSeparator; 046import javax.swing.SwingConstants; 047import javax.swing.UIManager; 048 049import com.jgoodies.forms.layout.Sizes; 050import com.jgoodies.forms.util.Utilities; 051 052/** 053 * A singleton implementation of the {@link ComponentFactory} interface 054 * that creates UI components as required by the 055 * {@link com.jgoodies.forms.builder.PanelBuilder}.<p> 056 * 057 * The texts used in methods <code>#createLabel(String)</code> and 058 * <code>#createTitle(String)</code> can contain an optional mnemonic marker. 059 * The mnemonic and mnemonic index are indicated by a single ampersand 060 * (<tt>&</tt>). For example <tt>"&Save"</tt>, 061 * or <tt>"Save &as"</tt>. To use the ampersand itself 062 * duplicate it, for example <tt>"Look&&Feel"</tt>. 063 * 064 * @author Karsten Lentzsch 065 * @version $Revision$ 066 */ 067 068public final class DefaultComponentFactory implements ComponentFactory { 069 070 /** 071 * Holds the single instance of this class. 072 */ 073 private static final DefaultComponentFactory INSTANCE = new DefaultComponentFactory(); 074 075 /** 076 * The character used to indicate the mnemonic position for labels. 077 */ 078 private static final char MNEMONIC_MARKER = '&'; 079 080 // Instance ************************************************************* 081 082 private DefaultComponentFactory() { 083 // Suppresses default constructor, ensuring non-instantiability. 084 } 085 086 /** 087 * Returns the sole instance of this factory class. 088 * 089 * @return the sole instance of this factory class 090 */ 091 public static DefaultComponentFactory getInstance() { 092 return INSTANCE; 093 } 094 095 // Component Creation *************************************************** 096 097 /** 098 * Creates and returns a label with an optional mnemonic. 099 * 100 * <pre> 101 * createLabel("Name"); // No mnemonic 102 * createLabel("N&ame"); // Mnemonic is 'a' 103 * createLabel("Save &as"); // Mnemonic is the second 'a' 104 * createLabel("Look&&Feel"); // No mnemonic, text is Look&Feel 105 * </pre> 106 * 107 * @param textWithMnemonic the label's text - 108 * may contain an ampersand (<tt>&</tt>) to mark a mnemonic 109 * @return an label with optional mnemonic 110 */ 111 @Override 112 public JLabel createLabel(String textWithMnemonic) { 113 JLabel label = new JLabel(); 114 setTextAndMnemonic(label, textWithMnemonic); 115 return label; 116 } 117 118 /** 119 * Creates and returns a title label that uses the foreground color 120 * and font of a <code>TitledBorder</code>. 121 * 122 * <pre> 123 * createTitle("Name"); // No mnemonic 124 * createTitle("N&ame"); // Mnemonic is 'a' 125 * createTitle("Save &as"); // Mnemonic is the second 'a' 126 * createTitle("Look&&Feel"); // No mnemonic, text is Look&Feel 127 * </pre> 128 * 129 * @param textWithMnemonic the label's text - 130 * may contain an ampersand (<tt>&</tt>) to mark a mnemonic 131 * @return an emphasized title label 132 */ 133 @Override 134 public JLabel createTitle(String textWithMnemonic) { 135 JLabel label = new TitleLabel(); 136 setTextAndMnemonic(label, textWithMnemonic); 137 label.setVerticalAlignment(SwingConstants.CENTER); 138 return label; 139 } 140 141 /** 142 * Creates and returns a labeled separator with the label in the left-hand 143 * side. Useful to separate paragraphs in a panel; often a better choice 144 * than a <code>TitledBorder</code>. 145 * 146 * <pre> 147 * createSeparator("Name"); // No mnemonic 148 * createSeparator("N&ame"); // Mnemonic is 'a' 149 * createSeparator("Save &as"); // Mnemonic is the second 'a' 150 * createSeparator("Look&&Feel"); // No mnemonic, text is Look&Feel 151 * </pre> 152 * 153 * @param textWithMnemonic the label's text - 154 * may contain an ampersand (<tt>&</tt>) to mark a mnemonic 155 * @return a title label with separator on the side 156 */ 157 public JComponent createSeparator(String textWithMnemonic) { 158 return createSeparator(textWithMnemonic, SwingConstants.LEFT); 159 } 160 161 /** 162 * Creates and returns a labeled separator. Useful to separate 163 * paragraphs in a panel, which is often a better choice than a 164 * <code>TitledBorder</code>. 165 * 166 * <pre> 167 * final int LEFT = SwingConstants.LEFT; 168 * createSeparator("Name", LEFT); // No mnemonic 169 * createSeparator("N&ame", LEFT); // Mnemonic is 'a' 170 * createSeparator("Save &as", LEFT); // Mnemonic is the second 'a' 171 * createSeparator("Look&&Feel", LEFT); // No mnemonic, text is Look&Feel 172 * </pre> 173 * 174 * @param textWithMnemonic the label's text - 175 * may contain an ampersand (<tt>&</tt>) to mark a mnemonic 176 * @param alignment text alignment, one of <code>SwingConstants.LEFT</code>, 177 * <code>SwingConstants.CENTER</code>, <code>SwingConstants.RIGHT</code> 178 * @return a separator with title label 179 */ 180 @Override 181 public JComponent createSeparator(String textWithMnemonic, int alignment) { 182 if (textWithMnemonic == null || textWithMnemonic.length() == 0) { 183 return new JSeparator(); 184 } 185 JLabel title = createTitle(textWithMnemonic); 186 title.setHorizontalAlignment(alignment); 187 return createSeparator(title); 188 } 189 190 /** 191 * Creates and returns a labeled separator. Useful to separate 192 * paragraphs in a panel, which is often a better choice than a 193 * <code>TitledBorder</code>. 194 * 195 * <p>The label's position is determined by the label's horizontal alignment, 196 * which must be one of: 197 * <code>SwingConstants.LEFT</code>, 198 * <code>SwingConstants.CENTER</code>, 199 * <code>SwingConstants.RIGHT</code>.</p> 200 * 201 * <p>TODO: Since this method has been marked public in version 1.0.6, 202 * we need to precisely describe the semantic of this method.</p> 203 * 204 * <p>TODO: Check if we can relax the constraint for the label alignment 205 * and also accept LEADING and TRAILING.</p> 206 * 207 * @param label the title label component 208 * @return a separator with title label 209 * @exception NullPointerException if the label is <code>null</code> 210 * 211 * @since 1.0.6 212 */ 213 public JComponent createSeparator(JLabel label) { 214 if (label == null) { 215 throw new NullPointerException("The label must not be null."); 216 } 217 218 JPanel panel = new JPanel( 219 new TitledSeparatorLayout(!Utilities.isLafAqua())); 220 panel.setOpaque(false); 221 panel.add(label); 222 panel.add(new JSeparator()); 223 if (label.getHorizontalAlignment() == SwingConstants.CENTER) { 224 panel.add(new JSeparator()); 225 } 226 return panel; 227 } 228 229 // Helper Code *********************************************************** 230 231 /** 232 * Sets the text of the given label and optionally a mnemonic. 233 * The given text may contain an ampersand (<tt>&</tt>) 234 * to mark a mnemonic and its position. Such a marker indicates 235 * that the character that follows the ampersand shall be the mnemonic. 236 * If you want to use the ampersand itself duplicate it, for example 237 * <tt>"Look&&Feel"</tt>. 238 * 239 * @param label the label that gets a mnemonic 240 * @param textWithMnemonic the text with optional mnemonic marker 241 */ 242 private static void setTextAndMnemonic(JLabel label, 243 String textWithMnemonic) { 244 int markerIndex = textWithMnemonic.indexOf(MNEMONIC_MARKER); 245 // No marker at all 246 if (markerIndex == -1) { 247 label.setText(textWithMnemonic); 248 return; 249 } 250 int mnemonicIndex = -1; 251 int begin = 0; 252 int end; 253 int length = textWithMnemonic.length(); 254 int quotedMarkers = 0; 255 StringBuffer buffer = new StringBuffer(); 256 do { 257 // Check whether the next index has a mnemonic marker, too 258 if (markerIndex + 1 < length && textWithMnemonic 259 .charAt(markerIndex + 1) == MNEMONIC_MARKER) { 260 end = markerIndex + 1; 261 quotedMarkers++; 262 } else { 263 end = markerIndex; 264 if (mnemonicIndex == -1) { 265 mnemonicIndex = markerIndex - quotedMarkers; 266 } 267 } 268 buffer.append(textWithMnemonic.substring(begin, end)); 269 begin = end + 1; 270 markerIndex = begin < length 271 ? textWithMnemonic.indexOf(MNEMONIC_MARKER, begin) 272 : -1; 273 } while (markerIndex != -1); 274 buffer.append(textWithMnemonic.substring(begin)); 275 276 String text = buffer.toString(); 277 label.setText(text); 278 if (mnemonicIndex != -1 && mnemonicIndex < text.length()) { 279 label.setDisplayedMnemonic(text.charAt(mnemonicIndex)); 280 label.setDisplayedMnemonicIndex(mnemonicIndex); 281 } 282 } 283 284 /** 285 * A label that uses the TitleBorder font and color. 286 */ 287 @SuppressWarnings("serial") 288 private static final class TitleLabel extends JLabel { 289 290 private TitleLabel() { 291 // Just invoke the super constructor. 292 } 293 294 /** 295 * TODO: For the Synth-based L&f we should consider asking 296 * a <code>TitledBorder</code> instance for its font and color using 297 * <code>#getTitleFont</code> and <code>#getTitleColor</code> resp. 298 */ 299 @Override 300 public void updateUI() { 301 super.updateUI(); 302 Color foreground = getTitleColor(); 303 if (foreground != null) { 304 setForeground(foreground); 305 } 306 setFont(getTitleFont()); 307 } 308 309 private Color getTitleColor() { 310 return UIManager.getColor("TitledBorder.titleColor"); 311 } 312 313 /** 314 * Looks up and returns the font used for title labels. 315 * Since Mac Aqua uses an inappropriate titled border font, 316 * we use a bold label font instead. Actually if the title 317 * is used in a titled separator, the bold weight is questionable. 318 * It seems that most native Aqua tools use a plain label in 319 * titled separators. 320 * 321 * @return the font used for title labels 322 */ 323 private Font getTitleFont() { 324 return Utilities.isLafAqua() 325 ? UIManager.getFont("Label.font").deriveFont(Font.BOLD) 326 : UIManager.getFont("TitledBorder.font"); 327 } 328 329 } 330 331 /** 332 * A layout for the title label and separator(s) in titled separators. 333 */ 334 private static final class TitledSeparatorLayout implements LayoutManager { 335 336 private final boolean centerSeparators; 337 338 /** 339 * Constructs a TitledSeparatorLayout that either centers the separators 340 * or aligns them along the font baseline of the title label. 341 * 342 * @param centerSeparators true to center, false to align along 343 * the font baseline of the title label 344 */ 345 private TitledSeparatorLayout(boolean centerSeparators) { 346 this.centerSeparators = centerSeparators; 347 } 348 349 /** 350 * Does nothing. This layout manager looks up the components 351 * from the layout container and used the component's index 352 * in the child array to identify the label and separators. 353 * 354 * @param name the string to be associated with the component 355 * @param comp the component to be added 356 */ 357 @Override 358 public void addLayoutComponent(String name, Component comp) { 359 // Does nothing. 360 } 361 362 /** 363 * Does nothing. This layout manager looks up the components 364 * from the layout container and used the component's index 365 * in the child array to identify the label and separators. 366 * 367 * @param comp the component to be removed 368 */ 369 @Override 370 public void removeLayoutComponent(Component comp) { 371 // Does nothing. 372 } 373 374 /** 375 * Computes and returns the minimum size dimensions 376 * for the specified container. Forwards this request 377 * to <code>#preferredLayoutSize</code>. 378 * 379 * @param parent the component to be laid out 380 * @return the container's minimum size. 381 * @see #preferredLayoutSize(Container) 382 */ 383 @Override 384 public Dimension minimumLayoutSize(Container parent) { 385 return preferredLayoutSize(parent); 386 } 387 388 /** 389 * Computes and returns the preferred size dimensions 390 * for the specified container. Returns the title label's 391 * preferred size. 392 * 393 * @param parent the component to be laid out 394 * @return the container's preferred size. 395 * @see #minimumLayoutSize(Container) 396 */ 397 @Override 398 public Dimension preferredLayoutSize(Container parent) { 399 Component label = getLabel(parent); 400 Dimension labelSize = label.getPreferredSize(); 401 Insets insets = parent.getInsets(); 402 int width = labelSize.width + insets.left + insets.right; 403 int height = labelSize.height + insets.top + insets.bottom; 404 return new Dimension(width, height); 405 } 406 407 /** 408 * Lays out the specified container. 409 * 410 * @param parent the container to be laid out 411 */ 412 @Override 413 public void layoutContainer(Container parent) { 414 synchronized (parent.getTreeLock()) { 415 // Look up the parent size and insets 416 Dimension size = parent.getSize(); 417 Insets insets = parent.getInsets(); 418 int width = size.width - insets.left - insets.right; 419 420 // Look up components and their sizes 421 JLabel label = getLabel(parent); 422 Dimension labelSize = label.getPreferredSize(); 423 int labelWidth = labelSize.width; 424 int labelHeight = labelSize.height; 425 Component separator1 = parent.getComponent(1); 426 int separatorHeight = separator1.getPreferredSize().height; 427 428 FontMetrics metrics = label.getFontMetrics(label.getFont()); 429 int ascent = metrics.getMaxAscent(); 430 int hGapDlu = centerSeparators ? 3 : 1; 431 int hGap = Sizes.dialogUnitXAsPixel(hGapDlu, label); 432 int vOffset = centerSeparators 433 ? 1 + (labelHeight - separatorHeight) / 2 434 : ascent - separatorHeight / 2; 435 436 int alignment = label.getHorizontalAlignment(); 437 int y = insets.top; 438 if (alignment == SwingConstants.LEFT) { 439 int x = insets.left; 440 label.setBounds(x, y, labelWidth, labelHeight); 441 x += labelWidth; 442 x += hGap; 443 int separatorWidth = size.width - insets.right - x; 444 separator1.setBounds(x, y + vOffset, separatorWidth, 445 separatorHeight); 446 } else if (alignment == SwingConstants.RIGHT) { 447 int x = insets.left + width - labelWidth; 448 label.setBounds(x, y, labelWidth, labelHeight); 449 x -= hGap; 450 x--; 451 int separatorWidth = x - insets.left; 452 separator1.setBounds(insets.left, y + vOffset, 453 separatorWidth, separatorHeight); 454 } else { 455 int xOffset = (width - labelWidth - 2 * hGap) / 2; 456 int x = insets.left; 457 separator1.setBounds(x, y + vOffset, xOffset - 1, 458 separatorHeight); 459 x += xOffset; 460 x += hGap; 461 label.setBounds(x, y, labelWidth, labelHeight); 462 x += labelWidth; 463 x += hGap; 464 Component separator2 = parent.getComponent(2); 465 int separatorWidth = size.width - insets.right - x; 466 separator2.setBounds(x, y + vOffset, separatorWidth, 467 separatorHeight); 468 } 469 } 470 } 471 472 private JLabel getLabel(Container parent) { 473 return (JLabel) parent.getComponent(0); 474 } 475 476 } 477 478}