package nxm.sys.prim;

import java.awt.AWTEvent;
import java.awt.Button;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Label;
import java.awt.Panel;
import java.awt.ScrollPane;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import nxm.sys.inc.MessageHandler;
import nxm.sys.lib.Convert;
import nxm.sys.lib.FileName;
import nxm.sys.lib.Message;
import nxm.sys.lib.Shell;
import nxm.sys.lib.Time;
import nxm.sys.libg.GPrimitive;
import nxm.sys.libg.MWindow;

/**
  A graphical display primitive for messages being logged from macros.

  @author William Conway
*/
public class messagelog extends GPrimitive {
   private Logger logfile ;
   private LogButton IBut, WBut, EBut ;
   private Label LogLabel ;
   private Panel LogPane ;
   private CardLayout CL ;
   private ColorListBox[] CLB  = new ColorListBox[8] ;

   private Color BGC ; // Background color
   private Color FGC ; // Foreground color
   private Color ERRColor = Color.red ;
   private Color WRNColor = Color.yellow ;
   private Color INFColor = Color.white ;

   /** default constructor */
   public messagelog () {}

   public int open() {
      if ( MA.find("/FILE") ) {
        // check to see if we should buffer output and then open the log file
        if ( MA.getState ("/BUFFER", true) ) {
          logfile = new Logger ( true );
        } else {
          logfile = new Logger ( false );
        }

        logfile.open(MA.getFileName("/FILE") );
        logfile.writeln("Message log started at "+Time.tag() );
      }

      String title = MA.getS("/TITLE", "NeXt Midas Message Log");
      MW = new MWindow( title, this ) ;
      MW.open() ;
      MW.setSize( 400, 200 ) ;
      MW.addTo( this ) ;
      MW.setTheme(MA.getO("/THEME")) ;
      BGC = MW.panel.getBackground() ;
      FGC = MW.panel.getForeground() ;

      EBut = new LogButton("ERRORS ON", "ERRORS OFF", ERRColor, FGC, this ) ;
      EBut.Value = 1 ;
      WBut = new LogButton("WARNINGS ON", "WARNINGS OFF", WRNColor, FGC, this ) ;
      WBut.Value = 2 ;
      IBut = new LogButton("INFO ON", "INFO OFF", INFColor, FGC, this ) ;
      IBut.Value = 4 ;
      EBut.addTo( MW ) ;
      WBut.addTo( MW ) ;
      IBut.addTo( MW ) ;
      EBut.toggleOn() ;
      WBut.toggleOn() ;
      IBut.toggleOn() ;

      CL = new CardLayout() ;
      LogPane = new Panel() ;
      LogPane.setLayout( CL ) ;
      for (int i=0; i<8; i++) {
         CLB[i] = new ColorListBox(null, BGC, FGC ) ;
         CLB[i].setTrim( true ) ;
         CLB[i].setTrimSize( 500 ) ;
         LogPane.add( CLB[i], ""+i ) ;
      }
      MW.panel.add( LogPane ) ;
      CL.show( LogPane, "7") ;

      LogLabel = new Label("TYP   DAY        TIME        YEAR  TEXT") ;
      MW.panel.add( LogLabel ) ;

      resize() ;

      /* Handle any command line arguments the use may want about showing
         messages
      */
      if (!MA.getState("/ERROR", true)) {
        Message m = new Message( new String ("BUTTON"), 2,
                                 new String ("ERR") );
        handleButtonMsg (m);
      }

      if ( !MA.getState("/WARN", true)) {
        Message m = new Message ( new String ("BUTTON"), 1,
                                  new String ("WARN") );
        handleButtonMsg (m);
      }

      if ( !MA.getState("/INFO", true)) {
        Message m = new Message ( new String ("BUTTON"), 0,
                                  new String ("INFO") );
        handleButtonMsg (m);
      }

      return NORMAL ;
   }

   /** Standard data/event driven processing loop */
   public int process() { return NOOP; }

   /** Process a message */
   public int processMessage( Message msg ) {
     if ( !thisIsMe() )
       MQ.put(msg);
     else if ( msg.name.equals("RESIZE") )
       resize();
     else if ( msg.name.equals("EXIT") )
       return FINISH;
     else if ( msg.name.equals("LOG") )
       handleLogMsg(msg);
     else if ( msg.name.equals("LOGSTATUS") )
       handleLogStatusMsg(msg);
     else if ( msg.name.equals("BUTTON") ) {
       if (msg.data != null) {
         handleButtonMsg(msg);
       }
     }
     return NORMAL ;
   }

   /** Normal Close */
   public int close() {
     MW.close() ;
     if ( logfile != null ) {
       logfile.writeln("Message log stopped at "+Time.tag() ) ;
       logfile.close() ;
     }
     return NORMAL ;
   }

   /** resize the text terminal */
   public void resize() {
      if ( LogPane != null && EBut != null && WBut != null && IBut != null) {
         int btWidth = MW.pos.w/3 ;
         int ComponentHeight = 20 ;
         int LogWidth = MW.pos.w ;
         int LogHeight = MW.pos.h ;

         LogLabel.setBounds( 0, 0, LogWidth, ComponentHeight ) ;
         LogPane.setBounds( 0, 22, LogWidth, (LogHeight-28) ) ;
         EBut.resize( 0, (LogHeight-22), btWidth, ComponentHeight ) ;
         WBut.resize( btWidth, (LogHeight-22), btWidth, ComponentHeight ) ;
         IBut.resize( (btWidth*2), (LogHeight-22), btWidth, ComponentHeight ) ;
         for (int i=0; i<8; i++)
            CLB[i].setBounds( 0, 22, LogWidth, (LogHeight-25) ) ;
      }
   }
   /** set the value appropriately based on the key
    *  @param name  the name of the key
    *  @param value the value
    *  @return the value set
    */
   public Object setKey (String name, Object value) {
      if ( name.equals("FONT") ) {
         Font font = new Font ("monospaced", Font.PLAIN, Convert.o2l(value));
         resize();
      }
      else if ( name.equals("FONTH") ) {
         Font font = new Font ("monospaced", Font.PLAIN, Convert.o2l(value));
         resize();
      }
      else if ( name.equals("FONTB") ) {
         Font font = new Font ("monospaced", Font.PLAIN, Convert.o2l(value));
         EBut.setFont(font);
         WBut.setFont(font);
         IBut.setFont(font);
         resize();
      }
      else if ( name.equals("THEME") ) {
         MW.setTheme((String) value) ;
         M.info("Changing colors: "+MW.panel.getBackground() );
         BGC = MW.panel.getBackground() ;
         EBut.setBackground( BGC ) ;
         WBut.setBackground( BGC ) ;
         IBut.setBackground( BGC ) ;
         LogLabel.setBackground( BGC ) ;
         LogPane.setBackground( BGC ) ;
         for (int i=0; i<8; i++)
            CLB[i].setListBackground( BGC ) ;
         FGC = MW.panel.getForeground() ;
         EBut.OffColor = FGC ;
         WBut.OffColor = FGC ;
         IBut.OffColor = FGC ;
         LogLabel.setForeground( FGC ) ;
         LogPane.paintAll( LogPane.getGraphics() ) ;
      }
      return value ;
   }

   private void handleLogStatusMsg( Message msg ) {
      String logName = (String)msg.data;
      int status;

      if ( logName.startsWith("ERR") ) {
        status = msg.info;
        status = ( status == 0 ) ? 0:1;

        Message m = new Message ( new String ("BUTTON"), status,
                                  new String ("ERR") );
        handleButtonMsg(m);
      }
      else if ( logName.startsWith("WARN") ) {
        status = msg.info;
        status = ( status == 0 ) ? 0:2;

        Message m = new Message ( new String ("BUTTON"), status,
                                  new String ("WARN") );
        handleButtonMsg(m);
      }
      else {  // logName.startsWith("INFO")
        status = msg.info;
        status = ( status == 0) ? 0:4;

        Message m = new Message ( new String ("BUTTON"), status,
                                  new String ("INFO") );
        handleButtonMsg(m);
      }
   }

   private void handleLogMsg( Message msg ) {
      String[] logType = {"INF", "WRN", "ERR", "UNK"} ;
      Date thisDay = Calendar.getInstance().getTime() ;
      Color color = null ;
      String newline ;
      int colorcode = 8 ;

      newline = ( msg.info > 2 ) ?
      ("UNK  "+thisDay.toString()+"  "+msg.data ) :
      ( logType[msg.info]+"  "+thisDay.toString()+"  "+msg.data ) ;

      switch ( msg.info ) {
         case 0 : // info message
            color = INFColor ;
            colorcode = 4 ;
         break ;
         case 1 : // warning message
            color = WRNColor ;
            colorcode = 2 ;
         break ;
         case 2 : // error message
            color = ERRColor ;
            colorcode = 1 ;
         break ;
      }
      for (int i=1; i<8; i++) {
         if ( (i & colorcode) == colorcode )
            CLB[i].addListItem( newline, BGC, color ) ;
            CLB[i].scrollToBottom() ;
      }
      if ( logfile != null )
         logfile.writeln( newline ) ;
   }

   private void handleButtonMsg( Message msg ) {
      String btnName = (String)msg.data;
      if ( btnName.startsWith("ERR") ) {
         EBut.toggleState() ;
         EBut.Value = (!EBut.selected) ? 0:1 ;
      }
      else if ( btnName.startsWith("WARN") ) {
         WBut.toggleState() ;
         WBut.Value = (!WBut.selected) ? 0:2 ;
      }
      else {  // btnName.startsWith("INFO")
         IBut.toggleState() ;
         IBut.Value = (!IBut.selected) ? 0:4 ;
      }
      CL.show(LogPane, ""+(EBut.Value + WBut.Value + IBut.Value)) ;
   }

   /** An inner class for buttons to toggle messagelog states */
   private class LogButton extends MButton {
      /** NOTE: this should be changed if the structure/fields of this class changes. */
      private static final long serialVersionUID = 2012122811331L; // for Serializable interface

      private Color OnColor ;
      private Color OffColor ;
      private String OnText ;
      private String OffText ;
      private boolean selected ;
      private int Value ;

      private LogButton( String OnText, String OffText,
              Color OnColor, Color OffColor, MessageHandler mh ) {
         super( OnText, mh ) ;
         this.OnText = OnText ;
         this.OffText = OffText ;
         this.OnColor = OnColor ;
         this.OffColor = OffColor ;
         toggleOn() ;
      }
      private void toggleState() {
         if ( selected ) {
            toggleOff() ;
         }
         else {
            toggleOn() ;
         }
      }
      private void toggleOn() {
         setLabel( OnText ) ;
         setForeground( OnColor );
         selected = true ;
      }
      private void toggleOff() {
         setLabel( OffText ) ;
         setForeground( OffColor );
         selected = false ;
      }
   } // class LogButton

   private class Logger {
     private boolean _buffered;
     private boolean _writeable;
     private PrintWriter _writer;

     // constructor - sets whether buffering should be turned on or not
     //
     private Logger ( boolean buffer ) {
       _buffered = buffer;
       _writeable = false;
     };

     // open the logger and if no path is specified then set the path to AUx.1
     //
     public boolean open ( FileName name ) {
       if ( name.getPath () == "") {
         name.setPath ( M.io.getAuxPath("1") );
       }

       try {
         FileWriter fwriter = new FileWriter ( name.toString() );

         if ( _buffered )
           _writer = new PrintWriter ( new BufferedWriter ( fwriter ) );
         else
           _writer = new PrintWriter ( fwriter );

         _writeable = true;
       }
       catch ( Exception e ) {
         // e.printStackTrace();
         _writeable = false;
       }

       return true;
     };

     // write a line to the logger
     //
     public boolean writeln ( String line ) {
       if ( _writeable ) {
         try {
           _writer.write ( line + "\n");

           if ( ! _buffered )
             _writer.flush ();
         }
         catch ( Exception e ) {
           Shell.printStackTrace(e);
         }

         return true;
       }

       return false;
     };

     // sync and close the logger
     //
     public boolean close () {
       if ( _writeable ) {
         try {
           _writer.flush ();
           _writer.close ();
         }
         catch ( Exception e ) {
           Shell.printStackTrace(e);
         }

         return true;
       }

       return false;
     };
   } // class Logger


    /**
      A class to graphically display a button.

      NOTE: MButton is NOT serializable. The serialVersionUID is being left to prevent Java serializable
            warnings, as MButton extends Button which extends Component which implements Serializable.
            To serialize this would require the outer class messagelog and the field mh (MessageHandler)
            to be serializable.

      @author Bill Conway
    */
    public class MButton extends Button {
      /** NOTE: this should be changed if the structure/fields of this class changes. */
     private static final long serialVersionUID = 2012122812331L; // for Serializable interface

      /** the panel on which to draw the button */
      public Container panel;

      /** the button label */
      public String label;

      /** the MessageHandler for this button */
      public transient MessageHandler mh;

      /**
         Instantiate the button and initialize parameters
         @param label the button label
         @param mh the MessageHandler
       */
      public MButton (String label, MessageHandler mh) {
        super(label);
        initialize(label, mh);
      }

      /**
         Instantiate the button with the given text and a null MessageHandler

         @param label the button label
       */
      public MButton (String label) {
        this(label, null);
      }

      /** perform common constructor initialization */
      private void initialize(String label, MessageHandler mh) {
        this.label = label;
        this.mh = mh;
        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
        enableEvents(AWTEvent.KEY_EVENT_MASK);
      }

      /**
         Adds this button to the given MWindow's graphics panel.

         @param mw the MWindow to which the button is added
       */
      public void addTo (MWindow mw) {
        panel = mw.panel;
        Color cb = panel.getBackground();
        setBackground(cb);
        Color cf = panel.getForeground();
        setForeground(cf);
        panel.add(this);
      }

      /** Removes this button from the MWindow's graphics panel. */
      public void close () {
        if (panel!=null) panel.remove(this);
       }

      @Override
      public void processEvent (AWTEvent e) {
        if (e.getID()==MouseEvent.MOUSE_RELEASED) {
           mh.processMessage( new Message("BUTTON",0,this.label) );
        }
        super.processEvent (e);
      }

      /**
         Moves and resizes this button. The new location of the top-left
         corner is specified by <code>x</code> and <code>y</code>, and the
         new size is specified by <code>w</code> and <code>h</code>.

         @param x the  new <I>x</I>-coordinate of this button
         @param y the new <I>y</I>-coordinate of this button
         @param w the new width of this button
         @param h the new height of this button
       */
       public void resize(int x, int y, int w, int h) {
        setBounds(x, y, w, h);
      }
    }

    /** ColorListBox A color implementation of the List AWT component.  Elements
        added to the ColorListBox can have different foreground and background
        colors.  Elements are also selectable.

        <P>The ColorListBox also has a trim function that limits the number of
        elements displayed in the scrollable panel. When the maximum number of
        elements is reached, the first element in the list is removed leaving
        room for the new element.

        @author rma Coleman Research Corporation
    */
    public class ColorListBox extends ScrollPane {
      /** NOTE: this should be changed if the structure/fields of this class changes. */
      private static final long serialVersionUID = 2012122813331L; // for Serializable interface

      /** Panel associated with color box */ private Panel P ;
      /** All selected list elements      */ private ArrayList<ListItem> SelectedItems;
      /** List of Items                   */ private ArrayList<ListItem> ListItems;
      /** Can list elements be selected?  */ private boolean Selectable ;
      /** Trim function for box           */ private boolean Trim ;
      /** Trim size                       */ private int TrimSize ;
      /** Width of each list element      */ private byte LIWidth ;

      //Constructors

      /**
         Creates a new ColorListBox
       */
      public ColorListBox() {
        this(null) ;
      }

      /**
         Creates a new ColorListBox initialized with the array of items
         and Default FGC: Black, Default BGC: White
         @param List the list of items
       */
      public ColorListBox( String[] List ) {
         this(List, Color.black) ;
      }

      /**
         Creates a new ColorListBox initialized with the array of items
         and the given FGC and Default BGC: White
         @param List the list of items
         @param FGC the foreground color
       */
      public ColorListBox( String[] List, Color FGC ) {
         this(List, Color.white, FGC) ;
      }

      /**
         Creates a new ColorListBox initialized with the array of items,
         the given FGC and BGC
         @param List the list of items
         @param BGC the background color
         @param FGC the foreground color
       */
      public ColorListBox( String[] List, Color BGC, Color FGC ) {
        P = new Panel() ;
        P.setBackground(BGC) ;
        P.setLayout(null) ;
        add(P) ;

        SelectedItems = new ArrayList<>(100) ;
        ListItems = new ArrayList<>(100) ;
        Selectable = true ;
        Trim = false ;
        TrimSize = 0 ;
        LIWidth = 14 ;

        if ( List != null ) {
          for ( int i=0; i<List.length; i++ )
            addListItem(List[i], BGC, FGC) ;
        }
      }

      // Public Access Methods

      /**
         Add element with String (Default FGC: Black, Default BGC: White).
         @param Text the item to be added
       */
      public void addListItem( String Text ) { addListItem(Text, Color.black) ; }

      /**
         Add element with String and FGC (Default BGC: White).
         @param Text the item to be added
         @param FGC the foreground color for the item
       */
      public void addListItem( String Text, Color FGC ) {
         addListItem(Text, Color.white, FGC) ;
      }

      /**
         Add element with String, FGC and BGC.
         @param Text the item to be added
         @param BGC the background color for the item
         @param FGC the foreground color for the item
       */
      public void addListItem( String Text, Color BGC, Color FGC ) {
         ListItem item = new ListItem(Text, BGC, FGC) ;
         ListItems.add(item) ;
         drawListItem(item) ;
      }

      /**
         Add element using a ListItem.
         @param Item the item to be added
       */
      public void addListItem( ListItem Item ) {
        ListItems.add( Item ) ;
        drawListItem(Item) ;
      }

      /**
         Returns total number of elements in list.
         @return the number of elements in the list
       */
      public int getNumberOfListItems() { return ListItems.size() ; }

      /**
         Returns number of selected elements in list.
         @return the number of selected items in the list
       */
      public int getNumberOfSelectedItems() { return SelectedItems.size() ; }

      /**
         Returns element at Index.
         @param Index the position of the item
         @return the item that is associated with the specified index
       */
      public ListItem getListItem( int Index ) {
        return ListItems.get(Index) ;
      }

      /**
         Returns String[] of all selected elements.
         @return a String array containing items in the list
       */
      public String[] getSelectedItems() {
        String[] rtnstr = new String[SelectedItems.size()];
        for ( int i=0; i<SelectedItems.size(); i++ ) {
          ListItem item = SelectedItems.get(i) ;
          rtnstr[i] = item.getText() ;
        }
        return rtnstr ;
      }

      /** Clears selected elements and resets SelectedItems ArrayList. */
      public void clearSelectedItems() {
        for ( int i=0; i<SelectedItems.size(); i++ ) {
          ListItem item = SelectedItems.get( i ) ;
          item.setBackground( item.BGC ) ;
          item.Selected = false ;
        }
        SelectedItems.clear() ;
      }

      /** Removes all elements and resets ListItems &amp; SelectedItems ArrayLists.*/
      public void removeAllItems() {
        ListItems.clear() ;
        SelectedItems.clear() ;
        P.removeAll() ;
      }

      /**
         Removes an element from ListItems (and possibly SelectedItems)
                ArrayList based on String.
         @return true if the item is found, false otherwise
         @param Text the item to be removed
       */
      public boolean removeListItem( String Text ) {
        boolean foundit = false ;
        for ( int i=0; i<ListItems.size(); i++ ) {
          ListItem item = ListItems.get(i) ;
          if ( Text.equals(item.getText()) ) {
            if ( item.Selected ) deselectItem(item) ;
            ListItems.remove(i) ;
            foundit = true ;
            break ;
          }
        }
        return foundit ;
      }

      /**
         (NM) Adds ColorListBox to MWindow panel.
         @param MW the MWindow to which this list is to be added
       */
      public void addTo( MWindow MW ) {
        Container panel = MW.panel ;
        Color cb = panel.getBackground() ;
        setBackground(cb) ;
        panel.add(this) ;
      }

      /** Forces scroll to bottom of list. */
      public void scrollToBottom() {
        Dimension dim = getViewportSize() ;
        int PHeight = P.getHeight() ;
        setScrollPosition(0, (int)(PHeight - dim.getHeight())) ;
      }

      /** Forces scroll to top of list. */
      public void scrollToTop() { setScrollPosition(0, 0) ; }

      /**
         Toggles element selectability in ColorListBox.
         @param Opt if true, then elements in the list are selectable
       */
      public void setSelectable( boolean Opt ) { Selectable = Opt ; }

      /**
         Returns state of Selectable switch.
         @return true if this list allows element selection
       */
      public boolean isSelectable() { return Selectable ; }

      /**
         Toggles trim function of ColorListBox.
         @param Opt if true, trim function is turned on
       */
      public void setTrim( boolean Opt ) { Trim = Opt ; }

      /**
         Returns state of Trim switch.
         @return true if the trim function for this list is on
       */
      public boolean trimOn() { return Trim ; }

      /**
         Toggles trim function of ColorListBox.
         @param Size the new trim size
       */
      public void setTrimSize( int Size ) { TrimSize = Size ; }

      /**
         Returns state of Trim switch.
         @return the state of the trim switch
       */
      public int GetTrimSize() { return TrimSize ; }

      /**
         Returns width of elements in list.
         @return the width of the elements in the list
       */
      public byte getItemWidth() { return LIWidth ; }

      /**
         Sets width of SUBSEQUENT elements in list (has no effect on
         elements current in the list).
         @param Width the new width for elements in the list
       */
      public void setItemWidth( byte Width ) { LIWidth = Width ; }

      /**
         Changes color of panel containing list elements.
         @param NewColor the new list background color
       */
      public void setListBackground( Color NewColor ) {
        P.setBackground(NewColor) ;
      }

      /**
         Changes size of ColorListBox.
         @param Width the new width
         @param Height the new height
       */
      public void setColorListBoxSize( int Width, int Height ) {
        setSize(Width, Height) ;
      }

      /**
         (NM) Resize &amp; position ColorListBox.
         @param X the new <I>x</I>-coordinate of the list location
         @param Y the new <I>y</I>-coordinate of the list location
         @param Width the new width
         @param Height the new height
       */
      public void resize( int X, int Y, int Width, int Height ) {
        setBounds(X, Y, Width, Height) ;
      }

      // Private Methods

      // Draws new list element on panel.
      private void drawListItem( ListItem Item) {
        if ( Trim ) {
          if ( ListItems.size() > TrimSize ) {
            Color nextColor = Item.getForeground() ;
            String nextText = Item.getText() ;
            Color saveColor ;
            String saveText ;
            ListItems.remove(TrimSize) ;
            for ( int i=ListItems.size()-1; i>=0; i-- ) {
              ListItem toItem = ListItems.get(i) ;
              saveColor = toItem.getForeground() ;
              saveText = toItem.getText() ;

              toItem.setForeground(nextColor) ;
              toItem.setText(nextText) ;

              nextColor = saveColor ;
              nextText = saveText ;
            }
            return ;
          }
        }

        int Width = getWidth() ;
        int Height = ListItems.size()*LIWidth ;
        P.setBounds(0, 0, Width, Height) ;
        P.add(Item) ;
        Item.setBounds(0, (Height-LIWidth), Width, LIWidth) ;
        paintAll(getGraphics()) ;
      }

      // Adds a ListItem to the SelectedItems Vector list.
      private void selectItem( ListItem Item ) {
        SelectedItems.add(Item) ;
        Item.setBackground(Color.lightGray) ;
        Item.Selected = true ;
      }
      /* Removes a ListItem from the SelectedItems Vector list. Only the
         first instance of the ListItem will be removed.  If there are
         duplicates in the list, you'll need to call this more than once.
       */
      private void deselectItem( ListItem Item ) {
        for ( int i=0; i<SelectedItems.size(); i++ ) {
          if ( Item.equals(SelectedItems.get(i)) ) {
            SelectedItems.remove(i) ;
            break ;
          }
        }
        Item.setBackground(Item.BGC) ;
        Item.Selected = false ;
      }

      /** Inner class ListItem - no one should need direct access to this class.

          NOTE: ListItem is NOT serializable. The serialVersionUID is being left to prevent Java serializable
                warnings, as ListItem extends Label which extends Component which implements Serializable.
                To serialize this would require the outer class messagelog and the field Listener (ListItemListener)
                to be serializable.

          A ListItem is an element object in the ColorListBox. It implements the
          MouseListener interface so that it can respond to mouse clicks (as
          selected or unselected.)
       */
      class ListItem extends Label{
        /** NOTE: this should be changed if the structure/fields of this class changes. */
        private static final long serialVersionUID = 2012122814331L; // for Serializable interface

        private boolean Selected ;
        private transient ListItemListener Listener ;
        private Color BGC ;

        // constructor - only ColorListBox should create ListItems
        ListItem( String Text, Color BGC, Color FGC ) {
          super(Text) ;
          this.setBackground(BGC) ;
          this.BGC = BGC ;
          this.setForeground(FGC) ;
          Listener = new ListItemListener() ;
          this.addMouseListener(Listener) ;
        }
        /* This could be set up as an anonymous inner class off of the
           addMouseListener() call in ListItem. I intentionally left it
           out for readability.
         */
        private class ListItemListener extends MouseAdapter {
          public void mouseClicked(MouseEvent e) {
            if ( Selectable ) {
              ListItem theSource = (ListItem) e.getSource() ;
              if ( !theSource.Selected )
                selectItem(theSource) ;
              else
                deselectItem(theSource) ;
            }
          }
        }  // class ListItemListener
      }  // class ListItem

    } // class ColorListBox
} // class messagelog
