content: package jcomicdownloader.tools;
  
  import java.awt.BorderLayout;
  import java.awt.Component;
  import java.awt.Container;
  import java.awt.Dimension;
  import java.awt.FlowLayout;
  import java.awt.Font;
  import java.awt.GraphicsEnvironment;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.HeadlessException;
  import java.awt.Insets;
  import java.awt.Window;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.ComponentAdapter;
  import java.awt.event.ComponentEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  import java.io.Serializable;
  
  import javax.swing.AbstractAction;
  import javax.swing.Action;
  import javax.swing.ActionMap;
  import javax.swing.InputMap;
  import javax.swing.JButton;
  import javax.swing.JCheckBox;
  import javax.swing.JComponent;
  import javax.swing.JDialog;
  import javax.swing.JLabel;
  import javax.swing.JList;
  import javax.swing.JOptionPane;
  import javax.swing.JPanel;
  import javax.swing.JScrollPane;
  import javax.swing.KeyStroke;
  import javax.swing.ListSelectionModel;
  import javax.swing.UIManager;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.ChangeListener;
  import javax.swing.event.EventListenerList;
  import javax.swing.event.ListSelectionEvent;
  import javax.swing.event.ListSelectionListener;
  import jcomicdownloader.SetUp;
  
  /**
   * <code>JFontChooser</code> provides a pane of controls designed to allow
   * a user to manipulate and select a font.
   *
   * This class provides three levels of API:
   * <ol>
   * <li>A static convenience method which shows a modal font-chooser
   * dialog and returns the font selected by the user.
   * <li>A static convenience method for creating a font-chooser dialog
   * where <code>ActionListeners</code> can be specified to be invoked when
   * the user presses one of the dialog buttons.
   * <li>The ability to create instances of <code>JFontChooser</code> panes
   * directly (within any container). <code>PropertyChange</code> listeners
   * can be added to detect when the current "font" property changes.
   * </ol>
   * <p>
   *
   * @author Adrian BER
   */
  public class JFontChooser extends JComponent {
  
      /** The list of possible font sizes. */
      private static final Integer[] SIZES = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 };
      /** The list of possible fonts. */
      private static final String[] FONTS = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
      private FontSelectionModel selectionModel;
      private JList fontList;
      private JList sizeList;
      private JCheckBox boldCheckBox;
      private JCheckBox italicCheckBox;
      private JLabel previewLabel;
      /** The preview text, if null the font name will be the preview text. */
      private String previewText;
      /** Listener used to update the font of the selection model. */
      private SelectionUpdater selectionUpdater = new SelectionUpdater();
      /** Listener used to update the font in the components. This should be registered
       * with the selection model. */
      private LabelUpdater labelUpdater = new LabelUpdater();
      /** True if the components are being updated and no event should be generated. */
      private boolean updatingComponents = false;
  
      /** Listener class used to update the font in the components. This should be registered
       * with the selection model. */
      private class LabelUpdater implements ChangeListener {
  
          public void stateChanged( ChangeEvent e ) {
              updateComponents();
          }
      }
  
      /** Listener class used to update the font of the preview label. */
      private class SelectionUpdater implements ChangeListener, ListSelectionListener {
  
          public void stateChanged( ChangeEvent e ) {
              if ( !updatingComponents ) {
                  setFont( buildFont() );
              }
          }
  
          public void valueChanged( ListSelectionEvent e ) {
              if ( !updatingComponents ) {
                  setFont( buildFont() );
              }
          }
      }
  
      /**
       * Shows a modal font-chooser dialog and blocks until the
       * dialog is hidden.  If the user presses the "OK" button, then
       * this method hides/disposes the dialog and returns the selected color.
       * If the user presses the "Cancel" button or closes the dialog without
       * pressing "OK", then this method hides/disposes the dialog and returns
       * <code>null</code>.
       *
       * @param component    the parent <code>Component</code> for the dialog
       * @param title        the String containing the dialog's title
       * @return the selected font or <code>null</code> if the user opted out
       * @exception HeadlessException if GraphicsEnvironment.isHeadless()
       * returns true.
       * @see java.awt.GraphicsEnvironment#isHeadless
       */
      public Font showDialog( Component component, String title ) {
  
          FontTracker ok = new FontTracker( this );
          JDialog dialog = createDialog( component, title, true, ok, null );
          dialog.setSize( 300, 400 );
          dialog.addWindowListener( new FontChooserDialog.Closer() );
          dialog.addComponentListener( new FontChooserDialog.DisposeOnClose() );
  
          dialog.setVisible( true ); // blocks until user brings dialog down...
          dialog.setSize( 300, 400 );
          return ok.getFont();
      }
  
      /**
       * Creates and returns a new dialog containing the specified
       * <code>ColorChooser</code> pane along with "OK", "Cancel", and "Reset"
       * buttons. If the "OK" or "Cancel" buttons are pressed, the dialog is
       * automatically hidden (but not disposed).  If the "Reset"
       * button is pressed, the color-chooser's color will be reset to the
       * font which was set the last time <code>show</code> was invoked on the
       * dialog and the dialog will remain showing.
       *
       * @param c              the parent component for the dialog
       * @param title          the title for the dialog
       * @param modal          a boolean. When true, the remainder of the program
       *                       is inactive until the dialog is closed.
       * @param okListener     the ActionListener invoked when "OK" is pressed
       * @param cancelListener the ActionListener invoked when "Cancel" is pressed
       * @return a new dialog containing the font-chooser pane
       * @exception HeadlessException if GraphicsEnvironment.isHeadless()
       * returns true.
       * @see java.awt.GraphicsEnvironment#isHeadless
       */
      public JDialog createDialog( Component c, String title, boolean modal,
              ActionListener okListener, ActionListener cancelListener ) {
  
          return new FontChooserDialog( c, title, modal, this,
                  okListener, cancelListener );
      }
  
      /**
       * Creates a color chooser pane with an initial font which is the same font
       * as the default font for labels.
       */
      public JFontChooser() {
          this( new DefaultFontSelectionModel() );
      }
  
      /**
       * Creates a font chooser pane with the specified initial font.
       *
       * @param initialFont the initial font set in the chooser
       */
      public JFontChooser( Font initialFont ) {
          this( new DefaultFontSelectionModel( initialFont ) );
      }
  
      /**
       * Creates a font chooser pane with the specified
       * <code>FontSelectionModel</code>.
       *     * @param model the font selection model used by this component
       */
      public JFontChooser( FontSelectionModel model ) {
          selectionModel = model;
          init( model.getSelectedFont() );
          selectionModel.addChangeListener( labelUpdater );
      }
    
      // 因為中文字預設都排在下面，所以若反過來就會排在前面
      private String[] getInverseStrings( String[] strings ) {
          String[] inverseStrings = new String[strings.length];
          
          for ( int i = 0; i < strings.length; i ++ )
              inverseStrings[i] = strings[strings.length-i-1];
          
          return inverseStrings;
      }
  
      private void init( Font font ) {
          setLayout( new GridBagLayout() );
  
          Insets ins = new Insets( 2, 2, 2, 2 );
  
          fontList = new JList( getInverseStrings( FONTS ) );
          fontList.setVisibleRowCount( 10 );
          fontList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
          add( new JScrollPane( fontList ), new GridBagConstraints( 0, 0, 1, 1, 2, 2,
                  GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                  ins, 0, 0 ) );
  
          sizeList = new JList( SIZES );
          ((JLabel) sizeList.getCellRenderer()).setHorizontalAlignment( JLabel.RIGHT );
          sizeList.setVisibleRowCount( 10 );
          sizeList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
          add( new JScrollPane( sizeList ), new GridBagConstraints( 1, 0, 1, 1, 1, 2,
                  GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                  ins, 0, 0 ) );
  
          boldCheckBox = new JCheckBox( "Bold" );
          //add( boldCheckBox, new GridBagConstraints( 0, 1, 2, 1, 1, 0,
          //        GridBagConstraints.WEST, GridBagConstraints.NONE,
          //        ins, 0, 0 ) );
  
          italicCheckBox = new JCheckBox( "Italic" );
          //add( italicCheckBox, new GridBagConstraints( 0, 2, 2, 1, 1, 0,
          //        GridBagConstraints.WEST, GridBagConstraints.NONE,
          //        ins, 0, 0 ) );
  
          previewLabel = new JLabel( Common.getStringUsingDefaultLanguage( "預覽字體區", "preview" ) );
          previewLabel.setHorizontalAlignment( JLabel.CENTER );
          previewLabel.setVerticalAlignment( JLabel.CENTER );
          add( new JScrollPane( previewLabel ), new GridBagConstraints( 0, 3, 2, 1, 1, 1,
                  GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                  ins, 0, 0 ) );
  
          setFont( font == null ? previewLabel.getFont() : font );
  
          fontList.addListSelectionListener( selectionUpdater );
          sizeList.addListSelectionListener( selectionUpdater );
          boldCheckBox.addChangeListener( selectionUpdater );
          italicCheckBox.addChangeListener( selectionUpdater );
      }
  
      private Font buildFont() {
  //        Font labelFont = previewLabel.getFont();
  
          String fontName = (String) fontList.getSelectedValue();
          if ( fontName == null ) {
              fontName = SetUp.getDefaultFontName();
  //            fontName = labelFont.getName();
          }
          Integer sizeInt = (Integer) sizeList.getSelectedValue();
          if ( sizeInt == null ) {
              sizeInt = new Integer( SetUp.getDefaultFontSize() );
          }
  
          // create the font
  //        // first create the font attributes
  //        HashMap map = new HashMap();
  //        map.put(TextAttribute.BACKGROUND, Color.white);
  //        map.put(TextAttribute.FAMILY, fontName);
  //        map.put(TextAttribute.FOREGROUND, Color.black);
  //        map.put(TextAttribute.SIZE , new Float(size));
  //        map.put(TextAttribute.UNDERLINE, italicCheckBox.isSelected() ? TextAttribute.UNDERLINE_LOW_ONE_PIXEL : TextAttribute.UNDERLINE_LOW_TWO_PIXEL);
  //        map.put(TextAttribute.STRIKETHROUGH, italicCheckBox.isSelected() ? TextAttribute.STRIKETHROUGH_ON : Boolean.FALSE);
  //        map.put(TextAttribute.WEIGHT, boldCheckBox.isSelected() ? TextAttribute.WEIGHT_BOLD : TextAttribute.WEIGHT_REGULAR);
  //        map.put(TextAttribute.POSTURE,
  //                italicCheckBox.isSelected() ? TextAttribute.POSTURE_OBLIQUE : TextAttribute.POSTURE_REGULAR);
  //
  //        return new Font(map);
  
          return new Font( fontName,
                  (italicCheckBox.isSelected() ? Font.ITALIC : Font.PLAIN)
                  | (boldCheckBox.isSelected() ? Font.BOLD : Font.PLAIN),
                  sizeInt );
      }
  
      /** Updates the font in the preview component according to the selected values. */
      private void updateComponents() {
          updatingComponents = true;
  
          Font font = getFont();
  
          fontList.setSelectedValue( font.getName(), true );
          sizeList.setSelectedValue( font.getSize(), true );
          boldCheckBox.setSelected( font.isBold() );
          italicCheckBox.setSelected( font.isItalic() );
  
          if ( previewText == null ) {
              previewLabel.setText( font.getName() );
          }
  
          // set the font and fire a property change
          Font oldValue = previewLabel.getFont();
          previewLabel.setFont( font );
          firePropertyChange( "font", oldValue, font );
  
          updatingComponents = false;
      }
  
      /**
       * Returns the data model that handles font selections.
       *
       * @return a FontSelectionModel object
       */
      public FontSelectionModel getSelectionModel() {
          return selectionModel;
      }
  
      /**
       * Set the model containing the selected font.
       *
       * @param newModel   the new FontSelectionModel object
       */
      public void setSelectionModel( FontSelectionModel newModel ) {
          FontSelectionModel oldModel = selectionModel;
          selectionModel = newModel;
          oldModel.removeChangeListener( labelUpdater );
          newModel.addChangeListener( labelUpdater );
          firePropertyChange( "selectionModel", oldModel, newModel );
      }
  
      /**
       * Gets the current font value from the font chooser.
       *
       * @return the current font value of the font chooser
       */
      public Font getFont() {
          return selectionModel.getSelectedFont();
      }
  
      /**
       * Sets the current font of the font chooser to the specified font.
       * The <code>ColorSelectionModel</code> will fire a <code>ChangeEvent</code>
       * @param font the font to be set in the font chooser
       * @see JComponent#addPropertyChangeListener
       */
      public void setFont( Font font ) {
          selectionModel.setSelectedFont( font );
      }
  
      /** Returns the preview text displayed in the preview component.
       * @return the preview text, if null the font name will be displayed
       */
      public String getPreviewText() {
          return previewText;
      }
  
      /** Sets the preview text displayed in the preview component.
       * @param previewText the preview text, if null the font name will be displayed
       */
      public void setPreviewText( String previewText ) {
          this.previewText = previewText;
          previewLabel.setText( "" );
          updateComponents();
      }
  }
  
  
  /*
   * Class which builds a font chooser dialog consisting of
   * a JFontChooser with "Ok", "Cancel", and "Reset" buttons.
   *
   * Note: This needs to be fixed to deal with localization!
   */
  class FontChooserDialog extends JDialog {
  
      private Font initialFont;
      private JFontChooser chooserPane;
  
      public FontChooserDialog( Component c, String title, boolean modal,
              JFontChooser chooserPane,
              ActionListener okListener, ActionListener cancelListener ) {
          super( JOptionPane.getFrameForComponent( c ), title, modal );
          //setResizable(false);
  
          String okString = UIManager.getString( "ColorChooser.okText" );
          String cancelString = UIManager.getString( "ColorChooser.cancelText" );
          String resetString = UIManager.getString( "ColorChooser.resetText" );
  
          /*
           * Create Lower button panel
           */
          JPanel buttonPane = new JPanel();
          buttonPane.setLayout( new FlowLayout( FlowLayout.CENTER ) );
          JButton okButton = new JButton( okString );
          getRootPane().setDefaultButton( okButton );
          okButton.setActionCommand( "OK" );
          if ( okListener != null ) {
              okButton.addActionListener( okListener );
          }
          okButton.addActionListener( new ActionListener() {
  
              public void actionPerformed( ActionEvent e ) {
                  setVisible( false );
              }
          } );
          buttonPane.add( okButton );
  
          JButton cancelButton = new JButton( cancelString );
  
          // The following few lines are used to register esc to close the dialog
          Action cancelKeyAction = new AbstractAction() {
  
              public void actionPerformed( ActionEvent e ) {
                  // todo make it in 1.3
  //                ActionListener[] listeners
  //                        = ((AbstractButton) e.getSource()).getActionListeners();
  //                for (int i = 0; i < listeners.length; i++) {
  //                    listeners[i].actionPerformed(e);
  //                }
              }
          };
          KeyStroke cancelKeyStroke = KeyStroke.getKeyStroke( (char) KeyEvent.VK_ESCAPE );
          InputMap inputMap = cancelButton.getInputMap( JComponent.WHEN_IN_FOCUSED_WINDOW );
          ActionMap actionMap = cancelButton.getActionMap();
          if ( inputMap != null && actionMap != null ) {
              inputMap.put( cancelKeyStroke, "cancel" );
              actionMap.put( "cancel", cancelKeyAction );
          }
          // end esc handling
  
          cancelButton.setActionCommand( "cancel" );
          if ( cancelListener != null ) {
              cancelButton.addActionListener( cancelListener );
          }
          cancelButton.addActionListener( new ActionListener() {
  
              public void actionPerformed( ActionEvent e ) {
                  setVisible( false );
              }
          } );
          buttonPane.add( cancelButton );
  
          JButton resetButton = new JButton( resetString );
          resetButton.addActionListener( new ActionListener() {
  
              public void actionPerformed( ActionEvent e ) {
                  reset();
              }
          } );
          int mnemonic = UIManager.getInt( "ColorChooser.resetMnemonic" );
          if ( mnemonic != -1 ) {
              resetButton.setMnemonic( mnemonic );
          }
          buttonPane.add( resetButton );
  
  
          // initialiase the content pane
          this.chooserPane = chooserPane;
  
          Container contentPane = getContentPane();
          contentPane.setLayout( new BorderLayout() );
          contentPane.add( chooserPane, BorderLayout.CENTER );
  
          contentPane.add( buttonPane, BorderLayout.SOUTH );
  
          pack();
          setLocationRelativeTo( c );
      }
  
      public void setVisible( boolean visible ) {
          if ( visible ) {
              initialFont = chooserPane.getFont();
          }
          super.setVisible( visible );
      }
  
      public void reset() {
          chooserPane.setFont( initialFont );
      }
  
      static class Closer extends WindowAdapter implements Serializable {
  
          public void windowClosing( WindowEvent e ) {
              Window w = e.getWindow();
              w.setVisible( false );
          }
      }
  
      static class DisposeOnClose extends ComponentAdapter implements Serializable {
  
          public void componentHidden( ComponentEvent e ) {
              Window w = (Window) e.getComponent();
              w.dispose();
          }
      }
  }
  
  class FontTracker implements ActionListener, Serializable {
  
      JFontChooser chooser;
      Font color;
  
      public FontTracker( JFontChooser c ) {
          chooser = c;
  
      }
  
      public void actionPerformed( ActionEvent e ) {
          color = chooser.getFont();
      }
  
      public Font getFont() {
          return color;
      }
  }
  
  /**
   * A generic implementation of <code>{@link FontSelectionModel}</code>.
   *
   * @author Adrian BER
   */
  class DefaultFontSelectionModel implements FontSelectionModel {
  
      /** The default selected font. */
      private static final Font DEFAULT_INITIAL_FONT = new Font( new Font( null ).getName(), Font.PLAIN, 18 );
      /** The selected font. */
      private Font selectedFont;
      /** The change listeners notified by a change in this model. */
      private EventListenerList listeners = new EventListenerList();
  
      /**
       * Creates a <code>DefaultFontSelectionModel</code> with the
       * current font set to <code>Dialog, 12</code>.  This is
       * the default constructor.
       */
      public DefaultFontSelectionModel() {
          this( DEFAULT_INITIAL_FONT );
      }
  
      /**
       * Creates a <code>DefaultFontSelectionModel</code> with the
       * current font set to <code>font</code>, which should be
       * non-<code>null</code>.  Note that setting the font to
       * <code>null</code> is undefined and may have unpredictable
       * results.
       *
       * @param selectedFont the new <code>Font</code>
       */
      public DefaultFontSelectionModel( Font selectedFont ) {
          if ( selectedFont == null ) {
              selectedFont = DEFAULT_INITIAL_FONT;
          }
          this.selectedFont = selectedFont;
      }
  
      public Font getSelectedFont() {
          return selectedFont;
      }
  
      public void setSelectedFont( Font selectedFont ) {
          if ( selectedFont != null ) {
              this.selectedFont = selectedFont;
              fireChangeListeners();
  
          }
      }
  
      public void addChangeListener( ChangeListener listener ) {
          listeners.add( ChangeListener.class, listener );
      }
  
      public void removeChangeListener( ChangeListener listener ) {
          listeners.remove( ChangeListener.class, listener );
      }
  
      /** Fires the listeners registered with this model. */
      protected void fireChangeListeners() {
          ChangeEvent ev = new ChangeEvent( this );
          Object[] l = listeners.getListeners( ChangeListener.class );
          for ( Object listener : l ) {
              ((ChangeListener) listener).stateChanged( ev );
          }
      }
  
  }
  
  /**
   * A model that supports selecting a <code>Font</code>.
   *
   * @author Adrian BER
   *
   * @see java.awt.Font
   */
  interface FontSelectionModel {
  
      /**
       * Returns the selected <code>Font</code> which should be
       * non-<code>null</code>.
       *
       * @return  the selected <code>Font</code>
       * @see     #setSelectedFont
       */
      Font getSelectedFont();
  
      /**
       * Sets the selected font to <code>font</code>.
       * Note that setting the font to <code>null</code>
       * is undefined and may have unpredictable results.
       * This method fires a state changed event if it sets the
       * current font to a new non-<code>null</code> font.
       *
       * @param font the new <code>Font</code>
       * @see   #getSelectedFont
       * @see   #addChangeListener
       */
      void setSelectedFont( Font font );
  
      /**
       * Adds <code>listener</code> as a listener to changes in the model.
       * @param listener the <code>ChangeListener</code> to be added
       */
      void addChangeListener( ChangeListener listener );
  
      /**
       * Removes <code>listener</code> as a listener to changes in the model.
       * @param listener the <code>ChangeListener</code> to be removed
       */
      void removeChangeListener( ChangeListener listener );
  
  }