Package nl.lxtreme.jvt220.terminal.vt220

Source Code of nl.lxtreme.jvt220.terminal.vt220.VT220Terminal$GraphicSetState

/**
* jVT220 - Java VT220 terminal emulator.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package nl.lxtreme.jvt220.terminal.vt220;


import static java.awt.event.KeyEvent.*;
import static nl.lxtreme.jvt220.terminal.vt220.VT220Parser.*;

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

import nl.lxtreme.jvt220.terminal.*;
import nl.lxtreme.jvt220.terminal.vt220.CharacterSets.CharacterSet;
import nl.lxtreme.jvt220.terminal.vt220.CharacterSets.GraphicSet;
import nl.lxtreme.jvt220.terminal.vt220.VT220Parser.CSIType;
import nl.lxtreme.jvt220.terminal.vt220.VT220Parser.VT220ParserHandler;


/**
* Represents a VT100 terminal implementation.
*/
public class VT220Terminal extends AbstractTerminal implements VT220ParserHandler
{
  // INNER TYPES

  /**
   * Provides the current state of the graphic set.
   */
  static class GraphicSetState
  {
    // VARIABLES

    private final GraphicSet[] m_graphicSets;
    private GraphicSet m_gl;
    private GraphicSet m_gr;
    private GraphicSet m_glOverride;

    // CONSTRUCTORS

    /**
     * Creates a new {@link GraphicSetState} instance.
     */
    public GraphicSetState()
    {
      m_graphicSets = new GraphicSet[4];
      for ( int i = 0; i < m_graphicSets.length; i++ )
      {
        m_graphicSets[i] = new GraphicSet( i );
      }

      resetState();
    }

    // METHODS

    /**
     * Designates the given graphic set to the character set designator.
     *
     * @param graphicSet
     *          the graphic set to designate;
     * @param designator
     *          the designator of the character set.
     */
    public void designateGraphicSet( GraphicSet graphicSet, char designator )
    {
      graphicSet.setDesignation( CharacterSet.valueOf( designator ) );
    }

    /**
     * Returns the (possibly overridden) GL graphic set.
     *
     * @return the GL graphic set, never <code>null</code>.
     */
    public GraphicSet getGL()
    {
      GraphicSet result = m_gl;
      if ( m_glOverride != null )
      {
        result = m_glOverride;
        m_glOverride = null;
      }
      return result;
    }

    /**
     * Returns the GR graphic set.
     *
     * @return the GR graphic set, never <code>null</code>.
     */
    public GraphicSet getGR()
    {
      return m_gr;
    }

    /**
     * Returns the current graphic set (one of four).
     *
     * @param index
     *          the index of the graphic set, 0..3.
     * @return a graphic set, never <code>null</code>.
     */
    public GraphicSet getGraphicSet( int index )
    {
      return m_graphicSets[index % 4];
    }

    /**
     * Returns the mapping for the given character.
     *
     * @param ch
     *          the character to map.
     * @return the mapped character.
     */
    public char map( char ch )
    {
      return CharacterSets.getChar( ch, getGL(), getGR() );
    }

    /**
     * Overrides the GL graphic set for the next written character.
     *
     * @param index
     *          the graphic set index, >= 0 && < 3.
     */
    public void overrideGL( int index )
    {
      m_glOverride = getGraphicSet( index );
    }

    /**
     * Resets the state to its initial values.
     */
    public void resetState()
    {
      for ( int i = 0; i < m_graphicSets.length; i++ )
      {
        m_graphicSets[i].setDesignation( CharacterSet.valueOf( ( i == 1 ) ? '0' : 'B' ) );
      }
      m_gl = m_graphicSets[0];
      m_gr = m_graphicSets[1];
      m_glOverride = null;
    }

    /**
     * Selects the graphic set for GL.
     *
     * @param index
     *          the graphic set index, >= 0 && <= 3.
     */
    public void setGL( int index )
    {
      m_gl = getGraphicSet( index );
    }

    /**
     * Selects the graphic set for GR.
     *
     * @param index
     *          the graphic set index, >= 0 && <= 3.
     */
    public void setGR( int index )
    {
      m_gr = getGraphicSet( index );
    }
  }

  /**
   * Denotes the various types of responses we can sent from this terminal.
   */
  static enum ResponseType
  {
    ESC, CSI, OSC, SS3;
  }

  /**
   * Contains the saved state of this terminal.
   */
  static class StateHolder
  {
    // VARIABLES

    private final CharacterSet[] m_graphicSetDesignations;

    private int m_cursorIndex;
    private short m_attrs;
    private boolean m_autoWrap;
    private boolean m_originMode;
    private int m_glIndex;
    private int m_grIndex;
    private int m_glOverrideIndex;

    // CONSTRUCTORS

    /**
     * Creates a new {@link StateHolder} instance.
     */
    public StateHolder()
    {
      m_graphicSetDesignations = new CharacterSet[4];
      m_cursorIndex = 0;
      m_autoWrap = true;
      m_originMode = false;
      m_glIndex = 0;
      m_grIndex = 1;
      m_glOverrideIndex = -1;
    }

    // METHODS

    public int restore( VT220Terminal terminal )
    {
      terminal.m_textAttributes.setAttributes( m_attrs );
      terminal.setAutoWrap( m_autoWrap );
      terminal.setOriginMode( m_originMode );

      GraphicSetState gss = terminal.m_graphicSetState;
      for ( int i = 0; i < gss.m_graphicSets.length; i++ )
      {
        gss.m_graphicSets[i].setDesignation( m_graphicSetDesignations[i] );
      }
      gss.setGL( m_glIndex );
      gss.setGR( m_grIndex );

      if ( m_glOverrideIndex >= 0 )
      {
        gss.overrideGL( m_glOverrideIndex );
      }

      return m_cursorIndex;
    }

    public void store( VT220Terminal terminal )
    {
      m_cursorIndex = terminal.getAbsoluteCursorIndex();
      m_attrs = terminal.m_textAttributes.getAttributes();
      m_autoWrap = terminal.isAutoWrapMode();
      m_originMode = terminal.isOriginMode();

      GraphicSetState gss = terminal.m_graphicSetState;
      m_glIndex = gss.m_gl.getIndex();
      m_grIndex = gss.m_gr.getIndex();

      m_glOverrideIndex = -1;
      if ( gss.m_glOverride != null )
      {
        m_glOverrideIndex = gss.m_glOverride.getIndex();
      }

      for ( int i = 0; i < gss.m_graphicSets.length; i++ )
      {
        m_graphicSetDesignations[i] = gss.m_graphicSets[i].getDesignation();
      }
    }
  }

  /**
   * Provides a VT220-compatible key mapper.
   */
  final class VT220KeyMapper implements IKeyMapper
  {
    // METHODS

    /**
     * {@inheritDoc}
     */
    @Override
    public String map( int keyCode, int modifiers )
    {
      switch ( keyCode )
      {
        case VK_UP:
          if ( isVT52mode() )
          {
            // Always ESC A
            return createResponse( ResponseType.ESC, "A" );
          }
          // CSI A in normal mode, SS3 A in application mode...
          return createResponse( isApplicationCursorKeys() ? ResponseType.SS3 : ResponseType.CSI, "A" );

        case VK_DOWN:
          if ( isVT52mode() )
          {
            // Always ESC B
            return createResponse( ResponseType.ESC, "B" );
          }
          // CSI B in normal mode, SS3 B in application mode...
          return createResponse( isApplicationCursorKeys() ? ResponseType.SS3 : ResponseType.CSI, "B" );

        case VK_RIGHT:
          if ( isVT52mode() )
          {
            // Always ESC C
            return createResponse( ResponseType.ESC, "C" );
          }
          // CSI C in normal mode, SS3 C in application mode...
          return createResponse( isApplicationCursorKeys() ? ResponseType.SS3 : ResponseType.CSI, "C" );

        case VK_LEFT:
          if ( isVT52mode() )
          {
            // Always ESC D
            return createResponse( ResponseType.ESC, "D" );
          }
          // CSI D in normal mode, SS3 D in application mode...
          return createResponse( isApplicationCursorKeys() ? ResponseType.SS3 : ResponseType.CSI, "D" );

        case VK_PAGE_DOWN:
          // Simulates NEXT SCREEN, CSI 6~
          return map( "\033[6~", null );

        case VK_PAGE_UP:
          // Simulates PREVIOUS SCREEN, CSI 5~
          return map( "\033[5~", null );

        case VK_HOME:
          // CSI H in normal mode, SS3 H in application mode...
          return map( "\033[H", "H" );

        case VK_END:
          // CSI F in normal mode, SS3 F in application mode...
          return map( "\033[F", "F" );

        case VK_NUMPAD0:
          return map( "0", "p", "0", "?p" );

        case VK_NUMPAD1:
          return map( "1", "q", "1", "?q" );

        case VK_NUMPAD2:
          return map( "2", "r", "2", "?r" );

        case VK_NUMPAD3:
          return map( "3", "s", "3", "?s" );

        case VK_NUMPAD4:
          return map( "3", "t", "4", "?t" );

        case VK_NUMPAD5:
          return map( "5", "u", "5", "?u" );

        case VK_NUMPAD6:
          return map( "6", "v", "6", "?v" );

        case VK_NUMPAD7:
          return map( "7", "w", "7", "?w" );

        case VK_NUMPAD8:
          return map( "8", "x", "8", "?x" );

        case VK_NUMPAD9:
          return map( "9", "y", "9", "?y" );

        case VK_MINUS:
          return map( "-", "m", "-", "?m" );

        case VK_COMMA:
          return map( ",", "l", ",", "?l" );

        case VK_PERIOD:
          return map( ".", "n", ".", "?n" );

        case VK_ENTER:
          return map( "\015", "M", "\015", "?M" );

        case VK_F1:
          if ( isVT52mode() )
          {
            return createResponse( ResponseType.ESC, "P" );
          }
          if ( ( modifiers & InputEvent.ALT_DOWN_MASK ) != 0 )
          {
            // Simulate PF1 with ALT-F1...
            return createResponse( ResponseType.SS3, "P" );
          }
          return createResponse( ResponseType.CSI, "11~" );

        case VK_F2:
          if ( isVT52mode() )
          {
            return createResponse( ResponseType.ESC, "Q" );
          }
          if ( ( modifiers & InputEvent.ALT_DOWN_MASK ) != 0 )
          {
            // Simulate PF2 with ALT-F2...
            return createResponse( ResponseType.SS3, "Q" );
          }
          return createResponse( ResponseType.CSI, "12~" );

        case VK_F3:
          if ( isVT52mode() )
          {
            return createResponse( ResponseType.ESC, "R" );
          }
          if ( ( modifiers & InputEvent.ALT_DOWN_MASK ) != 0 )
          {
            // Simulate PF3 with ALT-F3...
            return createResponse( ResponseType.SS3, "R" );
          }
          return createResponse( ResponseType.CSI, "13~" );

        case VK_F4:
          if ( isVT52mode() )
          {
            return createResponse( ResponseType.ESC, "S" );
          }
          if ( ( modifiers & InputEvent.ALT_DOWN_MASK ) != 0 )
          {
            // Simulate PF4 with ALT-F4...
            return createResponse( ResponseType.SS3, "S" );
          }
          return createResponse( ResponseType.CSI, "14~" );

        case VK_F5:
          // Seems not to be mapped at all according to
          // <http://www.vt100.net/docs/vt220-rm/table3-4.html>
          if ( isVT52mode() )
          {
            // Not mapped...
            return null;
          }
          return createResponse( ResponseType.CSI, "15~" );

        case VK_F6:
          if ( isVT52mode() )
          {
            // Not mapped...
            return null;
          }
          return createResponse( ResponseType.CSI, "17~" );

        case VK_F7:
          if ( isVT52mode() )
          {
            // Not mapped...
            return null;
          }
          return createResponse( ResponseType.CSI, "18~" );

        case VK_F8:
          if ( isVT52mode() )
          {
            // Not mapped...
            return null;
          }
          return createResponse( ResponseType.CSI, "19~" );

        case VK_F9:
          if ( isVT52mode() )
          {
            // Not mapped...
            return null;
          }
          return createResponse( ResponseType.CSI, "20~" );

        case VK_F10:
          if ( isVT52mode() )
          {
            // Not mapped...
            return null;
          }
          return createResponse( ResponseType.CSI, "21~" );

        case VK_F11:
          if ( isVT52mode() )
          {
            // Escape...
            return createResponse( ResponseType.ESC, "" );
          }
          return createResponse( ResponseType.CSI, "23~" );

        case VK_F12:
          if ( isVT52mode() )
          {
            // Backspace...
            return "\b";
          }
          return createResponse( ResponseType.CSI, "24~" );

        default:
          return null;
      }
    }

    private String map( String vt100normal, String vt100application )
    {
      return map( vt100normal, vt100application, null, null );
    }

    private String map( String vt100normal, String vt100application, String vt52normal, String vt52application )
    {
      if ( isVT52mode() )
      {
        // '0' is normal mode, ESC ? p in application mode...
        return isApplicationCursorKeys() ? createResponse( ResponseType.ESC, vt52application ) : vt52normal;
      }
      // '0' in normal mode, SS3 p in application mode...
      return isApplicationCursorKeys() ? createResponse( ResponseType.SS3, vt100application ) : vt100normal;
    }
  }

  // CONSTANTS

  private static final int OPTION_132COLS = 5;
  private static final int OPTION_ENABLE_132COLS = 6;
  private static final int OPTION_8BIT = 7;
  private static final int OPTION_ERASURE_MODE = 8;
  private static final int OPTION_REVERSE_WRAP_AROUND = 9;
  private static final int OPTION_APPLICATION_CURSOR_KEYS = 10;

  // VARIABLES

  private final GraphicSetState m_graphicSetState;
  private final VT220Parser m_vt220parser;
  private final StateHolder m_savedState;

  // CONSTRUCTORS

  /**
   * Creates a new {@link VT220Terminal} instance.
   *
   * @param columns
   *          the initial number of columns in this terminal, > 0;
   * @param lines
   *          the initial number of lines in this terminal, > 0.
   */
  public VT220Terminal( final int columns, final int lines )
  {
    super( columns, lines );

    m_graphicSetState = new GraphicSetState();
    m_vt220parser = new VT220Parser();
    m_savedState = new StateHolder();

    // Make sure the terminal is in a known state...
    reset();
  }

  // METHODS

  /**
   * {@inheritDoc}
   */
  @Override
  public void handleCharacter( char ch ) throws IOException
  {
    int idx = getAbsoluteCursorIndex();

    if ( isInsertMode() )
    {
      idx = insertChars( idx, m_graphicSetState.map( ch ), 1 ) + 1;
    }
    else
    {
      idx = writeChar( idx, m_graphicSetState.map( ch ) );
    }

    updateCursorByAbsoluteIndex( idx );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void handleControl( char controlChar ) throws IOException
  {
    int idx = getAbsoluteCursorIndex();

    switch ( controlChar )
    {
      case ENQ:
      {
        // Answerback, always respond with the name of this class...
        write( getClass().getSimpleName() );
        break;
      }
      case SO:
      {
        // Shift-out (select G1 as GL)...
        m_graphicSetState.setGL( 1 );
        break;
      }

      case SI:
      {
        // Shift-in (select G0 as GL)...
        m_graphicSetState.setGL( 0 );
        break;
      }

      case BELL:
      {
        // Bell...
        Toolkit.getDefaultToolkit().beep();
        break;
      }

      case BS:
      {
        // Backspace...
        if ( isWrapped() && ( isAutoWrapMode() || isReverseWrapAround() ) )
        {
          /*
           * When reverse-autowrap mode is enabled, and a backspace is received
           * when the cursor is at the left-most column of the page, the cursor
           * is wrapped to the right-most column of the previous line. If the
           * cursor is at the top line of the scrolling region, the cursor is
           * wrapped to the right-most column of the bottom line of the
           * scrolling region. If the cursor is at the top line of terminal
           * window, the cursor is wrapped to the right-most column of the
           * bottom line of the terminal window.
           */
          idx -= isWrapped() ? 2 : 1;
          if ( idx < getAbsoluteIndex( 0, getFirstScrollLine() ) )
          {
            idx = getAbsoluteIndex( getWidth(), getLastScrollLine() );
          }
        }
        else
        {
          /*
           * When reverse-autowrap mode is disabled, and a backspace is received
           * when the cursor is at the left-most column of the page, the cursor
           * remains at that position.
           */
          idx -= ( idx % getWidth() == 0 ? 0 : 1 );
        }
        break;
      }

      case TAB:
      {
        /*
         * (Horizontal) Tab. The cursor moves right to the next tab stop. If
         * there are no further tab stops set to the right of the cursor, the
         * cursor moves to the right-most column of the current line.
         */
        idx += getTabulator().getNextTabWidth( idx % getWidth() );
        break;
      }

      case VT:
      case LF:
      case FF:
      {
        /*
         * Line feed, vertical tab, or form feed. The cursor moves to the same
         * column of the next line. If the cursor is in the bottom-most line of
         * the scrolling region, the scrolling region scrolls up one line. Lines
         * scrolled off the top of the scrolling region are lost. Blank lines
         * with no visible character attributes are added at the bottom of the
         * scrolling region.
         */
        int row = ( idx / getWidth() );
        if ( row >= getLastScrollLine() )
        {
          scrollUp( 1 );
        }
        else
        {
          idx += getWidth();
        }
        if ( !isAutoNewlineMode() )
        {
          break;
        }
        // In case of auto-newline mode: fall through to CR...
      }

      case CR:
      {
        /*
         * Carriage return. The cursor moves to the left-most column of the
         * current line.
         */
        if ( isWrapped() && isAutoWrapMode() )
        {
          // In case we're already wrapped around to the next line we need to
          // undo this and go back to the previous line instead...
          idx--;
        }
        idx -= ( idx % getWidth() );
        break;
      }

      default:
      {
        log( "Unknown control character: " + ( int )controlChar );
        break;
      }
    }

    updateCursorByAbsoluteIndex( idx );
    resetWrapped();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void handleCSI( CSIType type, int... parameters ) throws IOException
  {
    int idx = getAbsoluteCursorIndex();
    switch ( type )
    {
      case ICH: // @
      {
        // Insert N (blank) Character(s) (default = 1)
        int count = parameters[0];
        idx = insertChars( idx, ' ', count );
        break;
      }

      case SL: // [ ]@
      {
        // Scroll left N character(s) (default = 1)
        int count = parameters[0];
        for ( int r = getFirstScrollLine(), last = getLastScrollLine(); r < last; r++ )
        {
          deleteChars( getAbsoluteIndex( 0, r ), count );
        }
        break;
      }

      case CUU: // A
      {
        // Moves the cursor up N lines in the same column. The cursor stops at
        // the top margin.
        int n = parameters[0];

        int col = idx % getWidth();
        int row = Math.max( getFirstScrollLine(), ( idx / getWidth() ) - n );

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case SR: // [ ]A
      {
        // Scroll right N character(s) (default = 1)
        int count = parameters[0];
        for ( int r = getFirstScrollLine(), last = getLastScrollLine(); r < last; r++ )
        {
          insertChars( getAbsoluteIndex( 0, r ), ' ', count );
        }
        break;
      }

      case CUD: // B
      {
        // Moves the cursor down N lines in the same column. The cursor stops at
        // the bottom margin.
        int n = parameters[0];

        int col = idx % getWidth();
        int row = Math.min( getLastScrollLine(), ( idx / getWidth() ) + n );

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case CUF: // C
      {
        // Moves the cursor right N columns. The cursor stops at the right
        // margin.
        int n = parameters[0];

        int col = Math.min( getWidth() - 1, ( idx % getWidth() ) + n );
        int row = idx / getWidth();

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case CUB: // D
      {
        // Moves the cursor left N columns. The cursor stops at the left margin.
        int n = parameters[0];

        if ( isAutoWrapMode() && isWrapped() )
        {
          idx = Math.max( getFirstAbsoluteIndex(), idx - n );
        }
        else
        {
          int col = Math.max( 0, ( idx % getWidth() ) - n );
          int row = idx / getWidth();
          idx = getAbsoluteIndex( col, row );
        }
        break;
      }

      case CNL: // E
      {
        // Move cursor down the indicated # of rows, to column 1.
        int n = parameters[0];

        int col = 0;
        int row = Math.min( getLastScrollLine(), ( idx / getWidth() ) + n );

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case CPL: // F
      {
        // Move cursor up the indicated # of rows, to column 1.
        int n = parameters[0];

        int col = 0;
        int row = Math.max( getFirstScrollLine(), ( idx / getWidth() ) - n );

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case CHA: // G,`
      {
        // Character Position Absolute [column] (default = [row,1])
        // Cursor Character Absolute [column] (default = [row,1])
        int x = parameters[0];

        int col = Math.max( 0, Math.min( getWidth(), x - 1 ) );
        int row = Math.max( getFirstScrollLine(), Math.min( getLastScrollLine(), idx / getWidth() ) );

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case CUP: // H,f
      {
        // Cursor Position [row;column] (default = [1,1])
        int row = parameters[0] - 1;
        int col = parameters[1] - 1;

        // Movement is *relative* to origin, if enabled...
        if ( isOriginMode() )
        {
          row += getFirstScrollLine();
        }
        if ( row > getLastScrollLine() )
        {
          row = getLastScrollLine();
        }
        if ( col >= getWidth() )
        {
          col = getWidth() - 1;
        }

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case CHT: // I
      {
        // Cursor Forward Tabulation N tab stops (default = 1)
        int count = parameters[0];
        while ( count-- > 0 )
        {
          idx += getTabulator().getNextTabWidth( idx % getWidth() );
        }
        break;
      }

      case ED: // J
      {
        // Erase in Display...
        int mode = parameters[0];

        clearScreen( mode, idx, isErasureMode() );
        break;
      }

      case DECSED: // ^J
      {
        // Erase in Display...
        int mode = parameters[0];

        clearScreen( mode, idx, true /* aKeepProtectedCells */);
        break;
      }

      case EL: // K
      {
        // Clear line...
        int mode = parameters[0];

        clearLine( mode, idx, isErasureMode() );
        break;
      }

      case DECSEL: // ^K
      {
        // Clear line...
        int mode = parameters[0];

        clearLine( mode, idx, true /* aKeepProtectedCells */);
        break;
      }

      case IL: // L
      {
        // (IL) Inserts N lines at the cursor. If fewer than N lines
        // remain from the current line to the end of the scrolling
        // region, the number of lines inserted is the lesser number.
        // Lines within the scrolling region at and below the cursor move
        // down. Lines moved past the bottom margin are lost. The cursor
        // is reset to the first column. This sequence is ignored when the
        // cursor is outside the scrolling region.
        int lines = parameters[0];

        int row = idx / getWidth();
        if ( row >= getFirstScrollLine() && row < getLastScrollLine() )
        {
          int col = idx % getWidth();
         
          scrollDown( row, getLastScrollLine(), lines );

          idx -= col;
        }
        break;
      }

      case DL: // M
      {
        // (DL) Deletes N lines starting at the line with the cursor. If
        // fewer than N lines remain from the current line to the end of
        // the scrolling region, the number of lines deleted is the lesser
        // number. As lines are deleted, lines within the scrolling region
        // and below the cursor move up, and blank lines are added at the
        // bottom of the scrolling region. The cursor is reset to the
        // first column. This sequence is ignored when the cursor is
        // outside the scrolling region.
        int lines = parameters[0];

        int row = idx / getWidth();
        if ( row >= getFirstScrollLine() && row < getLastScrollLine() )
        {
          int col = idx % getWidth();
         
          scrollUp( row, getLastScrollLine(), lines );

          idx -= col;
        }
        break;
      }

      case DCH: // P
      {
        // Delete N Character(s) (default = 1)
        int count = parameters[0];

        idx = deleteChars( idx, count );
        break;
      }

      case SU: // S
      {
        // Scroll N lines up...
        int lines = parameters[0];

        scrollUp( lines );
        break;
      }

      case SD: // T
      {
        // Scroll N lines down...
        int lines = parameters[0];

        scrollDown( lines );
        break;
      }

      case ECH: // X
      {
        // Erase N Character(s) (default = 1)
        int n = parameters[0] - 1;

        int tmpIdx = idx;
        if ( tmpIdx + n > getWidth() )
        {
          n = getWidth() - ( tmpIdx % getWidth() ) - 1;
        }

        do
        {
          removeChar( tmpIdx++, isErasureMode() );
        }
        while ( n-- > 0 );
        break;
      }

      case CBT: // Z
      {
        // Cursor Backward Tabulation N tab stops (default = 1)
        int count = parameters[0];
        while ( count-- > 0 )
        {
          idx -= getTabulator().getPreviousTabWidth( idx % getWidth() );
        }
        break;
      }

      case HPR: // a
      {
        // Move cursor right the indicated # of columns.
        int n = parameters[0];

        int w = getWidth();
        int col = Math.min( w - 1, ( idx % w ) + n );
        int row = idx / w;

        idx = getAbsoluteIndex( col, row );

        break;
      }

      case REP: // b
      {
        // Repeat the preceding graphic character N times
        int count = parameters[0];
        char ch = ( char )parameters[1];

        while ( count-- > 0 )
        {
          idx = writeChar( idx, ch );
        }
        break;
      }

      case PrimaryDA: // c
      {
        // Send Device Attributes (Primary DA)
        int arg = parameters[0];
        sendDeviceAttributes( arg );
        break;
      }

      case SecondaryDA: // >c
      {
        // Send Device Attributes (Secondary DA)
        int arg = parameters[0];
        if ( arg == 0 )
        {
          writeResponse( ResponseType.CSI, ">1;123;0c" );
        }
        break;
      }

      case VPA: // d
      {
        // (VPA) Line Position Absolute [row] (default = [1,column])
        int row = parameters[0] - 1;
        int col = idx % getWidth();

        if ( row < getFirstScrollLine() )
        {
          row = getFirstScrollLine();
        }
        else if ( row > getLastScrollLine() )
        {
          row = getLastScrollLine();
        }

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case VPR: // e
      {
        // Move cursor down the indicated N of columns (default = 1).
        int n = parameters[0];

        int col = idx % getWidth();
        int row = ( idx / getWidth() ) + n;
        if ( row > getLastScrollLine() )
        {
          row = getLastScrollLine();
        }

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case TBC: // g
      {
        // Tab Clear
        int arg = parameters[0];

        if ( arg == 0 )
        {
          int col = ( idx % getWidth() );
          getTabulator().clear( col );
        }
        else if ( arg == 3 )
        {
          clearAllTabStops();
        }
        break;
      }

      case SM: // h
      {
        // Set mode
        int arg = parameters[0];
        switch ( arg )
        {
          case 4:
            // (IRM) Insert/Replace Mode
            setInsertMode( true );
            break;

          case 6:
            // (ERM) Erasure mode
            setErasureMode( false );
            break;

          case 20:
            // (LNM) Automatic Newline
            setAutoNewlineMode( true );
            break;

          default:
            log( "Unknown SET MODE: " + arg );
            break;
        }
        break;
      }

      case DECSET: // ?h
      {
        // DEC Private Mode Set
        int arg = parameters[0];
        switch ( arg )
        {
          case 1:
            // (DECCKM) Application cursor keys; default = false
            setApplicationCursorKeys( true );
            break;

          case 2:
            // (DECANM) Designate USASCII for character sets G0-G3; set VT100
            // mode.
            m_graphicSetState.resetState();
            set8bitMode( false );
            break;

          case 3:
            // (DECCOLM) switch 80/132 column mode; default = 80 columns.
            if ( isEnable132ColumnMode() )
            {
              set132ColumnMode( true );
              // Clear entire screen...
              clearScreen( 2 );
              // Reset scrolling region...
              setOriginMode( false );
              setScrollRegion( getFirstScrollLine(), getLastScrollLine() );
              // Reset cursor to first possible position...
              idx = getAbsoluteIndex( 0, getFirstScrollLine() );
            }
            break;

          case 4:
            // (DECSCLM) Smooth (Slow) Scroll; ignored...
            break;

          case 5:
            // (DECSCNM) set reverse-video mode; default = off.
            setReverse( true );
            break;

          case 6:
            // (DECOM) set origin mode; default = off.
            setOriginMode( true );

            // Reset cursor to first possible position...
            idx = getAbsoluteIndex( 0, getFirstScrollLine() );
            break;

          case 7:
            // (DECAWM) set auto-wrap mode; default = on.
            setAutoWrap( true );
            break;

          case 8:
            // (DECARM) set auto-repeat mode; ignore.
            break;

          case 25:
            // (DECTCEM) Show Cursor; default = on.
            getCursor().setVisible( true );
            break;

          case 40:
            // Allow 80 -> 132 mode switching
            setEnable132ColumnMode( true );
            break;

          case 45:
            // Reverse-wraparound Mode; default = on.
            setReverseWrapAround( true );
            break;

          default:
            log( "Unknown DEC SET PRIVATE MODE: " + arg );
            break;
        }
        break;
      }

      case MC: // i
      case DECSMC: // ?i
      {
        // Media copy; not implemented...
        break;
      }

      case HPB: // j
      {
        // Character position backward N positions (default = 1).
        int n = parameters[0];

        int col = Math.max( 0, idx % getWidth() - n );
        int row = idx / getWidth();

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case VPB: // k
      {
        // Line position backward N lines (default = 1).
        int n = parameters[0];

        int w = getWidth();
        int col = idx % w;
        int row = Math.max( getFirstScrollLine(), ( idx / w ) - n );

        idx = getAbsoluteIndex( col, row );
        break;
      }

      case RM: // l
      {
        // Reset mode
        int arg = parameters[0];
        switch ( arg )
        {
          case 4:
            // (IRM) Insert/Replace Mode
            setInsertMode( false );
            break;

          case 6:
            // (ERM) Erasure mode
            setErasureMode( true );
            break;

          case 20:
            // (LNM) Automatic Newline
            setAutoNewlineMode( false );
            break;

          default:
            log( "Unknown RESET MODE: " + arg );
            break;
        }
        break;
      }

      case DECRST: // ?l
      {
        // Reset mode / DEC Private Mode Reset...
        int arg = parameters[0];
        switch ( arg )
        {
          case 1:
            // (DECCKM) Normal cursor keys; default = false
            setApplicationCursorKeys( false );
            break;

          case 2:
            // (DECANM) Set VT52 mode.
            m_graphicSetState.resetState();
            break;

          case 3:
            // (DECCOLM) switch 80/132 column mode; default = 80
            // columns.
            set132ColumnMode( false );
            // Clear entire screen...
            clearScreen( 2 );
            // Reset scrolling region...
            setOriginMode( false );
            setScrollRegion( getFirstScrollLine(), getLastScrollLine() );
            // Reset cursor to first possible position...
            idx = getAbsoluteIndex( 0, getFirstScrollLine() );
            break;

          case 4:
            // (DECSCLM) Smooth (Slow) Scroll; ignored...
            break;

          case 5:
            // (DECSCNM) set reverse-video mode; default = off.
            setReverse( false );
            break;

          case 6:
            // (DECOM) set origin mode; default = off.
            setOriginMode( false );

            // Reset cursor to first possible position...
            idx = getAbsoluteIndex( 0, getFirstScrollLine() );
            break;

          case 7:
            // (DECAWM) set auto-wrap mode; default = on.
            setAutoWrap( false );
            break;

          case 8:
            // (DECARM) disable auto-repeat mode; ignore.
            break;

          case 25:
            // (DECTCEM) Hide Cursor; default = on.
            getCursor().setVisible( false );
            break;

          case 40:
            // Disallow 80 -> 132 mode switching
            setEnable132ColumnMode( false );
            break;

          case 45:
            // Reverse-wraparound Mode; default = on.
            setReverseWrapAround( false );
            break;

          default:
            log( "Unknown DEC RESET PRIVATE MODE: " + arg );
            break;
        }
        break;
      }

      case SGR: // m
      {
        // Set Graphics Rendering
        handleGraphicsRendering( parameters );
        break;
      }

      case DSR: // n
      {
        int arg = parameters[0];
        if ( arg == 5 )
        {
          // Status report...
          writeResponse( ResponseType.CSI, "0n" );
        }
        else if ( arg == 6 )
        {
          // Report cursor position...
          int col = ( idx % getWidth() ) + 1;
          int row = ( idx / getWidth() ) + 1;
          writeResponse( ResponseType.CSI, String.format( "%d;%dR", row, col ) );
        }
        break;
      }

      case DECSDSR: // ?n
      {
        // Device Status Report (DEC-specific)
        int arg = parameters[0];
        switch ( arg )
        {
          case 6:
            // Report Cursor Position
            int col = ( idx % getWidth() ) + 1;
            int row = ( idx / getWidth() ) + 1;
            writeResponse( ResponseType.CSI, String.format( "?%d;%dR", row, col ) );
            break;

          case 15:
            // Report Printer Status; always reports it as not ready
            writeResponse( ResponseType.CSI, "?11n" );
            break;

          case 25:
            // Report UDK status; always reports it as locked
            writeResponse( ResponseType.CSI, "?21n" );
            break;

          case 26:
            // Report keyboard status; always reports it as North American
            writeResponse( ResponseType.CSI, "?27;1;0;0n" );
            break;

          default:
            log( "Unknown/unhandled DECSDR argument: " + arg );
            break;
        }
        break;
      }

      case DECSTR: // !p
      {
        // Soft terminal reset
        softReset();
        break;
      }

      case DECSCL: // "p
      {
        // Set conformance level
        int compatibilityLevel = ( parameters[0] - 60 );
        int eightBitMode = ( parameters.length > 1 ) ? parameters[1] : 0;
        setConformanceLevel( compatibilityLevel, eightBitMode != 1 );
        break;
      }

      case DECSCA: // "q
      {
        // Select character protection attribute
        int mode = parameters[0];
        // 0 or 2 == erasable; 1 == not erasable...
        m_textAttributes.setProtected( mode == 1 );
        break;
      }

      case DECSTBM: // r
      {
        // Set Scrolling Region [top;bottom] (default = full size of window)
        int top = parameters[0];
        int bottom = parameters[1];
        if ( bottom == 0 )
        {
          bottom = getHeight();
        }
        if ( bottom > top )
        {
          setScrollRegion( top - 1, bottom - 1 );
          idx = getAbsoluteIndex( 0, getFirstScrollLine() );
        }
        break;
      }

      case RestoreDECPM: // ?r; Restore DEC Private Mode Values; xterm specific
      case DECCARA: // $r; Change attributes in rectangular area; VT420 only
      case SaveDECPM: // ?s; Save DEC Private Mode Values; xterm specific
        break;

      case WindowManipulation: // t
      {
        // dtterm window manipulation...
        int param = parameters[0];
        switch ( param )
        {
          case 4:
            // resize to height;width in pixels
            if ( parameters.length == 3 )
            {
              int height = parameters[1];
              int width = parameters[2];

              setDimensionsInPixels( width, height );
              idx = getFirstAbsoluteIndex();
            }
            break;

          case 8:
            if ( parameters.length == 3 )
            {
              // resize to height;width in chars
              int rows = parameters[1];
              int cols = parameters[2];

              setDimensions( cols, rows );
              idx = getFirstAbsoluteIndex();
            }
            break;

          case 11:
            // report window state (always fixed)
            writeResponse( ResponseType.CSI, "1t" );
            break;

          case 13:
            // report window location (always fixed)
            writeResponse( ResponseType.CSI, "3;0;0t" );
            break;

          case 14:
            // report dimensions in pixels
            int width = 0;
            int height = 0;

            ITerminalFrontend frontend = getFrontend();
            if ( frontend != null )
            {
              Dimension size = frontend.getSize();
              width = size.width;
              height = size.height;
            }

            writeResponse( ResponseType.CSI, String.format( "%d;%d;%dt", ( param - 10 ), height, width ) );
            break;

          case 18:
          case 19:
            // report dimensions in characters
            writeResponse( ResponseType.CSI, String.format( "%d;%d;%dt", ( param - 10 ), getHeight(), getWidth() ) );
            break;

          case 20:
            // report window icon's label: OSC L <text> ST
            writeResponse( ResponseType.OSC, "L\033\\" );
            break;

          case 21:
            // report window title: OSC l <text> ST
            writeResponse( ResponseType.OSC, "l\033\\" );
            break;

          default:
            if ( param >= 24 )
            {
              // resize to N lines...
              int cols = getWidth();
              int rows = param;

              setDimensions( cols, rows );
              idx = getFirstAbsoluteIndex();
            }
            break;
        }
        break;
      }

      case DECRARA: // $t; reverse attributes in rectangular area; VT420 only
      case DECCRA: // $v; copy rectangular area; VT400 only
      case DECEFR: // 'w; Enable filter rectangle; VT340 only
        break;

      case DECREQTPARM: // x
      {
        // Request Terminal Parameters
        int arg = parameters[0];

        writeResponse( ResponseType.CSI, String.format( "%d;1;1;112;112;1;0x", arg + 2 ) );
        break;
      }

      case DECFRA: // $x; Fill rectangular area; VT400 only
      case DECELR: // 'z; Enable locator reports; VT340 only
      case DECERA: // $z; Erase rectangular area; VT400 only
      case DECSLE: // '{; Select locator events; VT340 only
      case DECSERA: // ${; Selective erase rectangular area; VT400 only
      case DECRQLP: // '|; Request locator position; VT340 only
        break;

      default:
        log( "Unhandled CSI: " + type );
        break;
    }
    // Update the cursor position...
    updateCursorByAbsoluteIndex( idx );
    resetWrapped();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void handleESC( char designator, int... parameters ) throws IOException
  {
    int idx = getAbsoluteCursorIndex();

    switch ( designator )
    {
      case 'D': // IND
      {
        idx = handleIND( idx );
        break;
      }
      case 'E': // NEL
      {
        idx = handleNEL( idx );
        break;
      }
      case 'H': // HTS
      {
        handleTabSet( idx );
        break;
      }
      case 'M': // RI
      {
        idx = handleRI( idx );
        break;
      }
      case 'N': // SS2
      {
        handleSS2();
        break;
      }
      case 'O': // SS3
      {
        handleSS3();
        break;
      }
      case 'V': // SPA
      {
        handleSPA();
        break;
      }
      case 'W': // EPA
      {
        handleEPA();
        break;
      }
      case 'Z': // DECID
      {
        sendDeviceAttributes( 0 );
        break;
      }
      case 'c': // RIS
      {
        reset();
        idx = getFirstAbsoluteIndex();
        break;
      }
      case 'n': // LS2
      {
        // Invoke the G2 Character Set as GL
        m_graphicSetState.setGL( 2 );
        break;
      }
      case 'o': // LS3
      {
        // Invoke the G3 Character Set as GL
        m_graphicSetState.setGL( 3 );
        break;
      }
      case '|': // LS3R
      {
        // Invoke the G3 Character Set as GR
        m_graphicSetState.setGR( 3 );
        break;
      }
      case '}': // LS2R
      {
        // Invoke the G2 Character Set as GR
        m_graphicSetState.setGR( 2 );
        break;
      }
      case '~': // LS1R
      {
        // Invoke the G1 Character Set as GR
        m_graphicSetState.setGR( 1 );
        break;
      }
      case '7': // DECSC
      {
        saveCursor( idx );
        break;
      }
      case '8': // DECRC
      {
        idx = restoreCursor();
        break;
      }
      case ' ':
      {
        char arg = ( char )parameters[0];
        switch ( arg )
        {
          case 'F':
            // 7-bit controls...
            set8bitMode( false );
            break;

          case 'G':
            // 8-bit controls...
            set8bitMode( true );
            break;

          case 'L':
          case 'M':
          case 'N':
            // ANSI conformance level... ignored.
            break;

          default:
            log( "Unhandled argument for ESC sp: " + arg );
            break;
        }
        break;
      }
      case '#':
      {
        char arg = ( char )parameters[0];
        switch ( arg )
        {
          case '8':
            // DEC Screen Alignment Test (DECALN)
            for ( int j = getFirstAbsoluteIndex(), last = getLastAbsoluteIndex(); j <= last; j++ )
            {
              writeChar( j, 'E' );
            }
            break;

          case '3':
          case '4':
          case '5':
          case '6':
            // Single/double line width/height... ignored.
            break;

          default:
            log( "Unhandled argument for ESC sp: " + arg );
            break;
        }
        break;
      }
      case '(':
      case ')':
      case '*':
      case '+':
      {
        // Designate G0/G1/G2 or G3 Character Set
        GraphicSet gs = m_graphicSetState.getGraphicSet( designator - '(' );
        m_graphicSetState.designateGraphicSet( gs, ( char )parameters[0] );
        break;
      }
      case '=':
      {
        // (DECPAM) Application Keypad
        setApplicationCursorKeys( true );
        break;
      }
      case '>':
      {
        // (DECPNM) Normal Keypad
        setApplicationCursorKeys( false );
        break;
      }

      default:
      {
        log( "Unhandled ESC designator: " + designator );
        break;
      }
    }
    // Update the cursor position...
    updateCursorByAbsoluteIndex( idx );
    resetWrapped();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void reset()
  {
    softReset();
    // Clear entire screen...
    clearScreen( 2 );
  }

  /**
   * Sets the dimensions of this terminal in columns and lines. Overridden in
   * order to handle zero and negative values as possible values.
   *
   * @param width
   *          the new width of this terminal in columns, if zero or negative,
   *          the maximum possible number of columns will be used;
   * @param height
   *          the new height of this terminal in lines, if zero or negative, the
   *          maximum possible number of lines will be used.
   */
  @Override
  public void setDimensions( int width, int height )
  {
    if ( width <= 0 || height <= 0 )
    {
      Dimension maximumSize = null;
      if ( getFrontend() != null )
      {
        maximumSize = getFrontend().getMaximumTerminalSize();
      }
      if ( width <= 0 )
      {
        width = ( maximumSize != null ) ? maximumSize.width : getWidth();
      }
      if ( height <= 0 )
      {
        height = ( maximumSize != null ) ? maximumSize.height : getHeight();
      }
    }
    super.setDimensions( width, height );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setLogLevel( int aLogLevel )
  {
    super.setLogLevel( aLogLevel );
    m_vt220parser.setLogLevel( aLogLevel );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected IKeyMapper createKeyMapper()
  {
    return new VT220KeyMapper();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int doReadInput( final CharSequence text ) throws IOException
  {
    return m_vt220parser.parse( text, this );
  }

  /**
   * Returns whether or not the 132-column mode is enabled.
   *
   * @return <code>true</code> if 132-column mode is enabled, <code>false</code>
   *         otherwise.
   * @see #set132ColumnMode(boolean)
   */
  protected final boolean is132ColumnMode()
  {
    return m_options.get( OPTION_132COLS );
  }

  /**
   * Returns whether or not responses are send in 8-bit or in 7-bit mode.
   *
   * @return <code>true</code> if responses are to be sent in 8-bit mode,
   *         <code>false</code> to send responses in 7-bit mode.
   * @see #set8bitMode(boolean)
   */
  protected final boolean is8bitMode()
  {
    return m_options.get( OPTION_8BIT );
  }

  /**
   * Returns whether or not the cursor keys map to application-specific keys, or
   * to normal keys.
   *
   * @return <code>true</code> if application cursor keys are to be used,
   *         <code>false</code> if normal cursor keys are to be used.
   * @see #setApplicationCursorKeys(boolean)
   */
  protected final boolean isApplicationCursorKeys()
  {
    return m_options.get( OPTION_APPLICATION_CURSOR_KEYS );
  }

  /**
   * Returns whether or not it is allows to switch from 80 to 132 columns.
   *
   * @return <code>true</code> if switching from 80 to 132 column mode is
   *         allowed, <code>false</code> otherwise.
   * @see #setEnable132ColumnMode(boolean)
   */
  protected final boolean isEnable132ColumnMode()
  {
    return m_options.get( OPTION_ENABLE_132COLS );
  }

  /**
   * Returns whether or not protected content can be erased.
   *
   * @return <code>true</code> if only unprotected content can be erased,
   *         <code>false</code> if both protected and unprotected content can be
   *         erased.
   * @see #setErasureMode(boolean)
   */
  protected final boolean isErasureMode()
  {
    return m_options.get( OPTION_ERASURE_MODE );
  }

  /**
   * Returns whether or not reverse wrap arounds are supported.
   *
   * @return <code>true</code> if a "reverse wrap around" is allowed,
   *         <code>false</code> otherwise.
   * @see #setReverseWrapAround(boolean)
   */
  protected final boolean isReverseWrapAround()
  {
    return m_options.get( OPTION_REVERSE_WRAP_AROUND );
  }

  /**
   * Returns whether or not this terminal is emulating a VT52.
   *
   * @return <code>true</code> if the terminal is currently emulating a VT52,
   *         <code>false</code> if it is emulating a VT100/VT220.
   */
  protected final boolean isVT52mode()
  {
    return m_vt220parser.isVT52mode();
  }

  /**
   * Enables or disables the auto-wrap mode.
   *
   * @param enable
   *          <code>true</code> to enable auto-wrap mode, <code>false</code> to
   *          disable it.
   */
  protected final void set132ColumnMode( boolean enable )
  {
    m_options.set( OPTION_132COLS, enable );

    if ( enable )
    {
      setDimensions( 132, getHeight() );
    }
    else
    {
      setDimensions( 80, getHeight() );
    }
  }

  /**
   * Sets whether or not responses send by this terminal are in 7- or in 8-bit
   * mode.
   *
   * @param enable
   *          <code>true</code> to send responses in 8-bit mode,
   *          <code>false</code> to send responses in 7-bit mode.
   */
  protected final void set8bitMode( boolean enable )
  {
    m_options.set( OPTION_8BIT, enable );
  }

  /**
   * Sets whether or not the cursor keys map to application-specific keys, or to
   * normal keys.
   *
   * @param enable
   *          <code>true</code> to enable application cursor keys,
   *          <code>false</code> to enable normal cursor keys.
   */
  protected final void setApplicationCursorKeys( boolean enable )
  {
    m_options.set( OPTION_APPLICATION_CURSOR_KEYS, enable );
  }

  /**
   * Sets whether or not the switching from 80 to 132 columns is allowed.
   *
   * @param enable
   *          <code>true</code> to allow switching from 80 to 132 columns,
   *          <code>false</code> to disallow this switching.
   */
  protected final void setEnable132ColumnMode( boolean enable )
  {
    m_options.set( OPTION_ENABLE_132COLS, enable );
  }

  /**
   * Sets whether or not protected content is to be erased.
   *
   * @param enable
   *          <code>true</code> to allow only unprotected content to be erased,
   *          <code>false</code> to allow both protected and unprotected content
   *          to be erased.
   */
  protected final void setErasureMode( boolean enable )
  {
    m_options.set( OPTION_ERASURE_MODE, enable );
  }

  /**
   * Sets whether or not reverse wrap arounds are supported, meaning that
   * hitting backspace or issuing cursor-back commands after a wrap-around has
   * taken place will revert this wrap-around.
   *
   * @param enable
   *          <code>true</code> to enable reverse wrap around,
   *          <code>false</code> to disable it.
   */
  protected final void setReverseWrapAround( boolean enable )
  {
    m_options.set( OPTION_REVERSE_WRAP_AROUND, enable );
  }

  /**
   * Creates a 7- or 8-bit response according to a given type.
   *
   * @param type
   *          the type of response;
   * @param content
   *          the actual content.
   * @return the 7- or 8-bit response, never <code>null</code>.
   */
  private String createResponse( ResponseType type, String content )
  {
    StringBuilder sb = new StringBuilder();
    switch ( type )
    {
      case ESC:
        sb.append( "\033" );
        break;

      case OSC:
        sb.append( is8bitMode() ? VT220Parser.OSC : "\033]" );
        break;

      case CSI:
        sb.append( is8bitMode() ? VT220Parser.CSI : "\033[" );
        break;

      case SS3:
        sb.append( is8bitMode() ? VT220Parser.SS3 : "\033O" );
        break;

      default:
        throw new RuntimeException( "Unhandled response type: " + type );
    }
    sb.append( content );
    return sb.toString();
  }

  /**
   * End of protected area.
   */
  private void handleEPA()
  {
    m_textAttributes.setProtected( false );
  }

  /**
   * Converts the graphics rendering options in the form of a given parameter
   * stack into recognized text attributes.
   *
   * @param parameters
   *          the parameters to handle as graphics rendering options.
   */
  private void handleGraphicsRendering( int[] parameters )
  {
    boolean containsHiddenAttr = false;
    for ( int p : parameters )
    {
      if ( p == 8 )
      {
        containsHiddenAttr = true;
        break;
      }
    }

    if ( m_textAttributes.isHidden() && !containsHiddenAttr )
    {
      // It appears that after an invisible attribute, *both* reverse as
      // invisible are to be reset...
      m_textAttributes.setHidden( false );
      m_textAttributes.setReverse( false );
    }

    for ( int p : parameters )
    {
      handleGraphicsRendering( m_textAttributes, p );
    }
  }

  /**
   * Handles the given graphics parameter.
   *
   * @param attributes
   *          the text attributes to update;
   * @param parameter
   *          the graphics parameter to handle.
   */
  private void handleGraphicsRendering( final TextAttributes attributes, final int parameter )
  {
    if ( parameter == 0 || parameter == 39 || parameter == 49 )
    {
      if ( parameter == 0 )
      {
        attributes.resetAll();
      }
      else
      {
        attributes.reset();
      }
    }

    if ( parameter == 1 )
    {
      attributes.setBold( true );
    }
    else if ( parameter == 4 )
    {
      attributes.setUnderline( true );
    }
    else if ( parameter == 5 )
    {
      // Blink on...
      attributes.setItalic( true );
    }
    else if ( parameter == 7 )
    {
      // Negative/reverse...
      attributes.setReverse( true );
    }
    else if ( parameter == 8 )
    {
      // Invisible (hidden)...
      attributes.setHidden( true );
    }
    else if ( parameter == 21 || parameter == 22 )
    {
      attributes.setBold( false );
    }
    else if ( parameter == 24 )
    {
      attributes.setUnderline( false );
    }
    else if ( parameter == 25 )
    {
      // Blink off...
      attributes.setItalic( false );
    }
    else if ( parameter == 27 )
    {
      // Reset negative...
      attributes.setReverse( false );
    }
    else if ( parameter == 28 )
    {
      // Reset invisible (shown)...
      attributes.setHidden( false );
    }
    else if ( ( parameter >= 30 ) && ( parameter <= 37 ) )
    {
      // Handle foreground color...
      attributes.setForeground( parameter - 29 );
    }
    else if ( parameter == 39 )
    {
      // Default foreground color...
      attributes.setForeground( 0 );
    }
    else if ( ( parameter >= 40 ) && ( parameter <= 47 ) )
    {
      attributes.setBackground( parameter - 39 );
    }
    else if ( parameter == 49 )
    {
      // Default background color...
      attributes.setBackground( 0 );
    }
    else if ( parameter > 0 )
    {
      log( "Unhandled attribute: " + parameter );
    }
  }

  /**
   * IND moves the cursor down one line in the same column. If the cursor is at
   * the bottom margin, the screen performs a scroll-up.
   *
   * @param aTermState
   */
  private int handleIND( final int index )
  {
    int col = index % getWidth();
    int row = ( index / getWidth() ) + 1;

    if ( row > getLastScrollLine() )
    {
      row = getLastScrollLine();
      scrollUp( 1 );
    }

    return getAbsoluteIndex( col, row );
  }

  /**
   * NEL moves the cursor to the first position on the next line. If the cursor
   * is at the bottom margin, the screen performs a scroll-up.
   *
   * @param aTermState
   */
  private int handleNEL( int index )
  {
    int col = 0;
    int row = ( index / getWidth() ) + 1;

    if ( row > getLastScrollLine() )
    {
      row = getLastScrollLine();
      scrollUp( 1 );
    }

    return getAbsoluteIndex( col, row );
  }

  /**
   * RI moves the cursor up one line in the same column. If the cursor is at the
   * top margin, the screen performs a scroll-down.
   *
   * @param aTermState
   */
  private int handleRI( int index )
  {
    int col = index % getWidth();
    int row = ( index / getWidth() ) - 1;

    if ( row < getFirstScrollLine() )
    {
      row = getFirstScrollLine();
      scrollDown( 1 );
    }

    return getAbsoluteIndex( col, row );
  }

  /**
   * Start of protected area.
   */
  private void handleSPA()
  {
    m_textAttributes.setProtected( true );
  }

  /**
   * Single Shift Select of G2 Character Set. Affects next character only.
   */
  private void handleSS2()
  {
    m_graphicSetState.overrideGL( 2 );
  }

  /**
   * Single Shift Select of G3 Character Set. Affects next character only.
   */
  private void handleSS3()
  {
    m_graphicSetState.overrideGL( 3 );
  }

  /**
   * @param aTermState
   */
  private void handleTabSet( int index )
  {
    getTabulator().set( ( index % getWidth() ) );
  }

  /**
   * (DECRC) Restores the previously stored cursor position.
   */
  private int restoreCursor()
  {
    return m_savedState.restore( this );
  }

  /**
   * (DECSC) Stores the current cursor position.
   */
  private void saveCursor( int aIndex )
  {
    m_savedState.store( this );
  }

  /**
   * Send Device Attributes (Primary DA).
   */
  private void sendDeviceAttributes( int arg ) throws IOException
  {
    if ( arg != 0 )
    {
      log( "Unknown DA: " + arg );
    }
    else
    {
      if ( isVT52mode() )
      {
        // Send back that we're a VT52...
        writeResponse( ResponseType.ESC, "/Z" );
      }
      else
      {
        // Send back that we're a VT220...
        writeResponse( ResponseType.CSI, "?62;1;2;4;6;8;9;15c" );
      }
    }
  }

  /**
   * Sets the compatibility level for this terminal.
   *
   * @param level
   *          &lt;= 1 equals to VT100 mode, &gt;= 2 equals to VT200 mode;
   * @param eightBitMode
   *          <code>true</code> if 8-bit controls are preferred (only in VT200
   *          mode), <code>false</code> if 7-bit controls are preferred.
   */
  private void setConformanceLevel( int level, boolean eightBitMode )
  {
    if ( level <= 1 )
    {
      // @formatter:off
      /* VT100 mode:
       * - keyboard sends ASCII only;
       * - keystrokes that normally send DEC Supplemental Characters transfer nothing;
       * - user-defined keys are do not operate;
       * - special function keys and six editing keys do not operate (except F11, F12, and F13, which send ESC, BS, and LF, respectively);
       * - always 7-bit mode: the 8th bit of all received characters is set to zero (0);
       * - character sets: Only ASCII, national replacement characters, and DEC special graphics are available;
       * - all transmitted C1 controls are forced to S7C1 state and sent as 7-bit escape sequences.
       */
      // @formatter:on
      softReset();
      set8bitMode( false );
    }
    else if ( level >= 2 )
    {
      // @formatter:off
      /* VT200 mode:
       * - keyboard permits full use of VT220 keyboard;
       * - 7 or 8-bit mode: the 8th bit of all received characters is significant;
       * - all VT220 character sets are available.
       */
      // @formatter:on
      softReset();
      set8bitMode( eightBitMode );
    }
  }

  /**
   * Sets the dimensions of the terminal frontend, in pixels.
   *
   * @param width
   *          the new width, in pixels;
   * @param height
   *          the new height, in pixels.
   */
  private void setDimensionsInPixels( int width, int height )
  {
    ITerminalFrontend frontend = getFrontend();
    if ( frontend != null )
    {
      frontend.setSize( width, height );
    }
  }

  /**
   * Performs a soft reset.
   */
  private void softReset()
  {
    // See: <http://www.vt100.net/docs/vt220-rm/table4-10.html> and
    // <http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V50A_HTML/MAN/MAN5/0036____.HTM>

    // Turns on the text cursor (DECTCEM)
    getCursor().setVisible( true );
    // Enables replace mode (IRM)
    setInsertMode( false );
    // Turns off origin mode (DECOM)
    setOriginMode( false );
    // Sets the top and bottom margins to the first and last lines of the window
    // (DECSTBM)
    setScrollRegion( getFirstScrollLine(), getLastScrollLine() );
    // Turns on autowrap (DECAWM)
    setAutoWrap( true );
    // Turns off reverse wrap
    setReverseWrapAround( true );
    // Unlocks the keyboard (KAM)
    // Sets the cursor keypad mode to normal (DECCKM)
    setApplicationCursorKeys( false );
    // Sets the numeric keypad mode to numeric (DECNKM)

    set8bitMode( false );
    set132ColumnMode( false );
    setEnable132ColumnMode( true );
    setReverse( false );
    // Sets selective erase mode off (DECSCA)
    setErasureMode( true );

    // Sets all character sets (GL, G0, G1, G2 and G3) to ASCII
    m_graphicSetState.resetState();
    // Turns off all character attributes (SGR)
    m_textAttributes.resetAll();

    // Clears any cursor state information saved with save cursor (DECSC)
    saveCursor( getFirstAbsoluteIndex() );
  }

  /**
   * Writes a response according to a given type.
   *
   * @param type
   *          the type of response to write;
   * @param content
   *          the actual content to write.
   * @throws IOException
   *           in case of I/O problems writing the response.
   */
  private void writeResponse( ResponseType type, String content ) throws IOException
  {
    write( createResponse( type, content ) );
  }
}
TOP

Related Classes of nl.lxtreme.jvt220.terminal.vt220.VT220Terminal$GraphicSetState

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.