View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  
20  /*
21   * This class has been adopted for the XNap project.
22   */
23  // Copyright (C) 2000 Greg Merrill (greghmerrill@yahoo.com)
24  // Distributed under the terms of the GNU General Public License (version 2)
25  // For details on the GNU GPL, please visit http://www.gnu.org/copyleft/gpl.html
26  // To find out more about this and other free software by Greg Merrill, 
27  //  please visit http://gregmerrill.imagineis.com
28  package org.xnap.gui.component;
29  
30  import java.awt.Canvas;
31  import java.awt.Color;
32  import java.awt.Component;
33  import java.awt.Font;
34  import java.awt.Graphics;
35  import java.awt.Graphics2D;
36  import java.awt.GraphicsEnvironment;
37  import java.awt.GridBagConstraints;
38  import java.awt.GridBagLayout;
39  import java.awt.Insets;
40  import java.awt.font.FontRenderContext;
41  import java.awt.font.GlyphVector;
42  import java.awt.geom.Rectangle2D;
43  import java.util.Observable;
44  import java.util.Observer;
45  
46  import javax.swing.DefaultListCellRenderer;
47  import javax.swing.JLabel;
48  import javax.swing.JList;
49  import javax.swing.JPanel;
50  import javax.swing.JScrollPane;
51  import javax.swing.JTextField;
52  import javax.swing.ListModel;
53  import javax.swing.ListSelectionModel;
54  import javax.swing.border.EmptyBorder;
55  import javax.swing.event.DocumentEvent;
56  import javax.swing.event.DocumentListener;
57  import javax.swing.event.ListSelectionEvent;
58  import javax.swing.event.ListSelectionListener;
59  
60  /***
61  A component which allows a user to select a font. Here is a code sample
62  demonstrating its use:
63  <a name="codeSample">
64  <p>
65  <!-- prefix each pre line with '*' to avoid javadoc whitespace bug -->
66  <pre>
67  * import java.awt.*;
68  * import java.awt.event.*;
69  * import javax.swing.*;
70  * 
71  * public class FontSelectionPanelDemo {
72  *   public static void main (String[] args) {
73  *     final JFrame frame = new JFrame();
74  *     JPanel panel = new JPanel(new BorderLayout());
75  *     final FontSelectionPanel fontSelectionPanel = new FontSelectionPanel(
76  *       new Font("Times New Roman", Font.BOLD+Font.ITALIC, 14)
77  *     );
78  *     panel.add(fontSelectionPanel, BorderLayout.CENTER);
79  *     JButton button = new JButton("OK");
80  *     button.addActionListener(new ActionListener () {
81  *       public void actionPerformed (ActionEvent e) {
82  *         try {
83  *           JOptionPane.showMessageDialog(
84  *             frame, 
85  *             "Selected font is: " + fontSelectionPanel.getSelectedFont(),
86  *             "Selected Font",
87  *             JOptionPane.INFORMATION_MESSAGE
88  *           );
89  *         }
90  *         catch (FontSelectionPanel.InvalidFontException ife) {
91  *           JOptionPane.showMessageDialog(
92  *             frame, 
93  *             "You have not selected a valid font",
94  *             "Invalid Font",
95  *             JOptionPane.ERROR_MESSAGE
96  *           );
97  *         }
98  *       }
99  *     });
100 *     panel.add(button, BorderLayout.SOUTH);
101 *     frame.setContentPane(panel);
102 *     frame.addWindowListener(new WindowAdapter () {
103 *       public void windowClosing (WindowEvent e) { System.exit(0); }
104 *     });
105 *     frame.pack();
106 *     frame.show();
107 *   }
108 * }
109 </pre>
110 
111 <p>
112 <a name="versionHistory">
113 <h3>Version History:</h3>
114 <dl>
115 <dt><b>1.1</b> (August 19, 2000)
116 <dd><tt>protected</tt> access to all major components now provided for the 
117   benefit of subclasses<br>
118   Added methods 
119     {@link #setSelectedFont(java.awt.Font)},
120     {@link #setSelectedFontFamily(String)},
121     {@link #setSelectedFontStyle(int)},
122     {@link #setSelectedFontSize(int)}<br>
123   GridBag fill strategy modified slightly to improve resizing behavior<br>
124   Changed sample code to use BorderLayout<br>
125   Added version history to javadocs
126 <dt><b>1.0</b> (August 15, 2000)
127 <dd>Initial release
128 </dl>
129 
130 <p>
131 Copyright (C) 2000 Greg Merrill (
132 <a href="mailto:greghmerrill@yahoo.com">greghmerrill@yahoo.com</a>).
133 Distributed under the terms of the GNU General Public License (version 2).
134 For details on the GNU GPL, please visit 
135 <a href="http://www.gnu.org/copyleft/gpl.html"
136 >http://www.gnu.org/copyleft/gpl.html</a>.
137 To find out more about this and other free software by Greg Merrill, 
138 please visit <a href="http://gregmerrill.imagineis.com"
139 >http://gregmerrill.imagineis.com</a>
140 
141 @author <a href="mailto:greghmerrill@yahoo.com">Greg Merrill</a>
142 @version 1.1
143 */
144 public class FontSelectionPanel extends JPanel {
145 
146   /***
147   Like {@link #FontSelectionPanel(java.awt.Font)}, except an initialFont of 
148   <code>null</code> will be used.
149   */
150   public FontSelectionPanel () {
151     this(null);
152   }
153 
154   /***
155   Like {@link #FontSelectionPanel(java.awt.Font, String[], int[])}, except that
156   a default list of styles 
157   (<code>{"Plain", "Bold", "Italic", "Bold Italic"}</code>) and font 
158   sizes (<code>{8, 9, 10, 12, 14}</code>) will be used.
159   @param initialFont see 
160     {@link #FontSelectionPanel(java.awt.Font, String[], int[])}
161   */
162   public FontSelectionPanel (Font initialFont) {
163     this(
164       initialFont,
165       // Don't change the following two values without changing the javadocs
166       new String [] {"Plain", "Bold", "Italic", "Bold Italic"},
167       new int [] {8, 9, 10, 12, 14}
168     );
169   }
170   
171   /***
172   Construct a new FontSelectionPanel whose family, style & size widget 
173   selections are set according to the supplied initial Font. Additionally, 
174   the style & size values available will be dictated by the values in 
175   styleDisplayNames and predefinedSizes, respectively.
176   @param initialFont the newly constructed FontSelectionPanel's family,
177     style, and size widgets will be set according to this value. This value
178     may be null, in which case an initial font will be automatically created.
179     This auto-created font will have a family, style, and size corresponding
180     to the first avaiable value in the widget form family, style, and size
181     respectively.
182   @param styleDisplayNames must contain exactly four members. The members
183     of this array represent the following styles, in order: Font.PLAIN, 
184     Font.BOLD, Font.ITALIC, and Font.BOLD+Font.ITALIC
185   @param predefinedSizes must contain one or more predefined font sizes which
186     will be available to the user as a convenience for populating the font
187     size text field; all values must be greater than 0.
188   */
189   public FontSelectionPanel (
190     Font initialFont,
191     String[] styleDisplayNames,
192     int[] predefinedSizes
193   ) {
194     super(new GridBagLayout());
195     this.setBorder(new EmptyBorder(12, 12, 11, 11));
196     GridBagConstraints gbc = new GridBagConstraints();
197 
198     String[] availableFontFamilyNames = 
199 GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
200 
201     if (initialFont == null) { initialFont = new Font(
202         availableFontFamilyNames[0], Font.PLAIN, predefinedSizes[0]
203     );}
204     
205     // Font family
206     fontFamilyList_ = new JList(availableFontFamilyNames);
207     fontFamilyList_.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
208     fontFamilyList_.setVisibleRowCount(8);
209     ListSelectionListener phraseCanvasUpdater = new ListSelectionListener () {
210       public void valueChanged (ListSelectionEvent e) {
211         if (!e.getValueIsAdjusting()) {
212           observable_.setChanged();
213           observable_.notifyObservers();
214         }
215       }
216     };
217     fontFamilyList_.addListSelectionListener(phraseCanvasUpdater);
218     gbc.fill = GridBagConstraints.BOTH;    
219     gbc.gridheight = 2;
220     this.add(new JScrollPane(fontFamilyList_), gbc);
221 
222     // Font style
223     fontStyleList_ = new FontStyleList(styleDisplayNames);
224     fontStyleList_.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
225     fontStyleList_.setVisibleRowCount(4);
226     fontStyleList_.addListSelectionListener(phraseCanvasUpdater);
227     gbc.gridx = 1;
228     gbc.insets = new Insets(0, 10, 0, 0);
229     // fontStyleList_ is put into a JScrollPane only because it puts a nice
230     // border around it which is consistent with the border around 
231     // fontFamilyList_
232     this.add(new JScrollPane(fontStyleList_), gbc);
233     
234     // Font size
235     fontSize_ = new JTextField();
236     fontSize_.setHorizontalAlignment(JTextField.RIGHT);
237     fontSize_.setColumns(4);
238     gbc.gridx = 2;
239     gbc.gridheight = 1;
240     gbc.fill = GridBagConstraints.HORIZONTAL;
241     this.add(fontSize_, gbc);
242     fontSizeList_ = new JList(
243       validateAndConvertPredefinedSizes(predefinedSizes)
244     );
245     fontSizeList_.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
246     // Will be able to see more than 1 row because gbc.fill set to BOTH
247     fontSizeList_.setVisibleRowCount(1);
248     fontSizeList_.setCellRenderer(new ListCellRenderer());
249     gbc.gridy = 1;
250     gbc.insets = new Insets(10, 10, 0, 0);
251     gbc.fill = GridBagConstraints.BOTH;
252     this.add(new JScrollPane(fontSizeList_), gbc);
253 
254     // Phrase Canvas (displays current font selection)
255     phraseCanvas_ = new PhraseCanvas(
256       initialFont.getFamily(), initialFont, Color.black
257     );
258     addObserver(new Observer () {
259       public void update (Observable o, Object arg) {
260         try {
261           phraseCanvas_.setPhrase((String)fontFamilyList_.getSelectedValue());
262           phraseCanvas_.setFont(FontSelectionPanel.this.getSelectedFont());
263         }
264         catch (InvalidFontException e) {
265           phraseCanvas_.setPhrase("");
266         }
267         phraseCanvas_.invalidate();
268         phraseCanvas_.repaint();
269       }
270     });
271     phraseCanvas_.setSize(
272       (int)this.getPreferredSize().getWidth(), 100
273     );
274     gbc.gridy = 2;
275     gbc.gridx = 0;
276     gbc.gridwidth = 3;
277     gbc.insets = new Insets(10, 0, 0, 0);
278     gbc.fill = GridBagConstraints.HORIZONTAL;
279     // put into JScrollPane for formatting purposes (no scrolling ever occurs)
280     this.add(new JScrollPane(phraseCanvas_), gbc);
281     
282     // Use FontSizeSynchronizer to ensure consistency between text field &
283     // list for font size
284     FontSizeSynchronizer fontSizeSynchronizer = 
285       new FontSizeSynchronizer(fontSizeList_, fontSize_);
286     fontSizeList_.addListSelectionListener(fontSizeSynchronizer);
287     fontSize_.getDocument().addDocumentListener(fontSizeSynchronizer);
288 
289     // Set initial widget values here at the end of the constructor to 
290     // ensure that all listeners have been added beforehand
291     fontFamilyList_.setSelectedValue(initialFont.getFamily(), true);
292     fontStyleList_.setSelectedStyle(initialFont.getStyle());
293     fontSize_.setText(String.valueOf(initialFont.getSize()));
294   }
295   /*** JList for font family */
296   protected JList fontFamilyList_;
297   /*** FontStlyeList (subclass of JList) for font style */
298   protected FontStyleList fontStyleList_;
299   /*** JTextField for font size */
300   protected JTextField fontSize_;
301   /*** JList for font size */
302   protected JList fontSizeList_;
303   /*** PhraseCanvas in which font samples are displayed */
304   protected PhraseCanvas phraseCanvas_;
305   
306   /***
307   @exception IllegalArgumentException thrown if <ul>
308     <li>predefinedSizes does not contain one or more integer values
309     <li>predefinedSizes contains any integers with a value of less than 1
310     </ul>
311   */  
312   private Integer[] validateAndConvertPredefinedSizes (int[] predefinedSizes) {
313     if (predefinedSizes == null) { throw new IllegalArgumentException(
314       "int[] predefinedSizes may not be null"
315     );}
316     if (predefinedSizes.length < 1) { throw new IllegalArgumentException(
317       "int[] predefinedSizes must contain one or more values"
318     );}
319     Integer[] predefinedSizeIntegers = new Integer[predefinedSizes.length];
320     for (int i=0; i < predefinedSizes.length; i++) {
321       if (predefinedSizes[i] < 1) { throw new IllegalArgumentException(
322         "int[] predefinedSizes may not contain integers with value less than 1"
323       );}
324       predefinedSizeIntegers[i] = new Integer(predefinedSizes[i]);
325     }
326     return predefinedSizeIntegers;
327   }
328 
329   /***
330   Adds an Observer to this FontSelectionPanel; the supplied Observer will 
331   have its update() method called any time the Font currently specified
332   in the FontSelectionPanel changes. (The <tt>arg</tt> supplied to the
333   Observer will be <tt>null</tt>.)
334   @param o observer to be added
335   @see java.util.Observer
336   */
337   public void addObserver (Observer o) { observable_.addObserver(o); }
338   /***
339   Removes an Observer from this FontSelectionPanel.
340   @param o Observer to be removed
341   @see java.util.Observer
342   */
343   public void deleteObserver (Observer o) { observable_.deleteObserver(o); }
344   /*** Observable used for registering/notifying Observers */
345   protected PublicChangeObservable observable_ = new PublicChangeObservable();
346 
347   /***
348   Returns the currently selected font family
349   @return currently selected font family
350   @exception NoFontFamilySelectedException thrown if no font family is
351     currently selected
352   */
353   public String getSelectedFontFamily () throws NoFontFamilySelectedException {
354     String fontFamily = (String)fontFamilyList_.getSelectedValue();
355     if (fontFamily == null) { throw new NoFontFamilySelectedException(
356         "No font family is currently selected"
357     );}
358     return fontFamily;
359   }
360 
361   /***
362   Returns the currently selected font style.
363   @return currently selected font style. This value will correspond to one
364     of the font styles specified in {@link java.awt.Font}
365   @exception NoFontStyleSelectedException thrown if no font 
366     style is currently selected
367   */
368   public int getSelectedFontStyle () throws NoFontStyleSelectedException {
369     return fontStyleList_.getSelectedStyle();
370   }
371   
372   /***
373   Returns the currently selected font size.
374   @return currently selected font size.
375   @exception NoFontSizeSpecifiedException thrown if no font size is
376     currently specified
377   @exception InvalidFontSizeException thrown if the font size 
378     currently specified is invalid
379   */
380   public int getSelectedFontSize () 
381   throws NoFontSizeSpecifiedException, InvalidFontSizeException {
382     String fontSize = fontSize_.getText();    
383     if ((fontSize == null) || (fontSize.equals(""))) {
384       throw new NoFontSizeSpecifiedException("No font size specified");
385     }
386     if (fontSize.length() > maxNumCharsInFontSize_) {
387       throw new InvalidFontSizeException("Too many characters in font size");
388     }
389     try { return Integer.parseInt(fontSize); }
390     catch (NumberFormatException e) { throw new InvalidFontSizeException(
391 "The number specified in the font size text field (" + fontSize_.getText() +
392 ") is not a valid integer."
393     );}
394   }
395 
396   /***
397   Returns the currently selected font.
398   @return currently selected font.
399   @exception InvalidFontException thrown if no valid font is currently 
400     specified; the actual class of the exception thrown may be
401     {@link FontSelectionPanel.InvalidFontException}, 
402     {@link FontSelectionPanel.NoFontFamilySelectedException}, 
403     {@link FontSelectionPanel.NoFontStyleSelectedException},
404     {@link FontSelectionPanel.NoFontSizeSpecifiedException}, 
405     or {@link FontSelectionPanel.InvalidFontSizeException}
406   */
407   public Font getSelectedFont () throws InvalidFontException {    
408     return new Font(
409       getSelectedFontFamily(), 
410       getSelectedFontStyle(), 
411       getSelectedFontSize()
412     );
413   }
414 
415   /***
416   Changes the currently selected font by assigning all widget values to match
417   the family/style/size values of the supplied font
418   @param font font whose values should be used to set widgets
419   @exception IllegalArgumentException thrown if the family or style of the
420     font supplied are not available or invalid
421   */
422   public void setSelectedFont (Font font) {
423     setSelectedFontFamily(font.getFamily());
424     setSelectedFontStyle(font.getStyle());
425     setSelectedFontSize(font.getSize());
426   }
427 
428   /***
429   Sets the currently selected font family.
430   @param family family to which selection should change
431   @exception IllegalArgumentException thrown if the supplied font family is
432     not among the list of available font families
433   */  
434   public void setSelectedFontFamily (String family) {
435     ListModel familyListModel = fontFamilyList_.getModel();
436     for (int i=0; i < familyListModel.getSize(); i++) {
437 	String s = familyListModel.getElementAt(i).toString();
438 	if (s.equalsIgnoreCase(family)) {
439 	    fontFamilyList_.setSelectedIndex(i);
440 	    fontFamilyList_.ensureIndexIsVisible(i); 
441 	    return;
442 	}
443     }
444     throw new IllegalArgumentException(
445 "The font family supplied, '" + family + "', is not in the list of availalbe " +
446 "font families."
447     );
448   }
449   
450   /***
451   Sets the currently selected font style.
452   @param style style to which selection should change
453   @exception IllegalArgumentException thrown if the supplied font style is
454     not one of Font.PLAIN, Font.BOLD, Font.ITALIC, or Font.BOLD+Font.ITALIC
455   */  
456   public void setSelectedFontStyle (int style) {
457     fontStyleList_.setSelectedStyle(style);
458   }
459 
460   /***
461   Sets the currently selected font size.
462   @param size size to which selection should change
463   */  
464   public void setSelectedFontSize (int size) {
465     fontSize_.setText(String.valueOf(size));
466   }
467 
468   /*** Maximum number of characters permissibile in a valid font size */
469   protected int maxNumCharsInFontSize_ = 3;
470   
471 
472   /***
473   This class synchronizes font size value between the list containing
474   available font sizes & the text field in which font size is ultimately
475   specified.
476   */  
477   protected class FontSizeSynchronizer 
478   implements DocumentListener, ListSelectionListener {
479     /***
480     @param list list containing predefined font sizes 
481     @param textField text field in which font size is specified
482     */
483     public FontSizeSynchronizer (JList list, JTextField textField) {
484       list_ = list;
485       textField_ = textField;
486     }
487 
488     /*** @see javax.swing.event.ListSelectionListener */
489     public void valueChanged (ListSelectionEvent e) {
490       if (updating_) { return; }
491       updating_ = true;
492       if (!e.getValueIsAdjusting()) { 
493         Object selectedValue = ((JList)e.getSource()).getSelectedValue();
494         if (selectedValue != null) {
495           textField_.setText(selectedValue.toString());
496         }
497         observable_.setChanged();
498         observable_.notifyObservers();
499       }
500       updating_ = false;
501     }
502     
503     /*** @see javax.swing.event.DocumentListener */
504     public void changedUpdate (DocumentEvent e) { handle(e); }
505     /*** @see javax.swing.event.DocumentListener */
506     public void insertUpdate (DocumentEvent e) { handle(e); }
507     /*** @see javax.swing.event.DocumentListener */
508     public void removeUpdate (DocumentEvent e) { handle(e); }
509     /*** Handles all DocumentEvents */
510     protected void handle (DocumentEvent e) {
511       if (updating_) { return; }
512       updating_ = true;
513       try {
514         Integer currentFontSizeInteger = Integer.valueOf(textField_.getText());
515         boolean currentSizeWasInList = false;
516         Object listMember;
517         for (int i=0; i < list_.getModel().getSize(); i++) {
518           listMember = list_.getModel().getElementAt(i);
519           if (listMember.equals(currentFontSizeInteger)) {
520             list_.setSelectedValue(currentFontSizeInteger, true);
521             currentSizeWasInList = true;
522             break;
523           }
524         }
525         if (!currentSizeWasInList) { list_.clearSelection(); }
526       }
527       catch (NumberFormatException nfe) { list_.clearSelection(); }
528       observable_.setChanged();
529       observable_.notifyObservers();
530       updating_ = false;
531     }
532     
533     protected JList list_;
534     protected JTextField textField_;
535     protected boolean updating_;
536   }
537 
538   //
539   // Static inner classes
540   //
541 
542   /***
543   Represents a list of the four font styles: plain, bold, italic, and 
544   bold italic
545   */
546   protected static class FontStyleList extends JList {
547     /***
548     Construct a new FontStyleList, using the supplied values for style
549     display names
550     @param styleDisplayNames must contain exactly four members. The members
551       of this array represent the following styles, in order: Font.PLAIN, 
552       Font.BOLD, Font.ITALIC, and Font.BOLD+Font.ITALIC
553     @exception IllegalArgumentException thrown if styleDisplayNames does not
554       contain exactly four String values
555     */  
556     public FontStyleList (String[] styleDisplayNames) {
557       super(validateStyleDisplayNames(styleDisplayNames));
558     }
559     private static String[] validateStyleDisplayNames (
560       String[] styleDisplayNames
561     ) {
562       if (styleDisplayNames == null) { throw new IllegalArgumentException(
563         "String[] styleDisplayNames may not be null"
564       );}
565       if (styleDisplayNames.length != 4) { throw new IllegalArgumentException(
566         "String[] styleDisplayNames must have a length of 4"
567       );}
568       for (int i=0; i < styleDisplayNames.length; i++) {
569         if (styleDisplayNames[i] == null) { throw new IllegalArgumentException(
570           "No member of String[] styleDisplayNames may be null"
571         );}
572       }
573       return styleDisplayNames;
574     }
575     /***
576     @return currently selected font style
577     @exception NoFontStyleSelectedException thrown if no font style is 
578       currently selected
579     */
580     public int getSelectedStyle () throws NoFontStyleSelectedException {
581       switch (this.getSelectedIndex()) {
582        case 0: return Font.PLAIN; case 1: return Font.BOLD;
583        case 2: return Font.ITALIC; case 3: return Font.BOLD+Font.ITALIC;
584        default: throw new NoFontStyleSelectedException(
585          "No font style is currently selected"
586        );
587       }
588     }
589     /***
590     Change the currently selected style in this FontStyleList
591     @param style new selected style for this FontStyleList
592     @exception IllegalArgumentException thrown if style is not one of 
593       Font.PLAIN, Font.BOLD, Font.ITALIC, or Font.BOLD+Font.ITALIC
594     */
595     public void setSelectedStyle (int style) {
596       switch (style) {
597         case Font.PLAIN: this.setSelectedIndex(0); break;
598         case Font.BOLD: this.setSelectedIndex(1); break;
599         case Font.ITALIC: this.setSelectedIndex(2); break;
600         case Font.BOLD+Font.ITALIC: this.setSelectedIndex(3); break;
601         default: throw new IllegalArgumentException(
602           "int style must come from java.awt.Font"
603         );
604       }
605     }
606   }
607   
608   /***
609   An implementation of {@link javax.swing.ListCellRenderer} which right
610   justifies all cells.
611   */
612   protected static class ListCellRenderer extends DefaultListCellRenderer {
613     public Component getListCellRendererComponent (
614       JList list,
615       Object value,
616       int index,
617       boolean isSelected,
618       boolean cellHasFocus
619     ) {
620       JLabel label = (JLabel)super.getListCellRendererComponent(
621         list, value, index, isSelected, cellHasFocus
622       );
623       label.setHorizontalAlignment(JLabel.RIGHT);
624       return label;
625     }
626   }
627 
628   /***
629   Subclass of {@link java.util.Observable} which allows <tt>public</tt> access
630   to the setChanged() method.
631   */
632   protected static class PublicChangeObservable extends Observable {
633     /*** @see java.util.Observable#setChanged() */
634     public void setChanged () { super.setChanged(); }
635   }
636 
637   /***
638   Component for displaying a "phrase" (a brief, one or two word String) using 
639   a particular font & a particular color.
640   */  
641   public static class PhraseCanvas extends Canvas {
642     /***
643     Constructs a new PhraseCanvas with the supplied phrase, font, and color.
644     @param phrase phrase to be displayed in this PhraseCanvas
645     @param font Font to use when rendering the phrase
646     @param color Color to use when rendering the phrase
647     */
648     public PhraseCanvas (String phrase, Font font, Color color) {
649       phrase_ = phrase;
650       font_ = font;
651       color_ = color;
652     }
653 
654     /*** @see java.awt.Canvas#paint(java.awt.Graphics) */
655     public void paint (Graphics g) {
656       // Workaround for bug in Font.createGlyphVector(), in review by
657       // Sun with review id 108400.
658       Font dummyFont = new Font(
659         font_.getFamily(), font_.getStyle(), font_.getSize()+1
660       );
661       dummyFont.createGlyphVector(
662         new FontRenderContext(null, antialiasOn_, false),
663         phrase_
664       );
665       
666       GlyphVector glyphVector = font_.createGlyphVector(
667         new FontRenderContext(null, antialiasOn_, false),
668         phrase_
669       );    
670       // Use precedent set by applications like MS Word to place
671       // glyph vector in the canvas:
672       // 1. If the total width of the glyph vector is less than the
673       //   width of the canvas, the glyph vector will be horizontally centered 
674       //   in the canvas; else the glyph vector will be left-aligned
675       // 2. If the total height of the glyph vector is less than the height of
676       //   the canvas, the glyph vector will be vertically centered in the
677       //   canvas; else the glyph vector will be bottom-aligned
678       Rectangle2D logicalBounds = glyphVector.getLogicalBounds();
679       double x;
680       if (logicalBounds.getWidth() < this.getWidth()) {
681         x = (this.getWidth()/2) - (logicalBounds.getWidth()/2);
682       }
683       else { x = 0; }
684       double y;
685       if (logicalBounds.getHeight() < this.getHeight()) {
686         y = (this.getHeight()/2) + (logicalBounds.getHeight()/2);
687       }
688       else { y = this.getHeight(); }
689       g.setColor(color_);
690       Graphics2D g2d = (Graphics2D)g;
691       g2d.drawGlyphVector(glyphVector, (float)x, (float)y);
692     }
693 
694     /*** Returns the phrase to be rendered by this PhraseCanvas.
695     @return phrase to be rendered by this PhraseCanvas */
696     public String getPhrase () { return phrase_; }
697     /*** Sets the phrase to be rendered by this PhraseCanvas.
698     @param phrase new phrase to be rendered by this PhraseCanvas;
699       this new value will be rendered the next time 
700       {@link #paint(java.awt.Graphics)} is called */
701     public void setPhrase (String phrase) { phrase_ = phrase; }
702     protected String phrase_;
703   
704     /*** Returns the font to use when rendering the phrase.
705     @return font to use when rendering the phrase */
706     public Font getFont () { return font_; }
707     /*** Sets the font to use when rendering the phrase.
708     @param font new font to use when rendering the phrase;
709       this new value will be used to render the phrase the next time 
710       {@link #paint(java.awt.Graphics)} is called */
711     public void setFont (Font font) { font_ = font; }
712     protected Font font_;
713 
714     /*** Returns the color to use when rendering the phrase.
715     @return color to use when rendering the phrase */
716     public Color getColor () { return color_; }
717     /*** Sets the color to use when rendering the phrase.
718     @param color new color to use when rendering the phrase;
719       this new value will be used to render the phrase the next time 
720       {@link #paint(java.awt.Graphics)} is called */
721     public void setColor (Color color) { color_ = color; }
722     protected Color color_;
723 
724     /*** Returns true iff anti-aliasing is used when rendering the phrase.
725     @return whether or not anti-aliasing is used when 
726       rendering the phrase */
727     public boolean isAntialiasOn () { return antialiasOn_; }
728     /*** Turn anti-aliasing on or off.
729     @param antialiasOn whether or not to use anti-aliasing when 
730       rendering the phrase this new value will be used to render the phrase 
731       the next time {@link #paint(java.awt.Graphics)} is called */
732     public void setAntialiasOn (boolean antialiasOn) {
733       antialiasOn_ = antialiasOn;
734     }
735     protected boolean antialiasOn_;
736   }
737     
738   /*** Indicates that an invalid font is currently specified */
739   public static class InvalidFontException extends Exception {
740     public InvalidFontException (String msg) { super(msg); }
741   }
742   /*** Indicates that no font family is currently selected */
743   public static class NoFontFamilySelectedException 
744   extends InvalidFontException {
745     public NoFontFamilySelectedException (String msg) { super(msg); }
746   }
747   /*** Indicates that no font style is currently selected */
748   public static class NoFontStyleSelectedException 
749   extends InvalidFontException {
750     public NoFontStyleSelectedException (String msg) { super(msg); }
751   }
752   /*** Indicates that no font size is currently specified */
753   public static class NoFontSizeSpecifiedException 
754   extends InvalidFontException {
755     public NoFontSizeSpecifiedException (String msg) { super(msg); }
756   }
757   /*** Indicates that an invalid font size is currently specified */
758   public static class InvalidFontSizeException extends InvalidFontException {
759     public InvalidFontSizeException (String msg) { super(msg); }
760   }
761   
762 }
763