Package nl.lxtreme.jvt220.terminal.swing

Source Code of nl.lxtreme.jvt220.terminal.swing.SwingFrontend$CharacterDimensions

/**
* 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.swing;


import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.image.*;
import java.io.*;
import java.text.*;
import java.util.*;
import java.util.List;

import javax.swing.*;

import nl.lxtreme.jvt220.terminal.*;
import nl.lxtreme.jvt220.terminal.ITerminal.ITextCell;


/**
* Provides a Swing frontend for {@link ITerminal}.
*/
public class SwingFrontend extends JComponent implements ITerminalFrontend
{
  // INNER TYPES

  /**
   * Small container for the width, height and line spacing of a single
   * character.
   */
  static final class CharacterDimensions
  {
    final int m_height;
    final int m_width;
    final int m_lineSpacing;

    /**
     * Creates a new {@link CharacterDimensions} instance.
     *
     * @param width
     *          the width of a single character, in pixels;
     * @param height
     *          the height of a single character, in pixels;
     * @param lineSpacing
     *          the spacing to use between two lines with characters, in pixels.
     */
    public CharacterDimensions( int width, int height, int lineSpacing )
    {
      m_width = width;
      m_height = height;
      m_lineSpacing = lineSpacing;
    }
  }

  /**
   * Asynchronous worker that reads data from an input stream and passes this to
   * the terminal backend.
   */
  final class InputStreamWorker extends SwingWorker<Void, Integer>
  {
    // VARIABLES

    private final InputStreamReader m_reader;

    // CONSTRUCTORS

    /**
     * Creates a new {@link InputStreamWorker} instance.
     *
     * @param inputStream
     *          the input stream to read from, cannot be <code>null</code>;
     * @param encoding
     *          the character encoding to use for the read input, cannot be
     *          <code>null</code>.
     */
    public InputStreamWorker( final InputStream inputStream, String encoding ) throws IOException
    {
      m_reader = new InputStreamReader( inputStream, encoding );
    }

    // METHODS

    @Override
    protected Void doInBackground() throws Exception
    {
      while ( !isCancelled() && !Thread.currentThread().isInterrupted() )
      {
        int r = m_reader.read();
        if ( r > 0 )
        {
          publish( Integer.valueOf( r ) );
        }
      }
      return null;
    }

    @Override
    protected void process( final List<Integer> readChars )
    {
      Integer[] chars = readChars.toArray( new Integer[readChars.size()] );

      try
      {
        writeCharacters( chars );
      }
      catch ( IOException exception )
      {
        exception.printStackTrace(); // XXX
      }
    }
  }

  // CONSTANTS

  /**
   * The default encoding to use for the I/O with the outer world.
   */
  private static final String ISO8859_1 = "ISO8859-1";

  // VARIABLES

  private final String m_encoding;
  private final CharBuffer m_buffer;

  private ITerminalColorScheme m_colorScheme;
  private ICursor m_oldCursor;
  private volatile CharacterDimensions m_charDims;
  private volatile BufferedImage m_image;
  private volatile boolean m_listening;
  private ITerminal m_terminal;
  private InputStreamWorker m_inputStreamWorker;
  private Writer m_writer;

  // CONSTRUCTORS

  /**
   * Creates a new {@link SwingFrontend} instance using ISO8859-1 encoding.
   */
  public SwingFrontend()
  {
    this( ISO8859_1 );
  }

  /**
   * Creates a new {@link SwingFrontend} instance.
   *
   * @param encoding
   *          the character encoding to use for the terminal.
   */
  public SwingFrontend( String encoding )
  {
    if ( encoding == null || "".equals( encoding.trim() ) )
    {
      throw new IllegalArgumentException( "Encoding cannot be null or empty!" );
    }

    m_encoding = encoding;
    m_buffer = new CharBuffer();
    m_colorScheme = new XtermColorScheme();

    setFont( Font.decode( "Monospaced-PLAIN-14" ) );

    mapKeyboard();

    setEnabled( false );
    setFocusable( true );
    setFocusTraversalKeysEnabled( false ); // disables TAB handling
    requestFocus();
  }

  // METHODS

  /**
   * Calculates the character dimensions for the given font, which is presumed
   * to be a monospaced font.
   *
   * @param aFont
   *          the font to get the character dimensions for, cannot be
   *          <code>null</code>.
   * @return an array of length 2, containing the character width and height (in
   *         that order).
   */
  private static CharacterDimensions getCharacterDimensions( Font aFont )
  {
    BufferedImage im = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB );

    Graphics2D g2d = im.createGraphics();
    g2d.setFont( aFont );
    FontMetrics fm = g2d.getFontMetrics();
    g2d.dispose();
    im.flush();

    int w = fm.charWidth( '@' );
    int h = fm.getAscent() + fm.getDescent();

    return new CharacterDimensions( w, h, fm.getLeading() + 1 );
  }

  /**
   * Calculates the union of two rectangles, allowing <code>null</code> values
   * to be passed in.
   *
   * @param rect1
   *          the 1st rectangle to create the union, if <code>null</code>, the
   *          2nd argument will be returned;
   * @param rect2
   *          the 2nd rectangle to create the union, if <code>null</code>, the
   *          1st argument will be returned.
   * @return the union of the two given rectangles.
   */
  private static Rectangle union( Rectangle rect1, Rectangle rect2 )
  {
    if ( rect2 == null )
    {
      return rect1;
    }
    if ( rect1 == null )
    {
      return rect2;
    }

    return rect1.union( rect2 );
  }

  /**
   * Connects this frontend to a given input and output stream.
   * <p>
   * This method will start a background thread to read continuously from the
   * given input stream.
   * </p>
   *
   * @param inputStream
   *          the input stream to connect to, cannot be <code>null</code>;
   * @param outputStream
   *          the output stream to connect to, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems.
   */
  @Override
  public void connect( InputStream inputStream, OutputStream outputStream ) throws IOException
  {
    if ( inputStream == null )
    {
      throw new IllegalArgumentException( "Input stream cannot be null!" );
    }
    if ( outputStream == null )
    {
      throw new IllegalArgumentException( "Output stream cannot be null!" );
    }

    disconnect();

    m_writer = new OutputStreamWriter( outputStream, m_encoding );

    m_inputStreamWorker = new InputStreamWorker( inputStream, m_encoding );
    m_inputStreamWorker.execute();

    setEnabled( true );
  }

  /**
   * Connects this frontend to a given output stream.
   * <p>
   * NOTE: when using this method, you need to explicitly call
   * {@link #writeCharacters(Integer...)} yourself in order to let anything
   * appear on the terminal.
   * </p>
   *
   * @param outputStream
   *          the output stream to connect to, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems.
   */
  @Override
  public void connect( OutputStream outputStream ) throws IOException
  {
    if ( outputStream == null )
    {
      throw new IllegalArgumentException( "Output stream cannot be null!" );
    }

    disconnect();

    m_writer = new OutputStreamWriter( outputStream, m_encoding );

    setEnabled( true );
  }

  /**
   * Disconnects this frontend from any input and output stream.
   *
   * @throws IOException
   *           in case of I/O problems.
   */
  @Override
  public void disconnect() throws IOException
  {
    try
    {
      if ( m_inputStreamWorker != null )
      {
        m_inputStreamWorker.cancel( true /* mayInterruptIfRunning */);
        m_inputStreamWorker = null;
      }
      if ( m_writer != null )
      {
        m_writer.close();
        m_writer = null;
      }
    }
    finally
    {
      setEnabled( false );
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Dimension getMaximumTerminalSize()
  {
    Rectangle bounds = getGraphicsConfiguration().getBounds();
    Insets insets = calculateTotalInsets();

    int width = bounds.width - insets.left - insets.right;
    int height = bounds.height - insets.top - insets.bottom;

    CharacterDimensions charDims = m_charDims;

    // Calculate the maximum number of columns & lines...
    int columns = width / charDims.m_width;
    int lines = height / ( charDims.m_height + charDims.m_lineSpacing );

    return new Dimension( columns, lines );
  }

  /**
   * Returns the current terminal.
   *
   * @return the terminal, can be <code>null</code>.
   */
  public ITerminal getTerminal()
  {
    return m_terminal;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Writer getWriter()
  {
    return m_writer;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isListening()
  {
    return m_listening;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setFont( Font font )
  {
    super.setFont( font );

    m_charDims = getCharacterDimensions( font );
  }

  /**
   * @see nl.lxtreme.jvt220.terminal.ITerminalFrontend#setReverse(boolean)
   */
  @Override
  public void setReverse( boolean reverse )
  {
    m_colorScheme.setInverted( reverse );
  }

  /**
   * Sets the size of this component in pixels. Overridden in order to redirect
   * this call to {@link #terminalSizeChanged(int, int)} with the correct number
   * of columns and lines.
   */
  @Override
  public void setSize( int width, int height )
  {
    Rectangle bounds = getGraphicsConfiguration().getBounds();
    Insets insets = calculateTotalInsets();

    if ( width == 0 )
    {
      width = bounds.width - insets.left - insets.right;
    }
    else if ( width < 0 )
    {
      width = getWidth();
    }
    if ( height == 0 )
    {
      height = bounds.height - insets.top - insets.bottom;
    }
    else if ( height < 0 )
    {
      height = getHeight();
    }

    CharacterDimensions charDims = m_charDims;

    int columns = width / charDims.m_width;
    int lines = height / ( charDims.m_height + charDims.m_lineSpacing );

    terminalSizeChanged( columns, lines );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setTerminal( ITerminal terminal )
  {
    if ( terminal == null )
    {
      throw new IllegalArgumentException( "Terminal cannot be null!" );
    }
    m_terminal = terminal;
    m_terminal.setFrontend( this );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void terminalChanged( final ITextCell[] cells, final BitSet heatMap )
  {
    SwingUtilities.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        updateTerminalImage( cells, heatMap );
      }
    } );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void terminalSizeChanged( final int columns, final int lines )
  {
    SwingUtilities.invokeLater( new Runnable()
    {
      @Override
      public void run()
      {
        recreateTerminalImage( columns, lines, true /* forceRepaint */ );
      }
    } );
  }

  /**
   * Writes the given sequence of characters directly to the terminal, similar
   * as writing to the standard output.
   *
   * @param charSeq
   *          the sequence of characters to write, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems writing to the terminal.
   */
  @Override
  public void writeCharacters( CharSequence charSeq ) throws IOException
  {
    InputStream is = new ByteArrayInputStream( charSeq.toString().getBytes() );
    InputStreamReader isr = new InputStreamReader( is, m_encoding );

    int ch;
    while ( ( ch = isr.read() ) >= 0 )
    {
      writeCharacters( ch );
    }
  }

  /**
   * Writes the given array of characters directly to the terminal, similar as
   * writing to the standard output.
   *
   * @param chars
   *          the characters to write, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems writing to the terminal.
   */
  @Override
  public void writeCharacters( Integer... chars ) throws IOException
  {
    m_buffer.append( chars );

    int n = m_terminal.read( m_buffer );

    m_buffer.removeUntil( n );
  }

  /**
   * Recreates the terminal image.
   *
   * @param columns
   *          the new number of columns, > 0;
   * @param lines
   *          the new number of lines, > 0.
   */
  void recreateTerminalImage( int columns, int lines, boolean forceRepaint )
  {
    assert SwingUtilities.isEventDispatchThread() : "Should be called from EDT only!";

    final Dimension dims = calculateSizeInPixels( columns, lines );

    if ( ( m_image == null ) || ( m_image.getWidth() != dims.width ) || ( m_image.getHeight() != dims.height ) )
    {
      if ( m_image != null )
      {
        m_image.flush();
      }
      m_image = getGraphicsConfiguration().createCompatibleImage( dims.width, dims.height );

      Graphics2D canvas = m_image.createGraphics();

      try
      {
        canvas.setBackground( m_colorScheme.getBackgroundColor() );
        canvas.clearRect( 0, 0, m_image.getWidth(), m_image.getHeight() );
      }
      finally
      {
        canvas.dispose();
        canvas = null;
      }

      // Update the size of this component as well...
      Insets insets = getInsets();
      super.setSize( dims.width + insets.left + insets.right, dims.height + insets.top + insets.bottom );

      repaint( 50L );
    }
  }

  /**
   * Updates the image representing the terminal contents.
   *
   * @param cells
   *          the text cells;
   * @param heatMap
   *          the heat map denoting the changed cells.
   */
  void updateTerminalImage( ITextCell[] cells, BitSet heatMap )
  {
    assert SwingUtilities.isEventDispatchThread() : "Should be called from the EDT only!";

    final int columns = m_terminal.getWidth();
    final int lines = m_terminal.getHeight();

    // Create copies of these data items to ensure they remain constant for
    // the remainder of this method...
    CharacterDimensions charDims = m_charDims;

    int cw = charDims.m_width;
    int ch = charDims.m_height;
    int ls = charDims.m_lineSpacing;

    if ( m_image == null )
    {
      // Ensure there's a valid image to paint on...
      recreateTerminalImage( columns, lines, false /* forceRepaint */ );
    }

    final Graphics2D canvas = m_image.createGraphics();
    canvas.setFont( getFont() );

    final Font font = getFont();
    final FontMetrics fm = canvas.getFontMetrics();
    final FontRenderContext frc = new FontRenderContext( null, true /* aa */, true /* fractionalMetrics */);

    Color cursorColor = null;
    Rectangle repaintArea = null;

    if ( m_oldCursor != null )
    {
      repaintArea = drawCursor( canvas, m_oldCursor, m_colorScheme.getBackgroundColor() );
    }

    for ( int i = 0; i < cells.length; i++ )
    {
      boolean cellChanged = heatMap.get( i );
      if ( cellChanged )
      {
        // Cell is changed...
        final ITextCell cell = cells[i];

        final int x = ( i % columns ) * cw;
        final int y = ( i / columns ) * ( ch + ls );

        final Rectangle rect = new Rectangle( x, y, cw, ch + ls );

        canvas.setColor( convertToColor( cell.getBackground(), m_colorScheme.getBackgroundColor() ) );
        canvas.fillRect( rect.x, rect.y, rect.width, rect.height );

        final String txt = Character.toString( cell.getChar() );

        AttributedString attrStr = new AttributedString( txt );
        cursorColor = applyAttributes( cell, attrStr, font );

        AttributedCharacterIterator characterIterator = attrStr.getIterator();
        LineBreakMeasurer measurer = new LineBreakMeasurer( characterIterator, frc );

        while ( measurer.getPosition() < characterIterator.getEndIndex() )
        {
          TextLayout textLayout = measurer.nextLayout( getWidth() );
          textLayout.draw( canvas, x, y + fm.getAscent() );
        }

        repaintArea = union( repaintArea, rect );
      }
    }

    // Draw the cursor...
    m_oldCursor = m_terminal.getCursor().clone();
    if ( cursorColor == null )
    {
      cursorColor = m_colorScheme.getTextColor();
    }

    repaintArea = union( repaintArea, drawCursor( canvas, m_oldCursor, cursorColor ) );

    // Free the resources...
    canvas.dispose();

    if ( repaintArea != null )
    {
      repaintArea.grow( 5, 3 );
      repaint( repaintArea );
    }
  }

  /**
   * Maps the keyboard to respond to keys like 'up', 'down' and the function
   * keys.
   */
  protected void mapKeyboard()
  {
    mapKeystroke( KeyEvent.VK_UP );
    mapKeystroke( KeyEvent.VK_DOWN );
    mapKeystroke( KeyEvent.VK_RIGHT );
    mapKeystroke( KeyEvent.VK_LEFT );

    mapKeystroke( KeyEvent.VK_PAGE_DOWN );
    mapKeystroke( KeyEvent.VK_PAGE_UP );
    mapKeystroke( KeyEvent.VK_HOME );
    mapKeystroke( KeyEvent.VK_END );

    mapKeystroke( KeyEvent.VK_NUMPAD0 );
    mapKeystroke( KeyEvent.VK_NUMPAD1 );
    mapKeystroke( KeyEvent.VK_NUMPAD2 );
    mapKeystroke( KeyEvent.VK_NUMPAD3 );
    mapKeystroke( KeyEvent.VK_NUMPAD4 );
    mapKeystroke( KeyEvent.VK_NUMPAD5 );
    mapKeystroke( KeyEvent.VK_NUMPAD6 );
    mapKeystroke( KeyEvent.VK_NUMPAD7 );
    mapKeystroke( KeyEvent.VK_NUMPAD8 );
    mapKeystroke( KeyEvent.VK_NUMPAD9 );
    mapKeystroke( KeyEvent.VK_MINUS );
    mapKeystroke( KeyEvent.VK_PLUS );
    mapKeystroke( KeyEvent.VK_COMMA );
    mapKeystroke( KeyEvent.VK_PERIOD );
    mapKeystroke( KeyEvent.VK_ENTER );
    mapKeystroke( KeyEvent.VK_KP_DOWN );
    mapKeystroke( KeyEvent.VK_KP_LEFT );
    mapKeystroke( KeyEvent.VK_KP_RIGHT );
    mapKeystroke( KeyEvent.VK_KP_UP );

    mapKeystroke( KeyEvent.VK_F1 );
    mapKeystroke( KeyEvent.VK_F1, InputEvent.ALT_DOWN_MASK );
    mapKeystroke( KeyEvent.VK_F2 );
    mapKeystroke( KeyEvent.VK_F2, InputEvent.ALT_DOWN_MASK );
    mapKeystroke( KeyEvent.VK_F3 );
    mapKeystroke( KeyEvent.VK_F3, InputEvent.ALT_DOWN_MASK );
    mapKeystroke( KeyEvent.VK_F4 );
    mapKeystroke( KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK );
    mapKeystroke( KeyEvent.VK_F5 );
    mapKeystroke( KeyEvent.VK_F6 );
    mapKeystroke( KeyEvent.VK_F7 );
    mapKeystroke( KeyEvent.VK_F8 );
    mapKeystroke( KeyEvent.VK_F9 );
    mapKeystroke( KeyEvent.VK_F10 );
    mapKeystroke( KeyEvent.VK_F11 );
    mapKeystroke( KeyEvent.VK_F12 );
  }

  /**
   * Maps the given keycode and modifiers to a terminal specific sequence.
   *
   * @param keycode
   *          the keycode to map;
   * @param modifiers
   *          the modifiers to map.
   * @return a terminal-specific sequence for the given input, or
   *         <code>null</code> if no specific sequence exists for this terminal.
   */
  protected String mapKeyCode( int keycode, int modifiers )
  {
    return getTerminal().getKeyMapper().map( keycode, modifiers );
  }

  /**
   * Creates a key mapping for the given keystroke and the given action which is
   * send as literal text to the terminal.
   *
   * @param keycode
   *          the keycode to map.
   */
  protected void mapKeystroke( int keycode )
  {
    mapKeystroke( keycode, 0 );
  }

  /**
   * Creates a key mapping for the given keystroke and the given action which is
   * send as literal text to the terminal.
   *
   * @param keycode
   *          the keycode to map;
   * @param modifiers
   *          the modifiers to map.
   */
  protected void mapKeystroke( int keycode, int modifiers )
  {
    final KeyStroke keystroke = KeyStroke.getKeyStroke( keycode, modifiers );
    final String key = keystroke.toString();

    getInputMap().put( keystroke, key );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void paintComponent( Graphics canvas )
  {
    m_listening = false;

    canvas.setColor( m_colorScheme.getBackgroundColor() );

    Rectangle clip = canvas.getClipBounds();
    canvas.fillRect( clip.x, clip.y, clip.width, clip.height );

    try
    {
      Insets insets = getInsets();

      canvas.drawImage( m_image, insets.left, insets.top, null /* observer */);
    }
    finally
    {
      m_listening = true;
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean processKeyBinding( KeyStroke keystroke, KeyEvent event, int condition, boolean pressed )
  {
    if ( !isEnabled() )
    {
      // Don't bother to process keys for a disabled component...
      return false;
    }

    InputMap inputMap = getInputMap( condition );
    ActionMap actionMap = getActionMap();

    try
    {
      if ( ( inputMap != null ) && ( actionMap != null ) && ( event.getID() == KeyEvent.KEY_PRESSED ) )
      {
        Object binding = inputMap.get( keystroke );
        if ( binding != null )
        {
          Action action = actionMap.get( binding );
          if ( action != null )
          {
            // Normal action; invoke it...
            return SwingUtilities.notifyAction( action, keystroke, event, this, event.getModifiers() );
          }
          else
          {
            // Keystroke we've mapped without an action, means we're going to
            // test whether there's a mapping for it. If so, use that as
            // response, otherwise respond with the 'regular' key...
            String mapping = mapKeyCode( keystroke.getKeyCode(), keystroke.getModifiers() );
            if ( mapping != null )
            {
              respond( mapping );
              return true;
            }
          }
        }

        if ( isRegularKey( keystroke ) )
        {
          respond( event.getKeyChar() );
          return true;
        }
      }
    }
    catch ( IOException exception )
    {
      exception.printStackTrace(); // XXX
    }

    return false;
  }

  /**
   * Writes a given number of characters to the terminal.
   *
   * @param chars
   *          the characters to write, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems responding.
   */
  protected void respond( char ch ) throws IOException
  {
    if ( m_writer != null )
    {
      m_writer.write( ch );
      m_writer.flush();
    }
  }

  /**
   * Writes a given number of characters to the terminal.
   *
   * @param chars
   *          the characters to write, cannot be <code>null</code>.
   * @throws IOException
   *           in case of I/O problems responding.
   */
  protected void respond( String chars ) throws IOException
  {
    if ( m_writer != null )
    {
      m_writer.write( chars );
      m_writer.flush();
    }
  }

  /**
   * Applies the attributes from the given {@link TextCell} to the given
   * {@link AttributedString}.
   *
   * @param textCell
   *          the text cell to get the attributes from;
   * @param attributedString
   *          the {@link AttributedString} to apply the attributes to;
   * @param font
   *          the font to use.
   * @return the primary foreground color, never <code>null</code>.
   */
  private Color applyAttributes( ITextCell textCell, AttributedString attributedString, Font font )
  {
    Color fg = convertToColor( textCell.getForeground(), m_colorScheme.getTextColor() );
    Color bg = convertToColor( textCell.getBackground(), m_colorScheme.getBackgroundColor() );

    attributedString.addAttribute( TextAttribute.FAMILY, font.getFamily() );
    attributedString.addAttribute( TextAttribute.SIZE, font.getSize() );
    attributedString.addAttribute( TextAttribute.FOREGROUND, textCell.isReverse() ^ textCell.isHidden() ? bg : fg );
    attributedString.addAttribute( TextAttribute.BACKGROUND, textCell.isReverse() ? fg : bg );

    if ( textCell.isUnderline() )
    {
      attributedString.addAttribute( TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON );
    }
    if ( textCell.isBold() )
    {
      attributedString.addAttribute( TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD );
    }
    if ( textCell.isItalic() )
    {
      attributedString.addAttribute( TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE );
    }
    return textCell.isReverse() ^ textCell.isHidden() ? bg : fg;
  }

  /**
   * Calculates the size (in pixels) of the back buffer image.
   *
   * @param columns
   *          the number of columns, > 0;
   * @param lines
   *          the number of lines, > 0.
   * @return a dimension with the image width and height in pixels.
   */
  private Dimension calculateSizeInPixels( int columns, int lines )
  {
    CharacterDimensions charDims = m_charDims;

    int width = ( columns * charDims.m_width );
    int height = ( lines * ( charDims.m_height + charDims.m_lineSpacing ) );
    return new Dimension( width, height );
  }

  /**
   * Calculates the total insets of this container and all of its parents.
   *
   * @return the total insets, never <code>null</code>.
   */
  private Insets calculateTotalInsets()
  {
    // Take the screen insets as starting point...
    Insets insets = Toolkit.getDefaultToolkit().getScreenInsets( getGraphicsConfiguration() );

    Container ptr = this;
    do
    {
      Insets compInsets = ptr.getInsets();
      insets.top += compInsets.top;
      insets.bottom += compInsets.bottom;
      insets.left += compInsets.left;
      insets.right += compInsets.right;
      ptr = ptr.getParent();
    }
    while ( ptr != null );
    return insets;
  }

  /**
   * Converts a given color index to a concrete color value.
   *
   * @param index
   *          the numeric color index, >= 0;
   * @param defaultColor
   *          the default color to use, cannot be <code>null</code>.
   * @return a color value, never <code>null</code>.
   */
  private Color convertToColor( int index, Color defaultColor )
  {
    if ( index < 1 )
    {
      return defaultColor;
    }
    return m_colorScheme.getColorByIndex( index - 1 );
  }

  /**
   * Draws the cursor on screen.
   *
   * @param canvas
   *          the canvas to paint on;
   * @param cursor
   *          the cursor information;
   * @param color
   *          the color to paint the cursor in.
   */
  private Rectangle drawCursor( final Graphics2D canvas, final ICursor cursor, final Color color )
  {
    if ( !cursor.isVisible() )
    {
      return null;
    }

    CharacterDimensions charDims = m_charDims;

    int cw = charDims.m_width;
    int ch = charDims.m_height;
    int ls = charDims.m_lineSpacing;

    int x = cursor.getX() * cw;
    int y = cursor.getY() * ( ch + ls );

    Rectangle rect = new Rectangle( x, y, cw, ch - 2 * ls );

    canvas.setColor( color );
    canvas.draw( rect );

    return rect;
  }

  /**
   * Returns whether the given keystroke represents a "regular" key, that is, it
   * is defined and not a modifier.
   *
   * @param keystroke
   *          the keystroke to test, cannot be <code>null</code>.
   * @return <code>false</code> if the given keystroke is either not defined or
   *         represents a modifier key (SHIFT, CTRL, etc.), <code>true</code>
   *         otherwise.
   */
  private boolean isRegularKey( KeyStroke keystroke )
  {
    int c = keystroke.getKeyCode();
    return ( c != KeyEvent.VK_UNDEFINED ) && ( c != KeyEvent.VK_SHIFT ) && ( c != KeyEvent.VK_ALT )
        && ( c != KeyEvent.VK_ALT_GRAPH ) && ( c != KeyEvent.VK_META ) && ( c != KeyEvent.VK_WINDOWS )
        && ( c != KeyEvent.VK_CONTROL );
  }
}
TOP

Related Classes of nl.lxtreme.jvt220.terminal.swing.SwingFrontend$CharacterDimensions

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.