gt:         if (0 != (attr&VT320.NORMAL))
context: /*
   * KindleTerminal.java
   * 
   * Copyright (c) 2010 VDP <vdp DOT kindle AT gmail.com>.
   * 
   * This file is part of MidpSSH.
   * 
   * MidpSSH is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * MidpSSH is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with MidpSSH.  If not, see <http ://www.gnu.org/licenses/>.
   */
  
  package kindle;
  
  import awt.AwtSession;
  import com.amazon.kindle.kindlet.event.KindleKeyCodes;
  import com.amazon.kindle.kindlet.ui.KRepaintManager;
  import com.amazon.kindle.kindlet.ui.KTextArea;
  import com.amazon.kindle.kindlet.ui.KindletUIResources;
  import com.amazon.kindle.kindlet.ui.KindletUIResources.KColorName;
  import com.amazon.kindle.kindlet.ui.KindletUIResources.KFontFamilyName;
  import com.amazon.kindle.kindlet.ui.KindletUIResources.KFontStyle;
  import gui.Redrawable;
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.EventQueue;
  import java.awt.Font;
  import java.awt.FontMetrics;
  import java.awt.Graphics;
  import java.awt.Image;
  import java.awt.Rectangle;
  import java.awt.event.ComponentAdapter;
  import java.awt.event.ComponentEvent;
  import java.awt.event.KeyEvent;
  import java.awt.event.KeyListener;
  import org.apache.log4j.Logger;
  import terminal.VT320;
  
  /**
   * The terminal emulation window.
   * 
   * This is the component that renders the emulation buffer
   * and accepts the user keyboard.
   *
   * @author VDP <vdp DOT kindle AT gmail.com>
   */
  public class KindleTerminal 
          extends KTextArea
          implements Redrawable, KeyListener {
  
      private static final int DIRTY_UNDEFINED = -1;
  
      // ------------------------------- Constants
      private static final int ROT_NORMAL = 0;
      private static final int ROT_90 = 2;
      private static final int FONT_SIZE = 20;
  
      // If more than UPDATE_THRESHOLD paint operations are needed the
      // whole screen is refreshed instead.
      private static final int UPDATE_THRESHOLD = 4;
  
      // ------------------------------- Data fields
      private AwtSession session;
      private Font font;
      /** the VDU buffer */
      protected VT320 buffer;
      /** first top and left character in buffer, that is displayed */
      protected int top, left;
      protected int width, height;
      private int fontWidth, fontHeight, fontAscent;
      protected int rotated;
      /** display size in characters */
      public int rows, cols;
      private Image backingStore = null;
      public Color fgcolor;
      public Color bgcolor;
      private final Object paintMutex = new Object();
  
      /** local copy of the lines that were displayed on the previous refresh */
      private char[][] prevChars;
      private int[][] prevAttrs;
  
      /** A buffer to hold the characters of the line currently rendered */
      private char[] renderChars;
  
      /** previous cursor positions */
      int prevCursorX, prevCursorY;
  
      private boolean invalid = true;
  
      private boolean symbolActive = false;
  
      int nDirty;
      Rectangle[] dirtyRects = new Rectangle[UPDATE_THRESHOLD];
      int currentDirty = DIRTY_UNDEFINED;
  
      private KindletUIResources resources;
  
      private Logger log;
  
      private Redrawer redrawer;
  
      /**
       * @param buffer
       */
      public KindleTerminal(VT320 buffer, AwtSession session, KindletUIResources rsrc) {
  
          this.log = Logger.getLogger(KindleTerminal.class.getName());
          
          this.buffer = buffer;
          this.resources = rsrc;
          
          rotated = ROT_NORMAL;
          
          initFont();
  
          this.session = session;
  
          fgcolor = resources.getColor(KColorName.BLACK);
          bgcolor = resources.getColor(KColorName.WHITE);
  
          this.addComponentListener(new ComponentAdapter() {
              public void componentResized(ComponentEvent e) {
                  sizeChanged();
                  redraw();
              }
          });
  
          this.prevCursorX = buffer.cursorX;
          this.prevCursorY = buffer.cursorY;
  
          this.addKeyListener(this);
  
          //setBackground(bgcolor);
  
          sizeChanged();
  
          for (int d = 0; d < dirtyRects.length; d++)
              dirtyRects[d] = new Rectangle();
  
          this.redrawer = new Redrawer(50, 1000, this);
          redrawer.start();
  
          buffer.setDisplay(this);
      }
  
      public void update(Graphics g) {
          log.debug("update() called");
          paint(g);
          //super.update(g);
      }
  
      protected void sizeChanged() {
          width = getWidth();
          height = getHeight();
          if (rotated != ROT_NORMAL) {
              width = getHeight();
              height = getWidth();
          }
          cols = width / fontWidth;
          rows = height / fontHeight;
          if (width > 0 && height > 0) {
              //System.out.println("Backing store created");
              backingStore = createImage(width, height);
          }
  
          int virtualCols = cols;
          int virtualRows = rows;
  
          this.renderChars = new char[cols];
  
          buffer.setScreenSize(virtualCols, virtualRows);
      }
  
      /**
       * Finds all screen regions that needs an update
       *
       * Those rectangles (un)covered by the cursor are also included.
       *
       * @param  rects the result will be recorded here
       * @return the number of valid dirty rectangles
       */
      private int findDirtyRects() {
          boolean isPrevCursorUpdated = false;
          boolean isCursorUpdated = false;
          int cursorCountDn = 2;
          if (buffer.cursorX == prevCursorX &&
                  buffer.cursorY == prevCursorY) {
  
              cursorCountDn = 0;
              isPrevCursorUpdated = true;
              isCursorUpdated = true;
          }
  
          int numDirty = 0;
  
          int nRows = buffer.charArray.length;
          int nCols = buffer.charArray[0].length;
          //int startVisible = buffer.windowBase;
          //int endVisible = buffer.windowBase + buffer.height;
  
          // Init 'previous' buffers
          if (prevChars == null ||
                  prevChars.length != nRows ||
                  prevChars[0].length != nCols) {
  
              prevChars = new char[nRows][];
              prevAttrs = new int[nRows][];
              for (int r=0; r < nRows; r++) {
                  prevChars[r] = (char[]) buffer.charArray[r].clone();
                  prevAttrs[r] = (int[]) buffer.charAttributes[r].clone();
              }
          }
  
          boolean searchFurther = true;
  
          for (int r=0; r < nRows; r++) {
              int startDirty = nCols;
              int endDirty = 0;
  
              char[] prevLine = prevChars[r];
              int[] prevLineAttrs = prevAttrs[r];
              char[] curLine = buffer.charArray[r];
              int[] curLineAttrs = buffer.charAttributes[r];
  
              for (int c=0; c < nCols; c++) {
                  if(prevLine[c] != curLine[c] ||
                     prevLineAttrs[c] != curLineAttrs[c]) {
                      if (c < startDirty)
                          startDirty = c;
  
                      endDirty = c;
  
                      prevLine[c] = curLine[c];
                      prevLineAttrs[c] = curLineAttrs[c];
                  }
              }
  
              if (searchFurther && startDirty <= endDirty) {
                  // prevCursorY == r-startVisible ??? (is it absolute or relative)
                  if (cursorCountDn != 0 && prevCursorY == r &&
                      prevCursorX <= endDirty && prevCursorX >= startDirty) {
                      isPrevCursorUpdated = true;
                      --cursorCountDn;
                  }
  
                  if (cursorCountDn != 0 && buffer.cursorY == r &&
                      buffer.cursorX <= endDirty && buffer.cursorX >= startDirty) {
                      isCursorUpdated = true;
                      --cursorCountDn;
                  }
  
                  if (cursorCountDn + numDirty >= UPDATE_THRESHOLD)  {
                      searchFurther = false; // (probably) doesn't make sense to continue
                      continue;
                  }
  
                  this.dirtyRects[numDirty++].setBounds(startDirty, r, endDirty-startDirty+1, 1);
                  //System.out.println("Dirty rect: " + nDirty + " cursorCountDn: " + cursorCountDn);
              }
          }
  
          if (!isPrevCursorUpdated) {
              this.dirtyRects[numDirty++].setBounds(prevCursorX, prevCursorY, 1, 1);
          }
  
          if (!isCursorUpdated) {
              this.dirtyRects[numDirty++].setBounds(buffer.cursorX, buffer.cursorY, 1, 1);
          }
  
          prevCursorX = buffer.cursorX;
          prevCursorY = buffer.cursorY;
  
          return numDirty;
      }
  
      private String attrString(int attr) {
          StringBuffer sb = new StringBuffer();
          sb.append("[FG:").append(Integer.toString((attr&VT320.COLOR_FG) >> 4));
          sb.append(" BG:").append(Integer.toString((attr&VT320.COLOR_BG) >> 8));
          if (0 != (attr&VT320.BOLD))
              sb.append(',').append("BLD");
          if (0 != (attr&VT320.INVERT))
              sb.append(',').append("INV");
  