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

import java.util.StringTokenizer;
import java.util.Vector;

/**
 * This class identifies spin button controls for the Gui-on-the-fly, from unused 
 * GofHostFields. Will only spin buttons with numerical ranges. 
 * @author J. Bergström
 */
public class GofSpinButtonIdentifier extends GofControlIdentifierAdapter implements PhantomControlType
{
  // ------------------
  // INSTANCE VARIABLES
  // ------------------

  /**
   * The start indicator for a range specification.
   * <p>
   * Defaults to "(".
   */
  private String spinRangeStart = "(";

  /**
   * The end indicator for a range specification.
   * <p>
   * Defaults to ")".
   */
  private String spinRangeEnd = ")";

  /**
   * The separator between the start and end values.
   * <p>
   * Defaults to "-".
   */
  private String spinValueSep = "-";

  /**
   * Flag indicating where to search for the spin button, and in what order if multiple search.
   * <p>
   * The following values are valid:
   * <pre>
   *   0  Do not search for spin button.
   *   1  Only search the preceding lead text.
   *   2  Only search the trailing lead text.
   *   3  First search the preceding, then the trailing lead text.
   *   4  First search the trailing, then the preceding lead text.
   * </pre>
   */
  private int spinSearch = 0;

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

  /**
   * Indicates the way the spin button's layout will be created.
   * Valid values are:
   * <pre>
   *    DEFAULT
   *    FONT
   *    COLOR
   *    FONTANDCOLOR
   * </pre>
   */
  private String layout;
  
  /*
   * Flag indicating if we should always use an editable or un-editable spin button.
   * Defaults to un-editable.
   */
  //private boolean isAlwaysEditable = false;

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

  /**
   * Loads setting for the controll from the server ini-file.
   * <p>
   * For the identification of spin buttons, the following settings are used:
   * <pre>
   *    spinrangestart    The start indicator for a value range.
   *    spinrangeend      The end indicator for a value range.
   *    spinvaluesep      The separator between the range values.
   * </pre>
   * There are also several possibilities of where the spin button indicator can 
   * be placed relative to the entry field that will be replaced by the spin button. 
   * The indicator can either be in the preceding lead text or in the trailing 
   * lead text.
   * <p>
   * The setting spinsearch will tell this class where to look for the spin button 
   * indicator, and in what order to search if multiple places are to be searched.
   * <p>
   * Valid values are:
   * <pre>
   *    pre    Search the preceding lead text.
   *    post   Search the trailing lead text.
   * </pre>
   * It is also possible to force the spin button to always be created as an editable
   * spin button. This is specified in the spinalwayseditable setting.
   * <pre>
   *    spinalwayseditable=1
   * </pre>
   * 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 spinfieldfiller, and 
   * if the filler character were the underscore character, it would look like this:
   * <pre>
   *    spinfieldfiller=_
   * </pre>
   * This class also uses a setting that affects the look of the spin buttons. This 
   * is the spinlayout setting. Valid values for this setting are:
   * <pre>
   *    spinlayout=DEFAULT
   *    spinlayout=FONT
   *    spinlayout=COLOR
   *    spinlayout=FONTANDCOLOR
   * </pre>
   * DEFAULT means that no settings are taken from the template panel; default values will be 
   * used instead. 
   * <p>
   * FONT means that the font is taken from template panel, from a combination box with the 
   * id=SB or SBE (SB is for non-editable combination boxes, SBE is for editable combination 
   * boxes). If this control cannot be found, or if it is not a combination box control, default 
   * values will be used.
   * <p>
   * COLOR means that the color is taken from template panel, from a combination box with the 
   * id=SB or SBE (SB is for non-editable combination boxes, SBE is for editable combination 
   * boxes). If this control cannot be found, or if it is not a combination box control, default 
   * values will be used.
   * <p>
   * FONTANDCOLOR means that the font and color are taken from template panel, from a combination 
   * box with the id=SB or SBE (SB is for non-editable combination boxes, SBE is for editable 
   * combination boxes). If this control cannot be found, or if it is not a combination box control, 
   * default values will be used.,
   * <p>
   * Any other value will be treated as DEFAULT.
   * @param confFile The server ini file.
   */
  @Override
  public void getControlSettings( IniFile confFile, String subsection )
  {
    String setting;
    setting = confFile.getData( subsection, "spinrangestart" );
    if( setting != null )
      spinRangeStart = setting;
    setting = confFile.getData( subsection, "spinrangeend" );
    if( setting != null )
      spinRangeEnd = setting;
    setting = confFile.getData( subsection, "spinvaluesep" );
    if( setting != null )
      spinValueSep = setting;

    setting = confFile.getData( subsection, "spinsearch" );
    spinSearch = parseSpinSearch( setting );

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

    layout = confFile.getData( subsection, "spinlayout" );
    if( layout == null )
      layout = "DEFAULT";
    /*
    setting = confFile.getData( subsection, "spinalwayseditable" );
    if( setting != null )
    {
      if( setting.equals( "1" ) == true )
        isAlwaysEditable = true;
    }*/
  }

  /**
   * Identifies all the spin-button controls from the <code>GofHostFields</code>.
   * <p>
   * The identification of spin buttons are done by searching for a lead text that 
   * indicates an entry field that could be replaced by a spin button. A lead text 
   * indicating a spin button should have a start indicator, an end indicator and 
   * two values separated by a separator. The start indicator is usually a start 
   * parenthesis, the end indicator is usually an end parenthesis, and the separator 
   * is usually a hyphen. But these are configurable in the configuration file.
   * <p>
   * After a lead text has been identified to hold a spin button indicator, it checks 
   * if the next or previous field is an entry field, and that it is on the same line. 
   * There is a configuration that determines if the lead text should be before the 
   * entry field, or after, or if both are accepted, and if so, in which order to search.
   * @param gofRuntime        The GuiOnTheFlyRuntime instance.
   * @param areaIdentifier    The areaIdentifier in which the controls 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.
   */
  @Override
  public void identifyCtrls( GuiOnTheFlyRuntime gofRuntime,
                             GofHostAreaIdentifier areaIdentifier, 
                             PhantomHostScreen phantomHostScreen, 
                             HostScreen hostScreen,
                             PhantomPanelData templPanel, 
                             PhantomPanelData newPanel,
                             int offsetX,
                             int offsetY )
  {
    this.templPanel = templPanel;

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

    for( int i = 0, s = gofHostFields.size( ); i < s; i++ )
    {
      GofHostField gofHostField = gofHostFields.elementAt( i );
      if( gofHostField.hasBeenProcessed == false && gofHostField.isProtected( ) == true && gofHostField.isEmpty( ) == false )
      {
        String text = gofHostField.getText( );
        int sp = text.indexOf( spinRangeStart );
        int ep = text.indexOf( spinRangeEnd, sp + 1 );
        if( sp > -1 && ep > -1 )
        {
          String spintext = text.substring( sp + 1, ep );
          int vseppos = spintext.indexOf( spinValueSep );
          if( vseppos > -1 )
          {
            int[] range = getMaxAndMinValue( spintext, vseppos );
            if( range[0] > 0 && range[1] > 0 )
            {
              boolean wasFound;
              boolean doLoop = true;
              int curSpinSearch = spinSearch;
              int j = 0;
              while( doLoop )
              {
                switch( curSpinSearch )
                {
                  case 1:
                  case 3:
                    j++;
                    break;
                  case 2:
                  case 4:
                    j--;
                    break;
                  default:
                    doLoop = false;
                }
              
                int curElement = i + j;
                if( gofHostFields.size( ) > curElement && curElement > 0 )
                {
                  GofHostField nxtGofHostField = gofHostFields.elementAt( curElement );
                  if( gofHostField.getY( ) == nxtGofHostField.getY( ) )
                  {
                    if( nxtGofHostField.isProtected( ) == false && nxtGofHostField.isEmpty( ) == false )
                    {
                      wasFound = convertFieldToSpinb( range, 
                                                      nxtGofHostField, 
                                                      areaIdentifier, 
                                                      phantomHostScreen, 
                                                      hostScreen, 
                                                      newPanel, 
                                                      offsetX, 
                                                      offsetY );
                      if( wasFound == true )
                        doLoop = false;
                      else
                      {
                        switch( curSpinSearch )
                        {
                          case 3:
                            j = 0;
                            curSpinSearch = 2;
                            break;
                          case 4:
                            j = 0;
                            curSpinSearch = 1;
                            break;
                          default:
                            doLoop = false;
                        }
                      }
                    }
                    else
                    {
                      switch( curSpinSearch )
                      {
                        case 3:
                          j = 0;
                          curSpinSearch = 2;
                          break;
                        case 4:
                          j = 0;
                          curSpinSearch = 1;
                          break;
                      }
                    }
                  }
                  else
                    doLoop = false; // We have switched line, don't continue.
                }
                else
                  doLoop = false;
              }
            }
          }
        }
      }
    }
  }

  /**
   * This method tries to found numeric values on each side of the spin-button value separator.
   * @param text   The text string that hopefully includes the min and max value.
   * @param seppos The position of spin value separator in the string.
   * @return An array of two elements containing the min and max value, or zero for both the 
   *         min and max value.
   */
  private int[] getMaxAndMinValue( String text, int seppos )
  {
    int[] values = new int[2];
    values[0] = 0;
    values[1] = 1;

    // Check if separator is first or last character, if so, no range.

    if( seppos == 0 || seppos == text.length( ) - 1 )
      return values;

    String startNum = "";
    String endNum = "";

    // Get start value as string.

    int p = seppos - 1;
    char c = text.charAt( p );
    while( c >= '0' && c <= '9' )
    {
      startNum = text.substring( p, p + 1 ) + startNum;
      p--;
      if( p < 0 )
        break;
      c = text.charAt( p );
    }

    // If start value equals empty string, no range.

    if( startNum.equals( "" ) == true )
      return values;

    // Get end value as string.

    p = seppos + 1;
    c = text.charAt( p );
    while( c >= '0' && c <= '9' )
    {
      endNum = text.substring( p, p + 1 ) + endNum;
      p++;
      if( p >= text.length( ) )
        break;
      c = text.charAt( p );
    }

    if( endNum.equals( "" ) == true )
      return values;

    // Try and convert the strings representing the start and end value into int values.

    int v1, v2;
    try
    {
      v1 = Integer.valueOf( startNum ).intValue( );
      v2 = Integer.valueOf( endNum ).intValue( );
    }
    catch( NumberFormatException e )
    {
      values[0] = 0;
      values[1] = 0;
      return values;
    }
    if( v1 < v2 )
    {
      values[0] = v1;
      values[1] = v2;
    }
    else
    {
      values[0] = v2;
      values[1] = v1;
    }

    return values;
  }

  /**
   * Tries to convert an entry field to a spin button.
   * <p>
   * Before the creation of the PhantomCComboBox, a check will be done to se if the text in the host field
   * is included in the list.
   * @param areaIdentifier    The areaIdentifier in which the controls 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           Offset in columns used for popup windows.
   * @param offsetY           Offset in lines used for popup windows.
   * @return <code>true</code> if the GofHostField was converted to a spin button box,
   *         <code>false</code> otherwise.
   */
  private boolean convertFieldToSpinb( int[] range, 
                                       GofHostField gofHostField,
                                       GofHostAreaIdentifier areaIdentifier, 
                                       PhantomHostScreen phantomHostScreen, 
                                       HostScreen hostScreen, 
                                       PhantomPanelData newPanel,
                                       int offsetX,
                                       int offsetY )
  {
    if( gofHostField.hasBeenProcessed == false && 
        gofHostField.isProtected( ) == false && 
        gofHostField.isEmpty( ) == false )
    {
      HostField hf = gofHostField.getHostField( );

      int x = gofHostField.getX( );
      int y = gofHostField.getY( );
      int cx = gofHostField.getCx( );
      //String orgtext = gofHostField.getText( );

      // Get template values
      /*
      int font = -1;
      int foregroundColor = -1;
      PhantomControl templateControl = null;
      if( isAlwaysEditable == false )
        templateControl = templPanel.getControlFromID( "SB" );
      else
        templateControl = templPanel.getControlFromID( "SBE" );
      
      if( layout.equals( "FONT" ) )
      {
        if( templateControl != null && templateControl.controlBase.type == CTRLTYPE_SPIN )
          font = ( ( PhantomCSpinButton )templateControl ).font;
      }
      else if( layout.equals( "COLOR" ) )
      {
        if( templateControl != null && templateControl.controlBase.type == CTRLTYPE_SPIN )
          foregroundColor = ( ( PhantomCSpinButton )templateControl ).getForegroundColor();
      }
      else if( layout.equals( "FONTANDCOLOR" ) )
      {
        if( templateControl != null && templateControl.controlBase.type == CTRLTYPE_SPIN )
        {
          font = ( ( PhantomCSpinButton )templateControl ).font;
          foregroundColor = ( ( PhantomCSpinButton )templateControl ).getForegroundColor();
        }
      }*/

      // Create the base control.

      PhantomControlBase bc = new PhantomControlBase( GOF_MARGINX + ( x - offsetX ) * GOF_STEPX,
                                                      GOF_MARGINY + ( y - offsetY ) * GOF_STEPY,
                                                      cx * GOF_GUIUNITX + 15,
                                                      GOF_GUIUNITY,
                                                      CTRLTYPE_SPIN );

      // Create the PhantomHostField.

      PhantomHostField phf = new PhantomHostField( phantomHostScreen, hostScreen, hf );
      if( spinFieldFiller != null )
      {
        phf.filler = spinFieldFiller.charAt( 0 );
        phf.flags = PhantomHostField.FORMAT_STRIPEND;
      }
      phantomHostScreen.addHostField( phf );

      // Create the spin button.

      PhantomCSpinButton sb = new PhantomCSpinButton( newPanel, bc, phf, range );

      // Add the spin button.

      newPanel.addControl( sb );
      areaIdentifier.addControl( sb );
      gofHostField.hasBeenProcessed = true;
      return true;
    }
    return false;
  }

  /**
   * Parses the spin search setting, and converts into a flag value.
   * @param setting The setting from the server.ini file.
   * @return The value for the spin search flag.
   */
  private int parseSpinSearch( String setting )
  {
    if( setting == null || setting.equals( "" ) )
      return 0;

    int flag = 0;
    StringTokenizer st = new StringTokenizer( setting, ", " );
    while( st.hasMoreTokens( ) )
    {
      String s = st.nextToken( );
      switch( flag )
      {
        case 1:
          if( s.equals( "pre" ) )
            flag = 1;
          else if( s.equals( "post" ) )
            flag = 3;
          break;
        case 2:
          if( s.equals( "pre" ) )
            flag = 4;
          else if( s.equals( "post" ) )
            flag = 2;
          break;
        default:
          if( s.equals( "pre" ) )
            flag = 1;
          else if( s.equals( "post" ) )
            flag = 2;
      }
    }
    return flag;
  }
}