Listing of Source ../source/GOF/GofListBoxIdentifier.java
package se.entra.phantom.server;

import java.util.StringTokenizer;
import java.util.Vector;
import java.io.IOException;
import java.util.NoSuchElementException;

/**
 * This class identifies list box controls for the Gui-on-the-fly, from unused GofHostFields.
 * This class will only try to identify selectable lists.
 * @author J. Bergström
 */
@SuppressWarnings("unchecked")
public class GofListBoxIdentifier extends GofControlIdentifierAdapter implements PhantomControlType
{
  // ------------------
  // INSTANCE VARIABLES
  // ------------------

  /**
   * The minimum number of lines (excluding the header) required in a listbox.
   * Default is 2.
   */
  private int minLines = 2;

  /**
   * The minimum number of columns required in a listbox.
   * Default is 2.
   */
  private int minColumns = 2;

  /**
   * The max number of lines to search for the header fields.
   * Default is 1.
   */
  private int maxheaderlines = 1;

  /**
   * The maximum number of character allowed for the selection field.
   * Default is 2.
   */
  private int maxselfieldlength = 2;
  
  /**
   * Flag indicating that a continuation mark has been found on the screen.
   */
  private boolean hasContMark;

  /**
   * The last line to search for a list. If a list continuation mark has been found, this will
   * be the same line as this is on, otherwise it will be screen's last line.
   */
  private int lastLine;

  /**
   * Filler character if there is one. Will be <code>null</code> otherwise.
   */
  private String listFieldFiller;

  /**
   * A list that indicates the way the list box's layout will be created.
   * Valid values are:
   * <pre>
   *    DEFAULT
   *    FONT
   *    COLOR
   *    LINES
   * </pre>
   */
  private Vector<String> layout = new Vector<String>( );

  // ----------------
  // INSTANCE METHODS
  // ----------------

  /**
   * Loads setting for the control from the server ini-file.
   * <p>
   * There are three settings for the list box that can be specified in the 
   * configuration file. They are the three below, with typical values.
   * <pre>
   *    listboxmincols=2
   *    listboxminlines=2
   *    listboxmaxheaderheight=2
   * </pre>
   * <p>
   * There are four settings for the list box that can be specified in the 
   * configuration file. They are the four listed below, with typical values.
   * <pre>
   *   listboxmincols=2
   *   listboxminlines=2
   *   listboxmaxheaderheight=2
   *   listboxmaxsellength=2
   * </pre>
   * The listboxmincols specifies the minimum number of columns required in a 
   * list box candidate for a list box control. The default value is two.
   * <p>
   * The listboxminlines specifies the minimum number of lines required in a 
   * list box candidate for a list box control. The default value is two.
   * <p>
   * The listboxmaxheaderheight specifies the maximum number of lines to search 
   * for the column headers. The default value is one.
   * <p>
   * There is also a setting that specifies if there is a filler character to use, 
   * and what character this should be. This setting is called listfieldfiller, 
   * and if the filler character were the underscore character, it would look like 
   * this:
   * <pre>
   *   listfieldfiller=_
   * </pre>
   * This class has a setting that affects the look of the entry fields. This is 
   * the listboxlayout setting. Valid values for this setting are:
   * <pre>
   *   listboxlayout=DEFAULT
   *   listboxlayout=FONT
   *   listboxlayout=COLOR
   *   listboxlayout=LINES
   * </pre>
   * DEFAULT means that no settings are taken from the template panel; default 
   * values will be used instead. 
   * <p>
   * FONT means that the fonts are taken from template panel, from a list box 
   * with the id=LISTB. If this control cannot be found, or if it is not an 
   * output text control, default values will be used. Both the header font 
   * and the list font are taken from the template list box.
   * <p>
   * COLOR means that the colors are taken from template panel, from a list 
   * box with the id=LISTB. If this control cannot be found, or if it is not 
   * an output text control, default values will be used. The colors that 
   * will be taken from the template list box are background- and the 
   * foreground colors for both the header and the list.
   * <p>
   * LINES means that all the line settings from template panel, from a list 
   * box with the id=LISTB. If this control cannot be found, or if it is not 
   * an output text control, default values will be used. The settings that 
   * are taken from the template list box are line between columns, line 
   * between headers, line between header and column and the line between lines.
   * <p>
   * Any other value will be ignored.
   * <p>
   * All these layout settings can be combined in a suitable way. For example, 
   * if FONT, COLOR and LINES were wanted, the setting would look like this:
   * <pre>
   *   listboxlayout=FONT COLOR LINES
   * </pre>
   * @param confFile   The server ini file.
   * @param subsection The name of the GOF subsection that we are currently loading settings from.
   */
  @Override
  public void getControlSettings( IniFile confFile, String subsection )
  {
    String setting;
    setting = confFile.getData( subsection, "listboxmaxheaderheight" );
    if( setting != null )
    {
      try
      {
        maxheaderlines = Integer.valueOf( setting ).intValue( );
      }
      catch( NumberFormatException e )
      {
      }
    }

    setting = confFile.getData( subsection, "listboxminlines" );
    if( setting != null )
    {
      try
      {
        minLines = Integer.valueOf( setting ).intValue( );
      }
      catch( NumberFormatException e )
      {
      }
    }

    setting = confFile.getData( subsection, "listboxmincols" );
    if( setting != null )
    {
      try
      {
        minColumns = Integer.valueOf( setting ).intValue( );
      }
      catch( NumberFormatException e )
      {
      }
    }

    setting = confFile.getData( subsection, "listfieldfiller" );
    if( setting != null && setting.equals( "" ) == false )
      listFieldFiller = setting;
    else
      listFieldFiller = null;

    setting = confFile.getData( subsection, "listboxlayout" );
    parseLayoutString( setting, layout );

    setting = confFile.getData( subsection, "listboxmaxsellength" );
    if( setting != null )
    {
      try
      {
        maxselfieldlength = Integer.valueOf( setting ).intValue( );
      }
      catch( NumberFormatException e )
      {
      }
    }
  }

  /**
   * Identifies all the list box controls from the <code>GofHostFields</code>.
   * The identification of list boxes is done in several steps.
   * <p>
   * First the identification process goes through all unused GofHostFields, and 
   * creates column elements from them. GofHostFields in consecutive lines that 
   * starts in the same column, and has the same width, will be placed in a single 
   * column element. 
   * <p>
   * In the second step, the identification process will go through all column elements, 
   * and create list elements from them. Column elements starting on the same line and 
   * with the same height are considered to belong to a single list element. When 
   * comparing the column elements start line to each other, consideration is taken that 
   * a column header might have been included at the top of some of the column elements. 
   * So if one starts, for example, one line above the other, but they end at the same 
   * line, they will still be considered to belong to the same list element. The first 
   * line in the column that started one line earlier will be used in the header.
   * <p>
   * Next, the identification process will check if a list element has the minimum 
   * number of lines and columns required for a list. The minimum number of lines and 
   * columns are specified in the configuration file. It will also check if the list 
   * element has texts or entry fields to left, if so it will not be considered a 
   * candidate for a list box. This means that the list box should be the first 
   * control from the left. This will always be true for a list in an AS/400 
   * application created as a subfile.
   * <p>
   * The list box candidates left will be checked if the first column (NOTYET! The last) 
   * is editable and has a maximum width not greater than the value specified for the 
   * listboxmaxsellength setting. Then this column is accepted as a selection column, 
   * and the list accepted as a list box.
   * <p>
   * As a last step the identification process will try and find the column headers, before 
   * creating the list box.
   * @param areaIdentifier    The areaIdentifier in which the entry fields should be identified..
   * @param phantomHostScreen The Phantom host screen corresponding to the host screen.
   * @param hostScreen        The host screen that we are trying to build a GOF panel for.
   * @param newPanel          The newly created Gui-on-the-fly runtime panel.
   * @param offsetX           The offset in columns for popup window.
   * @param offsetY           The offset in lines for popup window.
   */
  @SuppressWarnings("rawtypes")
  @Override
  public void identifyCtrls( GuiOnTheFlyRuntime gofRuntime,
                             GofHostAreaIdentifier areaIdentifier, 
                             PhantomHostScreen phantomHostScreen, 
                             HostScreen hostScreen,
                             PhantomPanelData templPanel, 
                             PhantomPanelData newPanel,
                             int offsetX,
                             int offsetY )
  {
    this.templPanel = templPanel;

    //prevHeadGce = null;

    Vector<GofHostField> gofHostFields = areaIdentifier.getAreasGofHostFields( );

    int scrnCx = hostScreen.getWidth( );
    int scrnCy = hostScreen.getHeight( );

    Object attrib = gofRuntime.getAttribute( "hasListCont" );
    if( attrib != null )
      hasContMark = ( ( Boolean )attrib ).booleanValue( );
    else
      hasContMark = false;

    attrib = gofRuntime.getAttribute( "lastListLine" );
    if( hasContMark == true && attrib != null )
      lastLine = ( ( Integer )attrib ).intValue( );
    else
      lastLine = scrnCy;

    Vector[] gceMap = createColumnElements( gofHostFields, scrnCx );

    Vector[] gleList = createListElements( gceMap, scrnCy, scrnCx );

    for( int i = 0; i < scrnCy; i++ )
    {
      Vector v = gleList[i];
      for( int j = 0, s = v.size( ); j < s; j++ )
      {
        GofListElement gle = ( GofListElement )v.elementAt( j );
        if( gle.colCount( ) >= minColumns && gle.height >= minLines && gle.isLeftColLeftmost( gceMap ) == true )
          createListBox( gle, phantomHostScreen, hostScreen, newPanel, gceMap, offsetX, offsetY );
      }
    }
  }

  /**
   * Creates column elements. GofHostFields in consecutive lines that starts in the 
   * same column, and has the same width, will be placed in a single column element.
   * @param gofHostFields  The GofHostFields to analyze.
   * @param scrnCx         The width of the area to analyze.
   * @return An array of <code>Vectors</code> containing all the column elements.
   *         A column element will placed in the Vector that is in the element with
   *         the index equal to the column elemet'ss start column.
   */
  @SuppressWarnings("rawtypes")
  private Vector[] createColumnElements( Vector<GofHostField> gofHostFields, int scrnCx )
  {
    Vector[] gceList = new Vector[scrnCx];
    for( int x = 0; x < scrnCx; x++ )
      gceList[x] = new Vector( );

    for( int i = 0, s = gofHostFields.size( ); i < s; i++ )
    {
      GofHostField ghf = gofHostFields.elementAt( i );
      int x = ghf.getX( );
      int y = ghf.getY( );

      if( y > lastLine )
        break;

      GofColumnElement prevGce;
      try
      {
        prevGce = ( GofColumnElement )gceList[x].lastElement( );
      }
      catch( NoSuchElementException e )
      {
        prevGce = null;
      }

      int cx = ghf.getCx( );
      if( prevGce != null && ( prevGce.startLine + prevGce.height ) == y && prevGce.width == cx )
      {
        prevGce.addGofHostField( y, ghf );
      }
      else
      {
        GofColumnElement gce = new GofColumnElement( x, y, ghf );
        gceList[x].addElement( gce );
      }
    }
    return gceList;
  }

  /**
   * Creates list elements from column elements starting on the same line, and with 
   * the same height are considered to belong to a single list element. When comparing 
   * the column elements start line to each other, consideration is taken that a column 
   * header might have been included at the top of some of the column elements, so if one 
   * starts, for example, one line above the other, but they end at the same line, they 
   * will still be considered to belong to the same list element. The first line in the 
   * column that started one line earlier will be used in the header.
   * @param gceList An array of Vectors holding all the column elements.
   * @param scrnCy  The height of the area to analyze.
   * @param scrnCx  The width of the area to analyze.
   * @return An array of vectors containing all the list elements. A list element will
   *         be placed in the Vector in the element with the index equal to the start
   *         line for the list element.
   */
  @SuppressWarnings("rawtypes")
  private Vector[] createListElements( Vector[] gceList, int scrnCy, int scrnCx )
  {
    Vector[] gleList = new Vector[scrnCy];
    for( int y = 0; y < scrnCy; y++ )
      gleList[y] = new Vector( );

    for( int x = 0; x < scrnCx; x++ )
    {
      for( int i = 0, s = gceList[x].size( ); i < s; i++ )
      {
        GofColumnElement gce = ( GofColumnElement )gceList[x].elementAt( i );
        int y = gce.startLine;
        GofListElement prevGle = getPrevListElement( gleList, y, gce.height );
        int yOffs = 0;
        while( prevGle == null && yOffs < maxheaderlines )
        {
          yOffs++;
          prevGle = getPrevListElement( gleList, y + yOffs, gce.height - yOffs );
        }

        if( prevGle != null )
        {
          prevGle.addGofColumnElement( gce );
          gce.offset = yOffs;
        }
        else
        {
          GofListElement gle = new GofListElement( x, y, gce );
          gleList[y].addElement( gle );
        }
      }
    }
    return gleList;
  }

  /**
   * Gets the previous list element that starts on the same row. 
   * @param gleList An array of Vectors containing all the list elements.
   * @param y       The required start line.
   * @param cy      The required height.
   * @return If no previous list element exists, or if the previous list 
   *         element's height does not equal the specified height, or has 
   *         a height less that minimum lines, <code>null</code> will be returned,
   *         otherwise the previous list element will be returned.
   */
  @SuppressWarnings("rawtypes")
  private GofListElement getPrevListElement( Vector[] gleList, int y, int cy )
  {
    GofListElement prevGle;
    try
    {
      prevGle = ( GofListElement )gleList[y].lastElement( );
      if( prevGle.height != cy || prevGle.height < minLines )
        prevGle = null;
    }
    catch( NoSuchElementException e )
    {
      prevGle = null;
    }
    return prevGle;
  }

  /**
   * Creates a listbox from a listbox element.
   * <p>
   * The list box element will be checked if the first column (NOTYET! The last) 
   * is editable and has a maximum width of two characters. Then this column is 
   * accepted as a selection column, and the list accepted as a list box.
   * <p>
   * As a last step the identification process will try and find the column headers, 
   * before creating the list box.
   * @param gle               The list box element that should be converted to a list box.
   * @param phantomHostScreen The Phantom host screen corresponding to the host screen.
   * @param hostScreen        The host screen that we are trying to build a GOF panel for.
   * @param newPanel          The newly created Gui-on-the-fly runtime panel.
   * @param gceMap
   */
  @SuppressWarnings("rawtypes")
  private void createListBox( GofListElement gle, 
                              PhantomHostScreen phantomHostScreen, 
                              HostScreen hostScreen, 
                              PhantomPanelData newPanel,
                              Vector[] gceMap,
                              int offsetX,
                              int offsetY )
  {
    int listX = 0, listY = 0, listXx = 0, listYy = 0;
    int headCy = 0;

    int s = gle.listsGofColumnElements.size( );
    GofListColumnElement[] lce = new GofListColumnElement[s];

    for( int i = 0; i < s; i++ )
    {
      GofColumnElement gce = gle.listsGofColumnElements.elementAt( i );

      boolean isEditable = gce.isEditable( );
      int x = gce.startCol;
      int y = gce.startLine + gce.offset;
      int cx = gce.width;
      int cy = gce.height - gce.offset;
      int colCx;

      if( ( i == 0 && isEditable == false ) || ( i == 0 && cx > maxselfieldlength ) )
        return;

      gce.markAsProcessed( );

      if( i < s - 1 )
      {
        // Not last column.
        GofColumnElement nxtgce = gle.listsGofColumnElements.elementAt( i + 1 );
        colCx = nxtgce.startCol - x;
      }
      else
        colCx = cx;

      if( listX == 0 || x < listX )
        listX = x;
      if( listY == 0 || y < listY )
        listY = y;
      if( listXx == 0 || x + cx > listXx )
        listXx = x + cx;
      if( listYy == 0 || y + cy > listYy )
        listYy = y + cy;

      String headText = "";
      if( maxheaderlines > 0 )
      {
        if( gce.offset == maxheaderlines )
        {
          // Get header from top of column.
          headText = gce.getHeadText( );
        }
        else if( gce.offset < maxheaderlines && gce.offset > 0 )
        {
          // Get header from column element(s) above column.
          headText = getHeader( x, y, cx, gce.offset, gceMap );
          // Get header from top of column.
          String colHeadText = gce.getHeadText( );
          if( headText.equals( "" ) == false )
            headText = headText + "\r\n" + colHeadText;
          else
            headText = colHeadText;
        }
        else
        {
          // Get header from column element(s) above column.
          headText = getHeader( x, y, cx, gce.offset, gceMap );
        }
      }

      int hh = 0;
      if( !headText.equals( "" ) )
      {
        hh = 1;
        int p = headText.indexOf( "\n" );
        while( p > -1 )
        {
          hh++;
          p = headText.indexOf( "\n", p + 1 );
        }
      }
      int newHcy = hh * GOF_STEPY;
      if( newHcy > headCy )
        headCy = newHcy;

      PhantomHostField phf = new PhantomHostField( phantomHostScreen, hostScreen, x, y, cx, cy );
      if( listFieldFiller != null )
      {
        phf.filler = listFieldFiller.charAt( 0 );
        phf.flags = PhantomHostField.FORMAT_STRIPEND;
      }
      phantomHostScreen.addHostField( phf );

      GofListColumnElement gofListColumnElement = new GofListColumnElement( phf, colCx, isEditable, headText.trim( ) );
      lce[i] = gofListColumnElement;
    }

    int font = -1;
    int headFont = -1;
    int color = 0;
    int listBgColor = 0;
    int headColor = 0;
    int headerBgColor = 0;
    int lineValues[] = new int[4];

    PhantomControl templateControl = templPanel.getControlFromID( "LISTB" );

    if( isReqLayout( "FONT" ) == true )
    {
      if( templateControl != null && templateControl.controlBase.type == CTRLTYPE_LIST )
      {
        font = ( ( PhantomCListBox )templateControl ).font;
        headFont = ( ( PhantomCListBox )templateControl ).headFont;
      }
    }
    if( isReqLayout( "COLOR" ) == true )
    {
      if( templateControl != null && templateControl.controlBase.type == CTRLTYPE_LIST )
      {
        color = ( ( PhantomCListBox )templateControl ).color;
        listBgColor = ( ( PhantomCListBox )templateControl ).listBgColor;
        headColor = ( ( PhantomCListBox )templateControl ).headColor;
        headerBgColor = ( ( PhantomCListBox )templateControl ).headerBgColor;
      }
    }
    if( isReqLayout( "LINES" ) == true )
    {
      if( templateControl != null && templateControl.controlBase.type == CTRLTYPE_LIST )
      {
        lineValues[0] = ( ( PhantomCListBox )templateControl ).divBetwCols;
        lineValues[1] = ( ( PhantomCListBox )templateControl ).divBetwHeaders;
        lineValues[2] = ( ( PhantomCListBox )templateControl ).divBetwHeadCol;
        lineValues[3] = ( ( PhantomCListBox )templateControl ).lineBetwRows;
      }
    }

    // Create the base control.

    int listCx = listXx - listX;
    int listCy = listYy - listY;
    PhantomControlBase bc = new PhantomControlBase( GOF_MARGINX + ( listX - offsetX ) * GOF_STEPX,
                                                    GOF_MARGINY + ( listY - offsetY ) * GOF_STEPY - 1 - headCy,
                                                    listCx * GOF_STEPX,
                                                    listCy * GOF_STEPY + headCy,
                                                    CTRLTYPE_LIST );
    try
    {
      PhantomCListBox lb = new PhantomCListBox( newPanel, bc, headCy, lineValues, lce );
      lb.font = font;
      lb.headFont = headFont;
      lb.color = color;
      lb.listBgColor = listBgColor;
      lb.headColor = headColor;
      lb.headerBgColor = headerBgColor;
      newPanel.addControl( lb );
    }
    catch( IOException e )
    {
    }
  }

  /**
   * Tries to found a header for a GofColumnElement.
   * @param x      The start column of the current column element.
   * @param y      The start line of the current column element.
   * @param cx     The width of he current column element.
   * @param offset The columns elements offset. This is the number of lines above 
   *               the list elements topline this column starts.
   * @param gceMap An array of Vectors into which all the screens column elements are mapped.
   * @return The header text, if one was found, otherwise an empty String.
   */
  @SuppressWarnings("rawtypes")
  private String getHeader( int x, int y, int cx, int offset, Vector[] gceMap )
  {
    String hText = "";
    boolean hasBeenFound = false;
    int headStartLine = y - maxheaderlines;

    // First check current column's startcolumn.

    for( int i = 0, s = gceMap[x].size( ); i < s; i++ )
    {
      GofColumnElement gce = ( GofColumnElement )gceMap[x].elementAt( i );
      int sl = gce.startLine;
      int el = sl + gce.height - 1;
      if( sl >= headStartLine && sl < y + offset && el >= headStartLine && el < y + offset )
      {
        // A column object that is completely inside the lines where the header is allowed to be.
        hText = gce.getText( 0, x, cx );
        hasBeenFound = true;
        break;
      }
      else if( sl < headStartLine && sl < y + offset && el >= headStartLine && el < y + offset )
      {
        // A column object that is starts above the first allowed header line, 
        // but ends inside the lines where the header is allowed to be.
        hText = gce.getText( headStartLine - sl, x, cx );
        hasBeenFound = true;
        break;
      }
    }

    if( hasBeenFound == false )
    {
      // Check startcolumns between the column's startcolumn and it's endcolumn.
      int xx = x + 1;
      int ss = x + cx;
      while( xx < ss && hasBeenFound == false )
      {
        for( int i = 0, s = gceMap[xx].size( ); i < s; i++ )
        {
          GofColumnElement gce = ( GofColumnElement )gceMap[xx].elementAt( i );
          int sl = gce.startLine;
          int el = sl + gce.height - 1;
          if( sl >= headStartLine && sl < y + offset && el >= headStartLine && el < y + offset )
          {
            // A column object that is completely inside the lines where the header is allowed to be.
            hText = gce.getText( 0, xx, cx - ( xx - x ) + 1 );
            hasBeenFound = true;
            break;
          }
          else if( sl < headStartLine && sl < y + offset && el >= headStartLine && el < y + offset )
          {
            // A column object that is starts above the first allowed header line, 
            // but ends inside the lines where the header is allowed to be.
            hText = gce.getText( headStartLine - sl, xx, cx - ( xx - x ) + 1 );
            hasBeenFound = true;
            break;
          }
        }
        xx++;
      }
    }

    if( hasBeenFound == false )
    {
      // Check startcolumns before the column's startcolumn.
      int xx = x - 1;
      while( xx >= 0 && hasBeenFound == false )
      {
        for( int i = 0, s = gceMap[xx].size( ); i < s; i++ )
        {
          GofColumnElement gce = ( GofColumnElement )gceMap[xx].elementAt( i );
          int sl = gce.startLine;
          int el = sl + gce.height - 1;
          if( sl >= headStartLine && sl < y + offset && el >= headStartLine && el < y + offset )
          {
            // A column object that is completely inside the lines where the header is allowed to be.
            hText = gce.getText( 0, x, cx );
            hasBeenFound = true;
            break;
          }
          else if( sl < headStartLine && sl < y + offset && el >= headStartLine && el < y + offset )
          {
            // A column object that is starts above the first allowed header line, 
            // but ends inside the lines where the header is allowed to be.
            hText = gce.getText( headStartLine - sl, x, cx );
            hasBeenFound = true;
            break;
          }
        }
        xx--;
      }
    }

    return hText;
  }

  // ==================================================================
  // GofColumnElement subclass
  // ==================================================================

  /**
   * The GofColumnElement is a private inner class that is used to store information 
   * about the column elements created during the identification process of the list 
   * box. A column element is a column of one or more GofHostFields that starts in 
   * the same x-position, and has the same width.
   * <p>
   * In addition to some methods to set and retrieve information, it also contains 
   * some methods for retrieving column headers, one method that checks if the column 
   * element is editable.
   * <p>
   * The used GofColumnElements will be deleted when a new host screen is processed.
   */
  private class GofColumnElement
  {
    // ------------------
    // INSTANCE VARIABLES
    // ------------------

    /**
     * A Vector containing all GofHostFields that is part of this column element.
     */
    private Vector<GofHostField> colsGofHostFields;

    /** 
     * The column elements startcolumn on the host screen.
     */
    private int startCol;

    /** 
     * The column elements startline on the host screen.
     */
    private int startLine;

    /**
     * The width of the column element in characters.
     */
    private int width;

    /**
     * The height of the column element in lines.
     */
    private int height;

    /**
     * The offset at which the list starts. Will be zero if no header text is in the column element.
     */
    private int offset = 0;

    // -----------
    // CONSTRUCTOR
    // -----------

    /**
     * Creates a new Gof column element.
     * @param x   The column element's start column on the host screen.
     * @param y   The column element's start line on the host screen.
     * @param ghf The first GofHostField that belongs to this column element.
     */
    private GofColumnElement( int x, int y, GofHostField ghf )
    {
      startCol = x;
      startLine = y;
      width = ghf.getCx( );
      height = 1;

      colsGofHostFields = new Vector<GofHostField>( );
      colsGofHostFields.addElement( ghf );
    }

    // ----------------
    // INSTANCE METHODS
    // ----------------

    /**
     * Adds a GofHostField to this column element. The new GofHostField must be on 
     * the line following the last line in this column element.
     * @param y   The line on which this column element is located.
     * @param ghf The GofHostField to add.
     */
    private void addGofHostField( int y, GofHostField ghf )
    {
      height = y - startLine + 1;

      colsGofHostFields.addElement( ghf );
    }

    /**
     * Marks all the GofHostFields in this column element as processed.
     */
    private void markAsProcessed( )
    {
      for( int i = 0, s = colsGofHostFields.size( ); i < s; i++ )
      {
        GofHostField ghf = colsGofHostFields.elementAt( i );
        ghf.hasBeenProcessed = true;
      }
    }

    /**
     * Checks if the column is editable. The column is considered editable if it has one
     * ore more fields that is not protected.
     * @return <code>true</code> if the column is editable, <code>false</code> otherwise.
     */
    private boolean isEditable( )
    {
      boolean isEditable = false;
      for( int i = 0, s = colsGofHostFields.size( ); i < s; i++ )
      {
        GofHostField ghf = colsGofHostFields.elementAt( i );
        if( ghf.isProtected( ) == false )
          isEditable = true;
      }
      return isEditable;
    }

    /**
     * Gets the header text from this column element. Depending on the offset,
     * the text should be combined from the equal number of GofHostFiels.
     * @return The header text.
     */
    private String getHeadText( )
    {
      String text = "";
      GofHostField ghf;
      int i = 0;
      while( i < offset )
      {
        ghf = colsGofHostFields.elementAt( i );
        if( text.equals( "" ) == false )
          text = text + "\r\n" + ghf.getText( );
        else
          text = ghf.getText( );
        ghf.hasBeenProcessed = true;
        i++;
      }
      return text;
    }

    /**
     * Gets the text for this column element. If the column element contains several lines, the text
     * from each line will be separated by CR + LF.
     * @param  yOffset The first line in this column element that the header should be read from.
     * @param  x       The character column to start reading the header from.
     * @param  cx      The width of the header text.
     * @return The text from the column element.
     */
    private String getText( int yOffset, int x, int cx )
    {
      String text = "";

      for( int i = yOffset, s = colsGofHostFields.size( ); i < s; i++ )
      {
        GofHostField ghf = colsGofHostFields.elementAt( i );
        int ghfX = ghf.getX( );
        int ghfCx = ghf.getCx( );
        String ghfText = ghf.getText( );
        if( ghfX <= x )
        {
          if( ghfX + ghfCx >= x + cx )
          {
            if( text.equals( "" ) )
              text = ghfText.substring( x - ghfX, x - ghfX + cx );
            else
              text = text + "\r\n" + ghfText.substring( x - ghfX, x - ghfX + cx );
            ghf.hasBeenProcessed = true;
          }
          else if( x - ghfX < ghfText.length( ) )
          {
            if( text.equals( "" ) )
              text = ghfText.substring( x - ghfX );
            else
              text = text + "\r\n" + ghfText.substring( x - ghfX );
            ghf.hasBeenProcessed = true;
          }
        }
      }
      return text;
    }
  }

  // ==================================================================
  // GofListElement subclass
  // ==================================================================

  /**
   * The GofListElement is a private inner class that is used to store information 
   * about the list elements created during the identification process of the list 
   * box. A list element consists of one or more column elements, where all column 
   * elements start on the same line, and has the same height.
   */
  private class GofListElement
  {
    // ------------------
    // INSTANCE VARIABLES
    // ------------------

    /**
     * A Vector containing all the column elements in the list element.
     */
    private Vector<GofColumnElement> listsGofColumnElements;

    /**
     * The start column on the host screen for this list element.
     */
    private int startCol;

    /** 
     * The start line on the host screen for this list element.
     */
    private int startLine;

    /*
     * The width in characters, on the host screen, for this list element.
     */
    //private int width;

    /**
     * The height in lines for this list element.
     */
    private int height;

    // -----------
    // CONSTRUCTOR
    // -----------

    /**
     * Creates a new list element.
     * @param x   The start column for this list element.
     * @param y   The start line for this list element.
     * @param gce The first column element in this list element.
     */
    private GofListElement( int x, int y, GofColumnElement gce )
    {
      startCol = x;
      startLine = y;
      //width = gce.width;
      height = gce.height;

      listsGofColumnElements = new Vector<GofColumnElement>( );
      listsGofColumnElements.addElement( gce );
    }

    // ----------------
    // INSTANCE METHODS
    // ----------------

    /**
     * Adds a column element to this list element. Will first check if it is the same column element
     * as the last column element already in the list element.
     * @param gce The new column element to add.
     */
    private void addGofColumnElement( GofColumnElement gce )
    {
      GofColumnElement lastGce;
      try
      {
        lastGce = listsGofColumnElements.lastElement( );
      }
      catch( NoSuchElementException e )
      {
        lastGce = null;
      }
      if( lastGce.equals( gce ) == false )
      {
        //width = gce.startCol + gce.width - startCol + 1;
        listsGofColumnElements.addElement( gce );
      }
    }

    /**
     * Gets the number of columns in the list box element.
     * @return The number of columns.
     */
    private int colCount( )
    {
      return listsGofColumnElements.size( );
    }

    /**
     * Checks if the leftmost column has any column element to the left side.
     * <p>
     * A column element is considered to be on the left side, if it starts in a column to the left
     * of the list's startcolumn, and if one of the following criteria matches:
     * <ul>
     *   <li>
     *     A column element that starts above the list's first line, bat that stretches to, or past 
     *     the first line in the list.
     *   </li>
     *   <li>
     *     A column element that starts between the list's first line and it's last line.
     *   </li>
     * </ul>
     * @param gceMap An arry of vectors containing all the columnelements on the screen.
     * @return <code>true</code> if the selection column has no column element to the left,
     *         <code>false</code> otherwise.
     */
    @SuppressWarnings("rawtypes")
    private boolean isLeftColLeftmost( Vector[] gceMap )
    {
      for( int i = 0; i < startCol; i++ )
      {
        Vector v = gceMap[i];
        for( int j = 0, s = v.size( ); j < s; j++ )
        {
          GofColumnElement gce = ( GofColumnElement )v.elementAt( j );
          if( gce.startLine < startLine && gce.startLine + gce.height >= startLine )
            return false;
          else if( gce.startLine >= startLine && gce.startLine < startLine + height )
            return false;
        } 
      }
      return true;
    }
  }

  /**
   * Parses the layout string into separate layout commands. Valid separators 
   * are blanks and commas.
   * @param layoutStr The layout string from gof.ini
   * @param layout    A <code>Vector</code> where the layout commands will be stored.
   */
  private void parseLayoutString( String layoutStr, Vector<String> layout )
  {
    if( layoutStr == null )
      return;

    StringTokenizer st = new StringTokenizer( layoutStr, ", " );
    while( st.hasMoreTokens( ) )
    {
      String s = st.nextToken( ).trim( );
      if( s.equals( "" ) == false )
        layout.addElement( s );
    }
  }

  /**
   * Checks if a layout command is in the <code>Vector</code> containing the requested 
   * layout commands.
   * @param str The layout command to check.
   * @return <code>true</code> if the layout command was in the list, 
   *         <code>false</code> otherwise.
   */
  private boolean isReqLayout( String str )
  {
    boolean isReq = false;

    for( int i = 0, s = layout.size( ); i < s; i++ )
    {
      String elem = layout.elementAt( i );
      if( str.equals( elem ) == true )
      {
        isReq = true;
        break;
      }
    }
    return isReq;
  }
}