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