/*
 * COPYRIGHT (c) 1998-1999 Jan-Henrik Haukeland, http://www.tildeslash.com/
 */

package ts.config;


import java.awt.*;
import java.util.*;
import java.io.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.plaf.*;
import javax.swing.plaf.metal.MetalTabbedPaneUI;

import ts.biff.Util;
import ts.biff.MailUtil;
import ts.biff.TerminateController;
import ts.config.ConfigException;
import ts.ui.GBLPanel; 


/**
 * A dialog for changing configuration properties for the application.
 * The class uses a Config object as the model to store changes.
 * Initital values is read from Config. Using the singleton pattern to
 * have only one instance of this class.
 *
 * <P>
 * <H3>Open points:</H3>
 *  The class is only partly localized
 *
 * @author  Jan-Henrik Haukeland &lt;hauk@tildeslash.com&gt;
 * @version $Id: Configure.java,v 1.27 2001/08/13 12:48:30 hauk Exp $
 *
 * @see Config
 */
public final class Configure extends JDialog implements ActionListener
{

  /** 
   * Hashtable for storing temporary changes. The key/values in
   * this table will be merged into Config if the user chooses 
   * to save changes
   */
  protected static Hashtable myLocalModel= new Hashtable();

  /** The model object to update if the user chooses to save changes */
  protected static Config myModel= Config.getInstance();


  /** Array holding TabbedPane flips- and tooltip text */
  private static final String[][] TABS= { 
      {"General",         "Set general parameters"},
      {"Fields",          "Select Mail header fields to display"},
      {"User interface",  "Set properties for the user interface"},
      {"Fetch mail",      "Select and setup protocol for fetching mail"},
      {"Send  mail",      "Select and setup protocol for sending mail"},
      {"Filter mail",     "Setup filter for mail your not interested in"},
      {"Timeout",         "Set optional timeout for the Biff frame"},
  };

  /** If pressed close the dialog and update Config if any changes */
  private JButton  mySaveButton; 

  /** 
   * If pressed close the dialog and discard any changes 
   * (will not update Config)
   */
  private JButton  myCancelButton; 
  
  /** Dialog width */
  private static final int    WIDTH=  570;

  /** Dialog height */
  private static final int    HEIGHT= 400;


  /** True if any property values was changed */
  private static boolean isChanged= false;


  /** self */
  private static Configure instance;



  /**
   * Make this a stand-alone also
   */
  public static void main(String a[])
  {
    Util.setUniversalFont(Font.decode("SansSerif-normal-12"));

    try {
      File conf= Util.getUserConfigFile();
      if ( conf == null )
	  Config.getInstance().save(Constant.USER_CONTROL_FILE);
      else
	  Config.getInstance().load(conf);
    } catch(Exception e) {
      Util.showError("Error: "+e.getMessage());
      System.out.println("\nTrace:");
      e.printStackTrace();
      System.exit(1);
    }

    Configure c= Configure.getInstance();
    c.showConfigure();
    c.hideConfigure();
    System.exit(0);
  }



  // ------------------------ Constructors -------------------------- //



  /**
   * Instantiate the Dialog
   */
  private Configure()
  {
    super(new JFrame(), Constant.CONF_DIALOG_TITLE, true);
    buildUI();
    setSize(WIDTH,HEIGHT); 
    Util.center((Window)this);

    this.addWindowListener( new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
	doCancel();
	hideConfigure();
      }
    });
  }    



  /**
   * Create a modal Configure with a default Frame
   * @return the Configure singelton
   */
  public static Configure getInstance()
  {
    if ( instance == null )
	instance= new Configure();
    return instance;
  }


  // ---------------------- Public methods -------------------------- //


  /**
   * Show the dialog
   */
  public void showConfigure()
  {
    this.show();
  }


  /**
   * Hide the dialog
   */
  public void hideConfigure()
  {
    this.setVisible(false);
    this.dispose();
  }


  /**
   * Returns true if any property values was changed
   * @return boolean, indicating if any property values was changed
   */
  public boolean isChanged()
  {
    return isChanged;
  }



  /**
   * Called if the save or cancel button is clicked. Properties are
   * saved iff the Save button receives an ActionEvent.
   * @param e An ActionEvent
   */
  public void actionPerformed(ActionEvent e)
  {
    if ( e.getSource() == mySaveButton ) {
      saveChanges();
    } else {
      doCancel();
    }
    hideConfigure();
  }


  // --------------------- Private methods ------------------------- //


  /**
   * Reset after cancel
   */
  private void doCancel()
  {
    myLocalModel.clear();
    isChanged= false;
  }



  /**   
   * If myLocalModel has any values then merge in key/values from
   * myLocalModel with myModel (i.e. Config) and save myModel as the
   * users local control file.  Key/values in myLocalModel will replace
   * any corsponding key/values in myModel. If an error occurs saving 
   * the control file, this method will exit the program with an 
   * appropriate error message.  
   */
  private void saveChanges()
  {
    try {
      
      if (  ! myLocalModel.isEmpty()  ) {
	for (Enumeration e= myLocalModel.keys(); e.hasMoreElements();) {
	  String key=   (String)e.nextElement();
	  String value= (String)myLocalModel.get(key);
	  myModel.set(key,value);
	}
	
	myModel.save(Constant.USER_CONTROL_FILE);
	myLocalModel.clear();
	isChanged= true;
      }
      else {
	isChanged= false;
      }

    } catch(ConfigException ce) {

      String msg= "Error: Could not save local controlfile "+
	           Constant.USER_CONTROL_FILE;

      Util.showError(msg);

      System.err.println(msg);

      TerminateController.exit();
    }
  }




  /**
   * Build this Dialogs's user interface
   */
  private void buildUI() 
  {
    TabbedPanel myTabs= new TabbedPanel();
    int i=0;
    myTabs.addTab(TABS[i][0],null,createGeneralPane(TABS[i][1]),TABS[i++][1]);
    myTabs.addTab(TABS[i][0],null,createFieldsPane(TABS[i][1]),TABS[i++][1]);
    myTabs.addTab(TABS[i][0],null,createUIPane(TABS[i][1]),TABS[i++][1]);
    myTabs.addTab(TABS[i][0],null,createGetMailPane(TABS[i][1]),TABS[i++][1]);
    myTabs.addTab(TABS[i][0],null,createSendMailPane(TABS[i][1]),TABS[i++][1]);
    myTabs.addTab(TABS[i][0],null,createFilterPane(TABS[i][1]),TABS[i++][1]);
    myTabs.addTab(TABS[i][0],null,createTimeOutPane(TABS[i][1]),TABS[i++][1]);

    JPanel buttonpanel= new JPanel();
    mySaveButton=       new JButton("Save");
    myCancelButton=     new JButton("Cancel");

    mySaveButton.setToolTipText("Save changes and close dialog");
    myCancelButton.setToolTipText("Discard changes");

    mySaveButton.addActionListener(this);
    myCancelButton.addActionListener(this);
    
    myCancelButton.requestFocus();

    buttonpanel.add(mySaveButton);
    buttonpanel.add(myCancelButton);

    JPanel c= new JPanel(true) {
      public Insets getInsets()  {
	return new Insets(30,30,30,30);
      }
    };
    
    c.setLayout(new BorderLayout());
    c.add("Center", myTabs);
    c.add("South",  buttonpanel);
    setContentPane(c);
  }



  /**
   * Create a JPanel with controllers for setting General application
   * properties.
   * @param title an Optional Title for the panel
   * @return A JPanel for setting General application properties
   */
  private JPanel createGeneralPane(String title)
  {
    JPanel general= new JPanel(new GridLayout(0,1,10,10)); 
    Util.setMargin(general,10);

    general.add(createUpdatePane());
    general.add(createGeneralCheckBoxPane());

    return general;
  }


  /**
   * Create a JPanel with a slider for updating how often
   * to check for new mail. Values are in minutes.
   * @return a Panel with a JSlider
   */
  private JPanel createUpdatePane()
  {
    class UpdatePanel extends JPanel implements ChangeListener
    {
      private int confupdate= ( Config.getint("update") / 60 );
      
      public UpdatePanel()
      {
	super(new BorderLayout());
	buildUI();
      }
    
      public void stateChanged(ChangeEvent e) 
      {
	JSlider source= (JSlider)e.getSource();
	if (!source.getValueIsAdjusting()) {
	  int minutes= (int)((JSlider)e.getSource()).getValue();
	  // Dont allow 0 as a value
	  if ( minutes == 0 ) {
	    source.setValue(1);
	    return;
	  }
	  int seconds= minutes*60; 
	  setChanges("update", seconds+"");
	}
      }

      private void buildUI()
      {
	JSlider update= new JSlider(JSlider.HORIZONTAL, 0, 30, confupdate); 
      
	update.setMajorTickSpacing(5);
	update.setMinorTickSpacing(1);
	update.setPaintTicks(true);
	update.setPaintLabels(true);
	update.setSnapToTicks(true);
      
	update.addChangeListener(this);

	Util.setTitleBorder(this,
				 "How often to check for new mail (minutes)");

	this.add("Center", update);
      }
    }
    return new UpdatePanel();
  }




  /**
   * Create a JPanel with check boxes for beep, for 
   * hiding table headers, and for numerating each
   * mail record
   * @return a Panel with Checkboxes
   */
  private JPanel createGeneralCheckBoxPane()
  {
    class CheckBoxPanel extends JPanel implements ItemListener
    {
      private JCheckBox beep, hide, numerate;
      
      public CheckBoxPanel()
      {
	super(new GridLayout(0,1));
	buildUI();
      }
    
      public void itemStateChanged(ItemEvent e) 
      {
	Object source = e.getItemSelectable();
	boolean selected= 
	    e.getStateChange() == ItemEvent.SELECTED?true:false;
	if ( source == beep ) {
	  setChanges("beep", selected+"");
	} else if ( source == hide ) {
	  setChanges("mail.table.hidetitles", selected+"");
	} else if ( source == numerate ) {
	  setChanges("mail.table.numerate", selected+"");
	}
      }

      private void buildUI()
      {
	boolean beepvalue= Config.getboolean("beep");
	boolean hidevalue= Config.getboolean("mail.table.hidetitles");
	boolean numeratevalue= Config.getboolean("mail.table.numerate");
	
	beep= new JCheckBox(Config.get("beep.name"), beepvalue);
	hide= new JCheckBox(Config.get("mail.table.hidetitles.name"),
			    hidevalue);
	numerate= new JCheckBox(Config.get("mail.table.numerate.name"), 
				numeratevalue);
  	this.add(beep);
	this.add(hide);
	this.add(numerate);
	
	beep.addItemListener(this);
	hide.addItemListener(this);
	numerate.addItemListener(this);
	
	Util.setTitleBorder(this, "General options");
      }
    }
    
    return new CheckBoxPanel();
  }




  /**
   * Create a JPanel with a list of Mail header fields to choose from
   * @param An optional border title
   * @return a Panel with a List of Mailheader fields
   */
  private JPanel createFieldsPane(String title)
  {
    class FieldsPanel extends JPanel implements ListSelectionListener,
	KeyListener
    {
      private String title;

      public FieldsPanel(String title)
      {
	super(new GridLayout(0,1,10,10));
	this.title= title;
	buildUI();
      }
      
      public void valueChanged(ListSelectionEvent e) {
	
	JList l= (JList)e.getSource();
	
	if ( ! l.isSelectionEmpty()) {
	  StringBuffer sb= new StringBuffer(); 
	  Object values[]= l.getSelectedValues();
	  for ( int i= 0; i<values.length; i++ ) {
	    sb.append((String)values[i]+" ");
	  }
	  setChanges("mail.table.wanted.fields", sb.toString());
	}
      }

      public void keyTyped(KeyEvent e) { }
      public void keyPressed(KeyEvent e) { }
      public void keyReleased(KeyEvent e) 
      {
	if ( e.isActionKey() ) return;
	JTextField m= (JTextField)e.getSource();
	setChanges("maxmailbodylines", m.getText());
      }



      private void buildUI()
      {
	JPanel panel= new JPanel(new BorderLayout());
	JList headers= new JList(Constant.MAIL_HEADERS);
	NumberField maxChar= new NumberField(5);
        
	headers.
	    setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    

	maxChar.setText(Config.get("maxmailbodylines"));
	headers.setSelectedIndices(getConfigSelected());
    
	
	JScrollPane scroll= new JScrollPane(headers,
					    ScrollPaneConstants.
					    VERTICAL_SCROLLBAR_AS_NEEDED,
					    ScrollPaneConstants.
					    HORIZONTAL_SCROLLBAR_AS_NEEDED);

	JPanel p= new JPanel() {
	  public Insets getInsets()  {
	    return new Insets(20,0,0,0);
	  }
	};

	p.add(new JLabel("Maximum number of lines to read "+
			 "from mail body: "));
	p.add(maxChar);

	panel.add("Center",scroll);
	panel.add("South", p);

	this.add(panel);

	headers.addListSelectionListener(this);
	maxChar.addKeyListener(this);
	
	Util.setTitleBorder(panel, title +" (Ctrl+click to select many)");
      }

      private int[] getConfigSelected()
      {
	Object[] headervalues=  MailUtil.getSelectedHeaderFields();
	int[] indices= new int[headervalues.length];
	for ( int i= 0; i<Constant.MAIL_HEADERS.length; i++ ) {
	  for ( int j= 0; j<headervalues.length; j++ ) {
	    if ( Constant.MAIL_HEADERS[i].equals(headervalues[j]) ) {
	      indices[j]= i;
	    }
	  }
	}
	return indices;
      }
      
    }
    
    return new FieldsPanel(title);
  }




  /**
   * Returns a JPanel with controls for setting the user interface
   * properties. E.g. Table colors, font and window size.
   * @param An optional border title
   * @return a Panel with controls for changing the user interface
   */
  private JPanel createUIPane(String title)
  {
    JPanel panel= new JPanel(new BorderLayout());
    
    Box b= Box.createVerticalBox();
    b.add(createColorPane());
    b.add(createWinSizePane());
    
    Box h= Box.createHorizontalBox();
    
    h.add(b);
    h.add(Box.createHorizontalStrut(10));
    h.add(createFontPane());
    
    panel.add("Center",h);

    return panel;
  }



  
  /**
   * Returns a JPanel with controls for setting Table colors
   * @return a Panel with controls for setting Table colors
   */
  private JPanel createColorPane()
  {
    class ColorPanel extends JPanel implements  ActionListener
    {
      private JButton fgcolor, bgcolor;
      private Color conffg= Config.getColor("mail.table.fgcolor");
      private Color confbg= Config.getColor("mail.table.bgcolor");

      public ColorPanel()
      {
	super(new BorderLayout());
	buildUI();
      }

      
      public void actionPerformed(ActionEvent e) 
      {
	Object source= e.getSource();
	if ( source == fgcolor ) {
	  Color f= JColorChooser.
	      showDialog(Configure.this,
			 "Table text color",
			 fgcolor.getBackground());
	  if ( f != null ) {
	    fgcolor.setBackground(f);
	    setChanges("mail.table.fgcolor",Util.colorEncode(f));
	  }
	} else if ( source == bgcolor ) {
	  Color b= JColorChooser.
	      showDialog(Configure.this,
			 "Table background color",
			 bgcolor.getBackground());
	  if ( b != null ) {
	    bgcolor.setBackground(b);
	    setChanges("mail.table.bgcolor",Util.colorEncode(b));
	  }
	}
      }

      
      private void buildUI()
      {
	fgcolor= new JButton();
	fgcolor.setToolTipText("Click to change text color");
	bgcolor= new JButton();
	bgcolor.setToolTipText("Click to change background color");
    
	fgcolor.setBackground(conffg);
	bgcolor.setBackground(confbg);
    
	fgcolor.setBorder(new CompoundBorder(new LineBorder(Color.black, 1),
					     new EmptyBorder(0,0,10,20)));

	bgcolor.setBorder(new CompoundBorder(new LineBorder(Color.black, 1),
					     new EmptyBorder(0,0,10,20)));

	JLabel fglabel= 
	    new JLabel(Config.get("mail.table.fgcolor.name"));
	JLabel bglabel= 
	    new JLabel(Config.get("mail.table.bgcolor.name"));
    
	fglabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
	bglabel.setAlignmentX(Component.RIGHT_ALIGNMENT);

	fgcolor.addActionListener(this);
	bgcolor.addActionListener(this);

	Box l=   Box.createVerticalBox(); 
	Box b=   Box.createVerticalBox();
	Box box= Box.createHorizontalBox();
	
	l.add(fglabel);l.add(Box.createVerticalStrut(5));
	l.add(bglabel); 
	b.add(fgcolor); b.add(Box.createVerticalStrut(5));
	b.add(bgcolor); 
    
	box.add(l); 
	box.add(Box.createHorizontalStrut(10));
	box.add(b);

	this.add("North",box);

	Util.setTitleBorder(this, "Table colors");
      }
      
    }

    return new ColorPanel();
  }




  
  /**
   * Returns a JPanel with controls for setting Table fonts.
   * @return a Panel with controls for setting Table fonts
   */
  private JPanel createFontPane()
  {
    class FontPanel extends JPanel implements ActionListener
    {
      private final String[] fonts= 
        {"Serif", "SansSerif", "Monospaced", "Dialog", "DialogInput"};
      private final String[] size= {"8", "10", "12", "14"}; 

      private String propertyName;
      private String title;
      private JComboBox fontList, sizeList;
      private JLabel example;
      private Font configFont;


      /**
       * @param prop Font property name to update
       * @param title Title for this panel
       */
      public FontPanel(String prop, String title)
      {
	super(new BorderLayout());
	propertyName= prop;
	this.title= title;
	buildUI();
      }
      
      public void actionPerformed(ActionEvent e) 
      {
	JComboBox source= (JComboBox)e.getSource();
	String selected= (String)source.getSelectedItem();
	String font= null;
	if ( source == fontList ) {
	  font= selected+"-plain-"+size[sizeList.getSelectedIndex()];
	} else if ( source == sizeList ) {
	  font= fonts[fontList.getSelectedIndex()]+"-plain-"+selected;
	}

	setChanges(propertyName, font);
	example.setFont(Font.decode(font));

      }
    

      private void buildUI()
      {
	configFont= Config.getFont(propertyName);

	fontList= new JComboBox(fonts);
	sizeList= new JComboBox(size);
	example=  new JLabel("Current font");

	fontList.setToolTipText("Select a font-family");
	sizeList.setToolTipText("Select a font-size");

	example.setFont(configFont);
	example.setForeground(Color.black);
	Util.setMargin(example,2);

	fontList.setSelectedItem(Util.fontParts(configFont)[0]);
	sizeList.setSelectedItem(Util.fontParts(configFont)[2]);

	JComboBox c[]= {fontList,sizeList};
	for (int i= 0; i<c.length; i++) {
	  c[i].setEditable(false);
	  c[i].addActionListener(this);
	}

	Box combos= Box.createHorizontalBox();
	combos.add(new JLabel("Font:"));
	combos.add(Box.createHorizontalStrut(5));
	combos.add(fontList);
	combos.add(Box.createHorizontalStrut(5));
	combos.add(new JLabel("Size:"));
	combos.add(Box.createHorizontalStrut(5));
	combos.add(sizeList);
	this.add("North",combos);

	JPanel e= new JPanel(new BorderLayout()); 
	e.add(example);
	e.setBackground(Color.white);
	this.add("South", e);

	Util.setTitleBorder(this, title);
     }
     
    }

    JPanel panel= new JPanel(new GridLayout(0,1,5,5));
    panel.add(new FontPanel("mail.table.font", "Table font"));
    panel.add(new FontPanel("mail.table.header.font", "Table header font"));
    
    return panel ;
  }





  /**
   * Create a JPanel with sliders for selecting the Biff window
   * width and height.
   * @return a Panel with controls for setting the window size
   */
  private JPanel createWinSizePane()
  {
    class WinSize extends JPanel implements ChangeListener
    {
      private int confwidth=  Config.getint("window.width");
      private int confheight= Config.getint("window.height");

      private JSlider height, width;
      private JLabel  info;
      
      public WinSize()
      {
	super(new BorderLayout());
	buildUI();
      }
    
      
      public void stateChanged(ChangeEvent e) 
      {
	JSlider source= (JSlider)e.getSource();
	int value= (int)((JSlider)e.getSource()).getValue();
	
	if ( source == width ) {
	  info.setText("Size: "+ value + " x "+ height.getValue());
	  if (!source.getValueIsAdjusting())
	      setChanges("window.width", value+"");
	}
	if ( source == height ) {
	  info.setText("Size: "+ width.getValue() + " x "+ value);
	  if (!source.getValueIsAdjusting())
	      setChanges("window.height", value+"");
	}
      }

      private void buildUI()
      {
	width=  new JSlider(JSlider.HORIZONTAL, 100, 1280, confwidth); 
	height= new JSlider(JSlider.VERTICAL,  10,  1024,  confheight); 
	info=   new JLabel("Size: "+ confwidth + " x "+ confheight);
	
	info.setAlignmentX(Component.CENTER_ALIGNMENT);
	info.setFont(Font.decode("Serif-plain-14"));

	JSlider s[]= {width,height};
	for (int i= 0; i<s.length; i++) {
	  s[i].setMajorTickSpacing(100);
	  s[i].setPaintTicks(true);
	  s[i].setSnapToTicks(false);
	  s[i].addChangeListener(this);
	}
      
	width.setToolTipText("Select Biff window width");
	height.setToolTipText("Select Biff window height");
	
	Util.setTitleBorder(this,
				 "Biff frame width and height");

	JPanel p= new JPanel(new BorderLayout());
	p.add("Center", info);
	p.add("South", width);
	p.add("East", height);
	this.add("Center", p);
      }
    }
    return new WinSize();
  }





  /**
   * Create a JPanel with controls for selecting a fetch protocol
   * and for configuring that protocol
   * @return a Panel with controls for setting the window size
   */
  private JPanel createGetMailPane(String title)
  {
    class GetMailPanel extends JPanel implements ActionListener
    {
      private JPanel mbox, pop;
      private String title;
      private JComboBox protoList;
      private String confproto= Config.get("fetch.protocol");

      public GetMailPanel(String title)
      {
	super(new BorderLayout());
	this.title= title;
	buildUI();
      }

      public void actionPerformed(ActionEvent e) 
      {
	JComboBox source= (JComboBox)e.getSource();
	int index= (int)source.getSelectedIndex();
	String selected= (String)source.getSelectedItem();

	if ( selected.equals(Constant.MBOX_PROTO) ) {
	  pop.setVisible(false);
	  mbox.setVisible(true);
	  this.add("Center", mbox);
	} else if ( selected.equals(Constant.POP3_PROTO) ) {
	  mbox.setVisible(false);
	  pop.setVisible(true);
	  this.add("Center", pop);
	}	

	this.validate();
	
	setChanges("fetch.protocol", Constant.FETCH_PROTOCOLS[index]);
      }


      private void buildUI()
      {
	protoList= new JComboBox(Constant.FETCH_PROTOCOLS);
	mbox= createMboxPane();
	pop= createPopPane();
	JLabel protolabel= new JLabel("Select fetch mail protocol: ");

	protoList.setSelectedItem(confproto);

	protolabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
	protoList.setAlignmentX(Component.LEFT_ALIGNMENT);

	Box b= Box.createHorizontalBox();
	b.add(protolabel);
	b.add(protoList);
	b.add(Box.createHorizontalGlue());

	this.add("North", b);
	
	if ( confproto.equals(Constant.POP3_PROTO) ) 
	  this.add("Center", pop);	    
	else if ( confproto.equals(Constant.MBOX_PROTO) )
	    this.add("Center", mbox);	    
	    
	protoList.addActionListener(this);
	
	this.setBorder(new EmptyBorder(20,0,10,0));
      }

    }

    return new GetMailPanel(title);
  }



  
  /**
   * Create a JPanel with controls for configuring the unix mbox protocol
   * @return a Panel with controls to configure the unix mbox 
   */
  private JPanel createMboxPane()
  {
    class MboxPanel extends JPanel implements KeyListener
    {
      private JTextField spool;
      private String confspool= Config.get("mbox.spool");
      
      public MboxPanel()
      {
	super(new BorderLayout());
	buildUI();
      }

      public void keyTyped(KeyEvent e) { }
      public void keyPressed(KeyEvent e) { }
      public void keyReleased(KeyEvent e) 
      {
	if ( e.isActionKey() ) return;
	setChanges("mbox.spool", spool.getText());
      }


      private void buildUI()
      {
	spool= new JTextField(confspool, 25);
	JLabel label= new JLabel("Mail spool directory: ");

	GBLPanel p= new GBLPanel();
	GBLPanel.constraints.fill= GridBagConstraints.HORIZONTAL;
	p.add(label,   0,0,1,1,GBLPanel.EAST);
	p.add(spool,   1,0,1,1,GBLPanel.WEST);

	this.add("Center", p);
	
	spool.addKeyListener(this);
      }

    }
    
    return new MboxPanel();
    
  }




  /**
   * Create a JPanel with controls for configuring the pop protocol
   * @return a Panel with controls to configure pop 
   */
  private JPanel createPopPane()
  {
    class PopPanel extends JPanel implements KeyListener  
    {
      private JTextField server, port, username, password;
      
      private String confserver= Config.get("pop.server");
      private String confport= Config.get("pop.server.port");
      private String confuname= Config.get("pop.uname");
      private String confpassword= Config.get("pop.password");
      
      public PopPanel()
      {
	super(new BorderLayout());
	buildUI();
      }

      public void keyTyped(KeyEvent e){ }
      public void keyPressed(KeyEvent e) { }
      public void keyReleased(KeyEvent e) 
      {
	if ( e.isActionKey() ) return;

	JTextField t= (JTextField)e.getSource();
	if ( t == server )
	    setChanges("pop.server",t.getText());
	else if ( t == port ) 
	    setChanges("pop.server.port",t.getText());
	else if ( t == username ) 
	    setChanges("pop.uname",t.getText());
	else if ( t == password ) 
	    setChanges("pop.password",t.getText());
      }

      
      private void buildUI()
      {
	server=   new JTextField(confserver,15);
	port=     new NumberField(confport,5);
	username= new JTextField(confuname,15);
	password= new JPasswordField(confpassword,15);

	JLabel serverlabel= new JLabel("Pop server hostname: ");
	JLabel portlabel= new JLabel("Port number: ");
	JLabel unamelabel= new JLabel("Username: ");
	JLabel passwordlabel= new JLabel("Password: ");

	GBLPanel p= new GBLPanel();
	p.add(serverlabel,   0,0,1,1,GBLPanel.EAST);
	p.add(server,        1,0,1,1,GBLPanel.WEST);
	p.add(portlabel,     0,1,1,1,GBLPanel.EAST);
	p.add(port,          1,1,1,1,GBLPanel.WEST);

	p.add(unamelabel,    0,2,1,1,GBLPanel.EAST);
	p.add(username,      1,2,1,1,GBLPanel.WEST);
	p.add(passwordlabel, 0,3,1,1,GBLPanel.EAST);
	p.add(password,      1,3,1,1,GBLPanel.WEST);

	Box b= Box.createHorizontalBox();
	b.add(p);
	
	this.add("Center", b);
	
	server.addKeyListener(this);
	port.addKeyListener(this);
	username.addKeyListener(this);
	password.addKeyListener(this);
      }

    }

    return new PopPanel();

  }



  /**
   * Returns a JPanel with controls for selecting and configuring
   * a sendmail protocol. At the moment only the smtp protocol is 
   * supported but we make this general.
   * @param An optional border title
   * @return a Panel with controls for selecting sendmail protocol
   */
  private JPanel createSendMailPane(String title)
  {
    class SendMailPanel extends JPanel implements ActionListener
    {
      private JPanel smtp;
      private String title;
      private JComboBox protoList;
      private String confproto= Config.get("send.protocol");

      public SendMailPanel(String title)
      {
	super(new BorderLayout());
	this.title= title;
	buildUI();
      }

      public void actionPerformed(ActionEvent e) 
      {
	JComboBox source= (JComboBox)e.getSource();
	int index= (int)source.getSelectedIndex();
	String selected= (String)source.getSelectedItem();

	if ( selected.equals(Constant.SMTP_PROTO) ) {
	  // Hide something else
	  smtp.setVisible(true);
	  this.add("Center", smtp);
	  this.validate();
	} //else if ( e.g. something else )
	
	setChanges("send.protocol", Constant.SEND_PROTOCOLS[index]);
      }


      private void buildUI()
      {
	protoList= new JComboBox(Constant.SEND_PROTOCOLS);
	smtp= createSmtpPane();

	JLabel protolabel= new JLabel("Select send mail protocol: ");

	protoList.setSelectedItem(confproto);
	protolabel.setAlignmentX(Component.RIGHT_ALIGNMENT);
	protoList.setAlignmentX(Component.LEFT_ALIGNMENT);

	Box b= Box.createHorizontalBox();
	b.add(protolabel);
	b.add(protoList);
	b.add(Box.createHorizontalGlue());

	this.add("North", b);
	
	if ( confproto.equals(Constant.SMTP_PROTO) )
	    this.add("Center", smtp);	    
	// else if ( e.g. something else )
	    
	protoList.addActionListener(this);
	
	this.setBorder(new EmptyBorder(20,0,10,0));
      }

    }

    return new SendMailPanel(title);
  }




  /**
   * Create a JPanel with controls for configuring the smtp protocol
   * @return a Panel with controls to configure smtp 
   */
  private JPanel createSmtpPane()
  {
    // Should really make this a general class so that PopPanel
    // could subclass this.
    class SmtpPanel extends JPanel implements KeyListener  
    {
      private JTextField server, port;
      
      private String confserver= Config.get("smtp.server");
      private String confport= Config.get("smtp.server.port");

      
      public SmtpPanel()
      {
	super(new BorderLayout());
	buildUI();
      }

      public void keyTyped(KeyEvent e){ }
      public void keyPressed(KeyEvent e) { }
      public void keyReleased(KeyEvent e) 
      {
	JTextField t= (JTextField)e.getSource();
	if ( t == server )
	    setChanges("smtp.server",t.getText());
	else if ( t == port ) 
	    setChanges("smtp.server.port",t.getText());
      }
      
      private void buildUI()
      {
	server=   new JTextField(confserver,15);
	port=     new NumberField(confport,5);

	JLabel serverlabel= new JLabel("Mail server hostname: ");
	JLabel portlabel= new JLabel("Port number: ");

	GBLPanel p= new GBLPanel();
	p.add(serverlabel,   0,0,1,1,GBLPanel.EAST);
	p.add(server,        1,0,1,1,GBLPanel.WEST);
	p.add(portlabel,     0,1,1,1,GBLPanel.EAST);
	p.add(port,          1,1,1,1,GBLPanel.WEST);

	this.add("Center", p);
	
	server.addKeyListener(this);
	port.addKeyListener(this);
      }

    }

    return new SmtpPanel();

  }





  /**
   * Create a JPanel with controls for filtering out mail messages of
   * no interests
   * @return a Panel with controls to filter out mail messages 
   */
  private JPanel createFilterPane(String title)
  {
    class FilterPanel extends JPanel implements KeyListener  
    {
      private JTextField to, cc, from, subject;
      
      private String confto= Config.get("mail.filter.to");
      private String confcc= Config.get("mail.filter.cc");
      private String conffrom= Config.get("mail.filter.from");
      private String confsubject= Config.get("mail.filter.subject");

      
      public FilterPanel()
      {
	super(new BorderLayout());
	buildUI();
      }

      public void keyTyped(KeyEvent e){ }
      public void keyPressed(KeyEvent e) { }
      public void keyReleased(KeyEvent e) 
      {
	JTextField t= (JTextField)e.getSource();
	if ( t == to )
	    setChanges("mail.filter.to",t.getText());
	else if ( t == cc ) 
	    setChanges("mail.filter.cc",t.getText());
	else if ( t == from ) 
	    setChanges("mail.filter.from",t.getText());
	else if ( t == subject ) 
	    setChanges("mail.filter.subject",t.getText());
      }
      
      private void buildUI()
      {
	to=      new JTextField(confto,25);
	cc=      new JTextField(confcc,25);
	from=    new JTextField(conffrom,25);
	subject= new JTextField(confsubject,25);

	JLabel tolabel= new JLabel(Config.get("mail.filter.to.name"));
	JLabel cclabel= new JLabel(Config.get("mail.filter.cc.name"));
	JLabel fromlabel= new JLabel(Config.get("mail.filter.from.name"));
	JLabel subjectlabel= new JLabel(Config.
					get("mail.filter.subject.name"));

	GBLPanel p= new GBLPanel();
	p.add(tolabel,      0,0,1,1,GBLPanel.EAST);
	p.add(to,           1,0,GBLPanel.REST,1,GBLPanel.WEST);
	p.add(cclabel,      0,1,1,1,GBLPanel.EAST);
	p.add(cc,           1,1,GBLPanel.REST,1,GBLPanel.WEST);

	p.add(fromlabel,    0,2,1,1,GBLPanel.EAST);
	p.add(from,         1,2,GBLPanel.REST,1,GBLPanel.WEST);
	p.add(subjectlabel, 0,3,1,1,GBLPanel.EAST);
	p.add(subject,      1,3,GBLPanel.REST,1,GBLPanel.WEST);


	JPanel desc= new JPanel(new GridLayout(0,1)) {
	  public Insets getInsets()  {
	    return new Insets(10,0,0,0);
	  }
	};

	desc.add(new JLabel("Indicate words that should be used to filter "+
			    "out mail messages that you're not"));
	desc.add(new JLabel("interested in. For each header field, seperate "+ 
			    "a word with a space."));
	desc.add(new JLabel("Example: In the From field add: @hotmail.com "+
			    "@aol.com @microsoft.com"));    
	desc.add(new JLabel("(Mail from these domains will not be "+
			    "displayed in the biff window)"));
	
	this.add("North", desc);
	this.add("Center", p);
	
	to.addKeyListener(this);
	cc.addKeyListener(this);
	from.addKeyListener(this);
	subject.addKeyListener(this);
      }

    }

    return new FilterPanel();

  }





  /** 
   * Create a JPanel with controls for setting timeout for the Biff
   * frame. I.e. to automatically hide the frame after a certain
   * amount of time.
   * @return a Panel with controls to set timeout 
   */
  private JPanel createTimeOutPane(String title)
  {
    class TimeOutPanel extends JPanel 
	implements ItemListener, ChangeListener, KeyListener
    {
      private JSlider      timeSlider;
      private JCheckBox    timeOut;
      private JPanel       timeoutPanel;
      private NumberField  wakeupField;
      
      private boolean confTimeout= Config.getboolean("window.timeout");
      private int confSec= Config.getint("window.timeout.seconds");
      private String confwakeupport= Config.get("wakeup.server.port");

      
      public TimeOutPanel()
      {
	super(new BorderLayout());
	buildUI();
      }

      public void itemStateChanged(ItemEvent e) 
      {
	Object source = e.getItemSelectable();
	boolean selected= 
	    e.getStateChange() == ItemEvent.SELECTED?true:false;

	if ( source == timeOut ) {

	  if ( selected ) {
	    timeoutPanel.setVisible(true);
	  }
	  else {
	    timeoutPanel.setVisible(false);
	  }

	  this.validate();
     
	  setChanges("window.timeout", selected+"");
	}

      }
      
      public void stateChanged(ChangeEvent e) 
      {
	JSlider source= (JSlider)e.getSource();
	if (!source.getValueIsAdjusting()) {
	  int sec= (int)((JSlider)e.getSource()).getValue();
	  setChanges("window.timeout.seconds", sec+"");
	}
      }
      
      public void keyTyped(KeyEvent e){ }
      public void keyPressed(KeyEvent e) { }
      public void keyReleased(KeyEvent e) 
      {
	setChanges("wakeup.server.port",wakeupField.getText());
      }


      private void buildUI()
      {
	wakeupField= new NumberField(confwakeupport,5);
	JLabel wakeupLabel= new JLabel("Wakeup Port number: ");
	
	timeOut=  new JCheckBox("Biff window timeout", confTimeout);
	timeOut.setAlignmentY(Component.TOP_ALIGNMENT);

	timeSlider=  new JSlider(JSlider.HORIZONTAL, 10, 60, confSec); 
	timeSlider.setMajorTickSpacing(10);
	timeSlider.setMinorTickSpacing(5);
	timeSlider.setPaintTicks(true);
	timeSlider.setPaintLabels(true);
	timeSlider.setSnapToTicks(true);
	

	timeoutPanel= new JPanel(new BorderLayout());

	JPanel sliderPanel= new JPanel();
	JPanel wakeupPanel= new JPanel();

	if ( ! confTimeout )
	    timeoutPanel.setVisible(false);

	sliderPanel.add(timeSlider);
	wakeupPanel.add(wakeupLabel);
	wakeupPanel.add(wakeupField);

	timeoutPanel.add("North",sliderPanel);
	timeoutPanel.add("South",wakeupPanel);

	JPanel desc= new JPanel(new GridLayout(0,1)) {
	  public Insets getInsets()  {
	    return new Insets(10,0,0,0);
	  }
	};

	JPanel panel= new JPanel(){
	  public Insets getInsets()  {
	    return new Insets(20,0,0,0);
	  }
	};

	desc.add(new JLabel("You can use timeout to make the Biff window "+
			    "disappear after a certain time. "));
	desc.add(new JLabel("It will pop up again when you have new mail. "+
			    "If you want the window to"));
	desc.add(new JLabel("always be visible on your desktop, don't use " +
			    "timeout."));

	panel.add(timeOut);
	panel.add(timeoutPanel);

	this.add("North",desc);
	this.add("Center",panel);
	
	Util.setTitleBorder(sliderPanel,"Timeout value (seconds)");

	
	timeOut.addItemListener(this);
	timeSlider.addChangeListener(this);
	wakeupField.addKeyListener(this);
      }

    }

    return new TimeOutPanel();

  } 
  


  /**
   * Save changed properties in the local model <I>only</I>
   */
  private void setChanges(String key, String value)
  {
    myLocalModel.put(key,value);
  }




  //--------------------------------------------------------- Private Classes
  


  /**
   * A Number field class
   */
  private static class NumberField extends JTextField 
  {
    public NumberField(int c) 
    {
      super(c);
    }

    public NumberField(String s, int c) 
    {
      super(s,c);
    }
    
    protected Document createDefaultModel() 
    {
      return new NumberDocument();
    }

    static class NumberDocument extends PlainDocument 
    {

      public void insertString(int offs, String str, AttributeSet a) 
	  throws BadLocationException 
      {
	
	char[] source = str.toCharArray();
	char[] result = new char[source.length];
	int j = 0;
	
	for (int i = 0; i < result.length; i++) {
	  if (Character.isDigit(source[i]))
	      result[j++] = source[i];
	}
	super.insertString(offs, new String(result, 0, j), a);
      }
    }
  }





  /**
   * Paint a JTabbedPane with a tick line below the tabs and 
   * no content border. The line works as a UI visual aid.
   * The class uses a sub-class of MetalTabbedPaneUI to paint
   * the UI. 
   *
   * @author  Jan-Henrik Haukeland &lt;hauk@tildeslash.com&gt;
   *
   * @see JTabbedPane
   * @see javax.swing.plaf.metal.MetalTabbedPaneUI
   */
  private static class TabbedPanel extends JTabbedPane
  {
    
    public TabbedPanel()
    {
      UIManager.put("TabbedPane.selected", new Color(153, 153, 204));
      UIManager.put("TabbedPane.contentBorderInsets", new Insets(20,2,3,3));
      this.setUI(new TabbedPanelUI());
    }
    
    
    // ------------------------ Inner class ------------------------- //
    
    
    class TabbedPanelUI extends MetalTabbedPaneUI
    {
      {
	// For swing versions < 1.1
	this.contentBorderInsets=  new Insets(20,2,3,3);
      }
      
      
      /**
       * Overridden to paint a context line 
       */
      protected void paintContentBorderTopEdge( Graphics g, int tabPlacement,
						int selectedIndex,
						int x, int y, 
						int width, int height ) { 
	g.setColor(new Color(153, 153, 204));
	g.fillRect(x+2, y, width-5, 20);
      }
      
      /**
       * Overridden to do nothing for TabbedPanelUI
       */
      protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
						  int selectedIndex,
						  int x, int y, int w, int h) { 
      }
      
      /**
       * Overridden to do nothing for TabbedPanelUI
       */
      protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
						int selectedIndex,
						int x, int y, int w, int h) { 
      }
      
      /**
       * Overridden to do nothing for TabbedPanelUI
       */
      protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
						 int selectedIndex,
						 int x, int y, int w, int h) {
      }
    }
    
  }
  
}




  
