view anagram/vaclgui/agview.cpp @ 8:ec2b657edf13

Add explicit lint-comment-style fallthrough annotations. GCC now assumes that if you don't have these you're making a mistake, which is annoying. XXX: This changeset updates the AG output files only (by hand) and is XXX: abusive - rebuilding them will erase the change. However, I need XXX: to get things to build before I can try to get AG to issue the XXX: annotations itself, so this seems like a reasonable expedient.
author David A. Holland
date Mon, 30 May 2022 23:51:43 -0400
parents 13d2b8934445
children
line wrap: on
line source

/*
 * AnaGram, A System for Syntax Directed Programming
 * Copyright 1997-2002 Parsifal Software. All Rights Reserved.
 * See the file COPYING for license and usage terms.
 *
 * agview.cpp
 */

#include <icoordsy.hpp>
#include <windows.h>

#include "agstring.h"
#include "agview.hpp"
#include "arrays.h"
#include "config.h"
#include "ctrlpanel.hpp"
#include "dspar.hpp"
#include "frame.hpp"
#include "minmax.h"
#include "vaclgui.hpp"

//#define INCLUDE_LOGGING
#include "log.h"


int focusWindowId;

const int AgView::defaultWindowHeight = 12;


Boolean AgFocusHandler::dispatchHandlerEvent(IEvent &event) {
  //LOGSECTION("AgFocusHandler::dispatchHandlerEvent", Log::off);
  LOGSECTION_OFF("AgFocusHandler::dispatchHandlerEvent");
  static int focusHandlerCalls = 0;
  focusHandlerCalls++;
  LOGV(event.eventId());
  if (event.eventId() == WM_SETFOCUS) {
    LOGS("WM_SETFOCUS") LCV((int) event.controlWindow());
    int flag;
    flag = gotFocus(event);
    if (flag) {
      event.setResult(flag);
    }
    return flag;
  }
  if (event.eventId() == WM_KILLFOCUS) {
    LOGS("WM_KILLFOCUS") LCV((int) event.controlWindow());
    int flag = lostFocus(event);
    event.setResult(flag);
    return flag;
  }
  return false;
}

AgColumnHead::AgColumnHead(int id, IWindow* parent)
  : IStaticText(id, parent, parent)
  , title()
  , nCols(0)
  , tabs(0)
  , xPos(0)
  , color(&ColorSpec::inactiveTitle)
  , dataColorChange(this, onColorChange)
{
  LOGSECTION("AgColumnHead constructor");
  IPaintHandler::handleEventsFor(this);
/*
  yBias =
    ICoordinateSystem::isConversionNeeded()
    ? font().maxSize().height() - font().maxDescender()
    : 0;
  LOGV(yBias);
*/
  dataColorChange.attach(&ColorSpec::data);
  dataColorChange.attach(&ColorSpec::inactiveTitle);
  dataColorChange.attach(&ColorSpec::activeTitle);
  setText("Column headers");
  show();
}


AgColumnHead::~AgColumnHead() {
  IPaintHandler::stopHandlingEventsFor(this);
}

void AgColumnHead::setTitles(int nCols_, AgString title_) {
  LOGSECTION("AgColumnHead::setTitles");
  nCols = nCols_;
  title = title_;

  setText(title.pointer());
  LOGV(title.pointer());
  LOGV(size().asString());
}

void AgColumnHead::setMargin(int margin_)  {
  margin = margin_;
}

void AgColumnHead::setPos(int pos_)  {
  xPos = pos_;
}

void AgColumnHead::setTabs(int *tabs_) {
  tabs = tabs_;
}

Boolean AgColumnHead::paintWindow(IPaintEvent &event) {
  int i = 0;

  if (event.rect().area() == 0) {
    return true;
  }
  LOGSECTION("AgColumnHead::paintWindow");
  LOGV(title.pointer());
  IPresSpaceHandle handle = event.presSpaceHandle();    //cookie
  SetBkMode(handle, TRANSPARENT);
  event.clearBackground(color->bg());
  font().beginUsingFont(handle);
  int textHeight = font().maxSize().height();
  int verticalMargin = (size().height() - textHeight)/2;

  LOGV(textHeight);
  LOGV(verticalMargin);
  //LOGV(yBias);
  LOGV(margin);
  LOGV(xPos);
  int yBias =
    ICoordinateSystem::isConversionNeeded()
    ? font().maxSize().height() - font().maxDescender()
    : 0;
  IPoint trueWhere(margin - xPos, verticalMargin + yBias);
  IPoint where =
    ICoordinateSystem::isConversionNeeded()
    ?  ICoordinateSystem::convertToNative(trueWhere, size())
    :  trueWhere;

  LOGV(trueWhere.asString());
  LOGV(where.asString());
  AgString temp(title.pointer());
  FindTabs tabsKluge(temp.pointer());
  char *p = tabsKluge.getField();
  LOGV(rect().asString());
  while (i < nCols) {
    LOGV(i);
    LOGV(p);
    LOGV(where.asString());
    LOGV(color->fg().asString());
    LOGV(p) LCV(where.asString());
    event.drawText(p,where, color->fg());
    where += IPoint(tabs[i], 0);
    p = tabsKluge.getField();
    i++;
  }
  font().endUsingFont(handle);
  return true;
}

AgStaticText &AgStaticText::scrollWindow(IPoint distance) {
  IRectangle clipRect(size());

  //LOGSECTION("AgStaticText::scrollWindow", Log::off);
  LOGSECTION_OFF("AgStaticText::scrollWindow");
  LOGV(distance.asString());
  LOGV(clipRect.asString());

  RECTL rct = clipRect.asRECTL();
  ScrollWindowEx(handle(), distance.x(), distance.y(), 0, (RECT *) &rct,
                 0,0,SW_INVALIDATE);
  return *this;
}

AgView &AgView::hideCursor() {
  if (!cursorEnabled) {
    return *this;
  }
  //LOGSECTION("AgView::hideCursor", Log::off);
  LOGSECTION_OFF("AgView::hideCursor");
  cursorHideCount++;
  LOGV(cursorHideCount);
  IFLOG(int flag = ) HideCaret(dataArea.handle());
  LOGV(flag);
  return *this;
}

AgView &AgView::showCursor() {
  if (!cursorEnabled) {
    return *this;
  }
  //LOGSECTION("AgView::showCursor", Log::off);
  LOGSECTION_OFF("AgView::showCursor");
  cursorHideCount--;
  LOGV(cursorHideCount);
  IFLOG(int flag = ) ShowCaret(dataArea.handle());
  LOGV(flag);
  return *this;
}



AgView::AgView(IWindow *ownerWindow_)
  : ICanvas(nextChildId(), ownerWindow_, ownerWindow_)
  , ownerWindow(ownerWindow_)
  , horizontalScrollBar(nextChildId(), this, this, IRectangle(),
                          IScrollBar::horizontal
                        | IWindow::visible)
  , verticalScrollBar(nextChildId(), this, this, IRectangle(),
                        IScrollBar::vertical
                      | IWindow::visible)
  , dataHole(nextChildId(), this, this)
  , dataArea(nextChildId(), &dataHole, &dataHole)
  , vsbShowing(false)
  , hsbShowing(false)
  , columnHeadTitle(nextChildId(), this)
  , columnHeadsPresent(0)
  , cursorLine(0)
  , mouseDown(0)
  , tabArray(0)
  , tableWidth(0)
  , tableHeight(0)
  , cursorEnabled(0)
  , cursorLocation(0,0)
  , pixelCursor(font().avgCharWidth(),0)
  , cursorLineHighlight(0)
  , prevHorizontal(0)
  , prevVertical(0)
  , color(&ColorSpec::syntaxFile)
  , cursorColor(&ColorSpec::activeCursor)
  , rightButtonDown(0)
  , layoutActive(0)
  , retainCursor(cursorLineHighlight)
  , frameWindow(0)
  , dataColorChange(this, onColorChange)
{
  LOGSECTION("AgView constructor");
  LOGV(id()) LCV((int) this) LCV(handle().asDebugInfo());
  LOGV(dataArea.id()) LCV((int) &dataArea) 
    LCV(dataArea.handle().asDebugInfo());
  dataColorChange.attach(&ColorSpec::data);
  dataColorChange.attach(&ColorSpec::inactiveCursor);
  dataColorChange.attach(&ColorSpec::activeCursor);
  for (int i = 0; i < 256; i++) {
    charWidth[i] = font().charWidth(i);
  }
  IWindow *f;
  for (f = ownerWindow; !f->isFrameWindow(); f=f->parent());
  frameWindow = (AgFrame *) f;
  windowId = id();
  AgString title = columnTitleText();
  LOGV(title.pointer());
  if (title.exists()) {
    columnHeadTitle.setTitles(nColumns(), title);
    ISize size = columnHeadTitle.size();
    columnHeadTitle.setMinimumSize(size);
    LOGV(size.asString());
    columnHeadTitle.setMargin(avgCharWidth());
    columnHeadTitle.enableFillBackground();
    columnHeadsPresent = 1;
  }

  horizontalScrollBar.setScrollableRange(IRange(0, 100));
  horizontalScrollBar.moveScrollBoxTo(0);
  verticalScrollBar.setScrollableRange(IRange(0, 100));
  verticalScrollBar.moveScrollBoxTo(0);

  IKeyboardHandler::handleEventsFor(&dataArea);
  IMouseHandler   ::handleEventsFor(&dataArea);
  IMouseHandler   ::handleEventsFor(&columnHeadTitle);
  IMouseHandler   ::handleEventsFor(&verticalScrollBar);
  IMouseHandler   ::handleEventsFor(&horizontalScrollBar);
  IPaintHandler   ::handleEventsFor(&dataArea);
  IResizeHandler  ::handleEventsFor(this);
  IScrollHandler  ::handleEventsFor(this);
  show();
}

AgView::~AgView() {
  delete [] tabArray;
  if (cursorEnabled) {
    AgFocusHandler::stopHandlingEventsFor(this);
    AgFocusHandler::stopHandlingEventsFor(&dataArea);
  }
  IKeyboardHandler::stopHandlingEventsFor(&dataArea);
  IMouseHandler   ::stopHandlingEventsFor(&dataArea);
  IMouseHandler   ::stopHandlingEventsFor(&columnHeadTitle);
  IMouseHandler   ::stopHandlingEventsFor(&verticalScrollBar);
  IMouseHandler   ::stopHandlingEventsFor(&horizontalScrollBar);
  IPaintHandler   ::stopHandlingEventsFor(&dataArea);
  IResizeHandler  ::stopHandlingEventsFor(this);
  IScrollHandler  ::stopHandlingEventsFor(this);
}

AgView &AgView::enableCursorBar(int flag) {
  flag = (flag != 0);
  if (flag == cursorLineHighlight) {
    return *this;
  }
  cursorLineHighlight = flag;
  if (flag) {
    retainCursor = 1;
    AgFocusHandler::handleEventsFor(&dataArea);
    AgFocusHandler::handleEventsFor(this);
    LOGV(dataArea.hasFocus());
    LOGV(hasFocus());
    if (dataArea.hasFocus()) {
      LOGV(dataArea.size().height());
      cursorOn();
      //setCursorPos(pixelCursor);
    }
  }
  else {
    if (dataArea.hasFocus()) {
      //hideCursor();
      cursorOff();
    }
    AgFocusHandler::stopHandlingEventsFor(&dataArea);
    AgFocusHandler::stopHandlingEventsFor(this);
  }
  return *this;
}

AgView &AgView::enableCursor(int flag) {
  LOGSECTION("AgView::enableCursor");
  LOGV(cursorEnabled) LCV(flag);
  flag = (flag != 0);
  if (flag == cursorEnabled) {
    return *this;
  }
  LOGS("changing cursor state");
  cursorEnabled = flag;
  LOGV(cursorEnabled);
  if (flag) {
    retainCursor = 0;
    AgFocusHandler::handleEventsFor(&dataArea);
    AgFocusHandler::handleEventsFor(this);
    LOGV(dataArea.hasFocus());
    LOGV(hasFocus());
    if (dataArea.hasFocus()) {
      LOGV(dataArea.size().height());
      cursorOn();
      //setCursorPos(pixelCursor);
    }
  }
  else {
    if (dataArea.hasFocus()) {
      //hideCursor();
      cursorOff();
    }
    AgFocusHandler::stopHandlingEventsFor(&dataArea);
    AgFocusHandler::stopHandlingEventsFor(this);
  }
  return *this;
}

AgView &AgView::cursorOn() {
  //LOGSECTION("AgView::cursorOn", Log::off);
  LOGSECTION_OFF("AgView::cursorOn");
  cint origin(horizontalScrollBar.scrollBoxPosition(),
	      lineHeight()*verticalScrollBar.scrollBoxPosition());
  pixelCursor.y = lineHeight()*cursorLine;
  pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
  cint relativeCursor = pixelCursor - origin;
  LOGV(relativeCursor);
  LOGV(cursorLine);
  LOGV(pixelCursor);
  LOGV(dataArea.rect().asString());
  LOGV(dataArea.hasFocus());
  IFLOG(int flag = ) CreateCaret(dataArea.handle(), NULL,0, lineHeight());
  LOGV(flag);
  SetCaretPos(relativeCursor.x, relativeCursor.y);
  IFLOG(flag = ) ShowCaret(dataArea.handle());
  LOGV(flag);
  cursorHideCount = 0;
  return *this;
}

AgView &AgView::setCursorPos(cint cursor) {
  if (!cursorEnabled) {
    return *this;
  }
  //LOGSECTION("AgView::setCursorPos", Log::off);
  LOGSECTION("AgView::setCursorPos");
  LOGSTACK;
  cint origin(horizontalScrollBar.scrollBoxPosition(),
              lineHeight()*verticalScrollBar.scrollBoxPosition());

  //cint origin(0, lineHeight()*verticalScrollBar.scrollBoxPosition());
  cint relativeCursor = cursor - origin;
  LOGV(cursor) LCV(origin) LCV(relativeCursor);
  hideCursor();
  IFLOG(int flag = ) SetCaretPos(relativeCursor.x, relativeCursor.y);
  LOGV(flag);
  showCursor();
  return *this;
}

AgView &AgView::cursorOff() {
  //LOGSECTION("AgView::cursorOff", Log::off);
  LOGSECTION_OFF("AgView::cursorOff");
  IFLOG(int flag = ) DestroyCaret();
  LOGV(flag);
  return *this;
}

Boolean AgView::gotFocus(IEvent &) {
  //LOGSECTION("AgView::gotFocus", Log::off);
  LOGSECTION_OFF("AgView::gotFocus");
  //LOGSTACK;
  if (cursorEnabled) {
    LOGV(pixelCursor);
    LOGV(dataArea.size().height());
    cursorOn();
    //setCursorPos(pixelCursor);
  }
  columnHeadTitle.color = &ColorSpec::activeTitle;
  columnHeadTitle.refresh();
  if (cursorLineHighlight) {
    cursorColor = &ColorSpec::activeCursor;
    repaintLine(cursorLine);
  }
  focusWindowId = windowId;
  return false;
  //return true;
}

Boolean AgView::lostFocus(IEvent &) {
  //LOGSECTION("AgView::lostFocus", Log::off);
  LOGSECTION_OFF("AgView::lostFocus");
  //LOGSTACK;
  if (cursorEnabled) {
    //hideCursor();
    cursorOff();
  }
  columnHeadTitle.color = &ColorSpec::inactiveTitle;
  columnHeadTitle.refresh();
  if (cursorLineHighlight) {
    cursorColor = &ColorSpec::inactiveCursor;
    repaintLine(cursorLine);
  }
  return false;
}

int AgView::findMaxWidth() {
  LOGSECTION("AgView::findMaxWidth");
  int maxWidth = 0;
  int nl = nLines();
  int nCols = nColumns();
  LOGV(nl) LCV(nCols);
  char *columnTitles = columnTitleText().pointer();
  int i;

  if (tabArray == NULL) {
    tabArray = new int[nCols + 1];
    memset(tabArray, 0, (nCols+1)*sizeof(int));
  }
  IFont windowFont = font();

  if (columnTitles) {
    //char *titles = new char[1+strlen(columnTitles)];
    //char *titles = local_array(1+strlen(columnTitles), char);
    LocalArray<char> titles(1+strlen(columnTitles));
    strcpy(titles, columnTitles);
    FindTabs tabs(titles);
    char *p = tabs.getField();
    i = 0;
    while(i < nCols && p) {
      int width = windowFont.textWidth(p);
      if (width > tabArray[i]) {
	tabArray[i] = width;
      }
      if (++i >= nCols) {
	break;
      }
      p = tabs.getField();
    }
    while (i < nCols) {
      tabArray[i++] = 0;
    }
    //delete [] titles;
  }
  else {
    for (i = 0; i < nCols; i++) {
      tabArray[i] = 0;
    }
  }
  columnHeadWidth = 0;
  for (i = 0; i < nCols; i++) {
    columnHeadWidth += tabArray[i] + 2*avgCharWidth();
  }
  //int *width = new int[nCols];
  //int *width = local_array(nCols, int);
  int *width = LocalArray<int>(nCols);
  memset(width, 0, nCols*sizeof(int));
  LOGV((int) width);
  LOGV((int) tabArray);
  int enWidth = avgCharWidth();
  int columnWidth = enWidth*tab_spacing;
  while (nl--) {
    LOGV(nl);
    AgString line = getLine(nl);
    LOGV(line);
    LOGV(nl);
    FindTabs tabs(line.pointer());
    char *p = tabs.getField();
    LOGV(nl) LCV((int) p);
    LOGV(p);
    int whereX = 0;
    if (nCols > 1) {
      i = 0;
      while (i < nCols && p) {
        width[i] = windowFont.textWidth(p);
        LOGV(width[i]);
        if (++i >= nCols) {
	  break;
	}
        p = tabs.getField();
      }
      if (i < nCols) {
	continue;
      }
    }
    else {
      while (p) {
	width[0] = whereX + font().textWidth(p);
	whereX = ((width[0] + columnWidth)/columnWidth) * columnWidth;
	p = tabs.getField();
      }
    }
    for (i = 0; i < nCols; i++) {
      if (width[i] > tabArray[i]) {
        tabArray[i] = width[i];
        LOGS("***") LV(tabArray[i]);
      }
      LOGS("***") LV(tabArray[i]);
    }
  }
  LOGS("width measured");
  //delete [] width;
  for (i = 0; i < nCols; i++) {
    tabArray[i] += 2*avgCharWidth();
    maxWidth += tabArray[i];
  }
  LOGV(maxWidth);
  return maxWidth;
}

ICanvas &AgView::layout() {
  LOGSECTION("AgView::layout");
  if (layoutActive) {
    return *this;
  }
  layoutActive++;
  doLayout();
  ICanvas::setLayoutDistorted(0, IWindow::layoutChanged);
  ICanvas::layout();
  layoutActive--;
  return *this;
}

ICanvas &AgView::setLayoutDistorted(unsigned long i, unsigned long j) {
  //LOGSECTION("AgView::setLayoutDistorted", Log::off);
  LOGSECTION_OFF("AgView::setLayoutDistorted");
  //i &= ~IWindow::fontChanged;
  //j |= IWindow::fontChanged;
  tableWidth = 0;
  ICanvas::setLayoutDistorted(i, j);
  return *this;
}

Boolean AgView::windowResize(IResizeEvent &){
  LOGSECTION("AgView resize event");
  setLayoutDistorted(IWindow::layoutChanged,0);
  return false;
}

ISize AgView::suggestSize() {
  LOGSECTION("AgView::suggestSize");
  int width = findMaxWidth();
  int minWidth = 40*font().avgCharWidth();
  int maxWidth = 60*font().avgCharWidth();
  if (width < minWidth) {
    width = minWidth;
  }
  if (width > maxWidth) {
    width = maxWidth;
  }
  int height = tableHeight;
  if (nLines() > defaultWindowHeight) {
    width += verticalScrollBar.minimumSize().width() + 1;
    height = lineHeight() * defaultWindowHeight;
  }
  else if (nLines() < 5) {
    height = lineHeight() * 5;
  }
  LOGV(width);
  LOGV(height);
  return ISize(width, height);
}

Boolean AgView::paintWindow(IPaintEvent &event) {
  if (event.controlWindow() != &dataArea) {
    return false;
  }

  int height = lineHeight();
  LOGSECTION("AgView::paintWindow");

  IPresSpaceHandle handle = event.presSpaceHandle();    //cookie
  SetBkMode(handle, TRANSPARENT);

  font().beginUsingFont(handle);
  LOGV(event.rect().asString());
  IRectangle invalidRect(
    ICoordinateSystem::isConversionNeeded()
    ? ICoordinateSystem::convertToApplication(event.rect(),dataArea.size())
    : event.rect()
  );
  LOGV(invalidRect.asString());
  LOGV(dataHole.rect().asString());
  LOGV(dataArea.rect().asString());

  int baseLine  = verticalScrollBar.scrollBoxPosition();
  int topY = baseLine*lineHeight();
  int minY = topY + invalidRect.minY();
  int maxY = topY + invalidRect.maxY();
  int bottomY = topY + dataHole.size().height();
  LOGV(minY) LCV(maxY);
  LOGV(topY) LCV(bottomY);
  if (minY < topY) {
    minY = topY;
  }
  if (maxY > bottomY) {
    maxY = bottomY;
  }
  invalidRect = IRectangle(IPoint(invalidRect.minX(), minY),
			   (IPoint(invalidRect.maxX(), maxY)));

  LOGV(invalidRect.asString());
  LOGV(dataHole.size().asString());
  int firstLine = invalidRect.minY()/height;
  int lastLine  = (invalidRect.maxY()+height - 1)/height - 1;

  if (firstLine < baseLine) {
    firstLine = baseLine;
    lastLine += baseLine;
  }

  int yBias =
    ICoordinateSystem::isConversionNeeded()
    ? height - maxDescender()
    : 0;


  int xMargin = avgCharWidth();

  int whereX = -horizontalScrollBar.scrollBoxPosition();
  int whereY = firstLine*height - topY;  //baseline location

  IColor    textBgndColor=color->bg();
  IColor    textFgndColor=color->fg();
  IColor    cursorBgndColor=color->bg();
  IColor    cursorFgndColor=color->fg();
  if (cursorLineHighlight) {
    cursorBgndColor=cursorColor->bg();
    cursorFgndColor=cursorColor->fg();
  }

  LOGV(baseLine);
  LOGV(firstLine);
  LOGV(lastLine);
  LOGV(horizontalScrollBar.scrollBoxPosition());

  int lastInvalidLine = lastLine;
  if (lastLine >= nLines()) {
    lastLine = nLines() - 1;
  }
  int windowHeight = dataArea.rect().size().height();
  int dataAreaWidth = dataArea.rect().size().width() - whereX;
  ISize lineSize(dataAreaWidth, height);
  IPoint offset(xMargin, 0);
  int k;

  for (k = firstLine; k <= lastLine; k++, whereY += height) {
    //LOGSECTION("AgDataView::paintWindow line loop", Log::off);
    LOGSECTION_OFF("AgDataView::paintWindow line loop");
    IPoint trueWhere(whereX, whereY + yBias);
    IPoint where =
      ICoordinateSystem::isConversionNeeded()
      ?  ICoordinateSystem::convertToNative(trueWhere, dataArea.size())
      :  trueWhere;
    IRectangle trueLineRect(0, whereY, dataAreaWidth, whereY+height);
    IRectangle lineRect =
      ICoordinateSystem::isConversionNeeded()
      ?  ICoordinateSystem::convertToNative(trueLineRect, dataArea.size())
      :  trueLineRect;
    IColor bgndColor = textBgndColor;
    IColor fgndColor = textFgndColor;
    if (k == cursorLine) {
      bgndColor = cursorBgndColor;
      fgndColor = cursorFgndColor;
    }
    LOGV(lineRect.asString());
    LOGV(where.asString());
    event.clearBackground(lineRect,bgndColor);
    AgString line = getLine(k);
    LOGV(line.pointer());
    FindTabs tabs(line.pointer());
    int enWidth = avgCharWidth();
    int columnWidth = enWidth*tab_spacing;
    char *p = tabs.getField();
    int nCols = nColumns();
    where += offset;
    int i = 0;
    if (nCols > 1) {
      while (i < nCols && p) {
	LOGV(p - line.pointer());
	event.drawText(p, where, fgndColor);
	where += IPoint(tabArray[i], 0);
	if (++i >= nCols) {
	  break;
	}
	p = tabs.getField();
      }
    }
    else {
      while (p) {
	event.drawText(p, where, fgndColor);
	int whereX = where.x();
	whereX += font().textWidth(p);
	whereX += horizontalScrollBar.scrollBoxPosition() - enWidth;
	whereX = ((whereX + columnWidth)/columnWidth) * columnWidth;
	whereX -= horizontalScrollBar.scrollBoxPosition() - enWidth;
	where.setX(whereX);
	p = tabs.getField();
      }
    }
  }
  if (k <= lastInvalidLine) {
    IRectangle trueRemainder(0,whereY,dataAreaWidth, windowHeight);
    IRectangle remainder =
      ICoordinateSystem::isConversionNeeded()
      ?  ICoordinateSystem::convertToNative(trueRemainder, dataArea.size())
      :  trueRemainder;
    LOGV(remainder.asString());
    event.clearBackground(remainder,textBgndColor);
  }
  font().endUsingFont(handle);
  return true;
}

AgView &AgView::repaintLine(int line) {
  LOGSECTION("AgView::repaintLine");
  if (line < 0 || line >= nLines()) {
    return *this;
  }
  int baseLine  = verticalScrollBar.scrollBoxPosition();
  int y = (line - baseLine) * lineHeight();
  IRectangle rect(0, y, dataArea.rect().size().width(), y + lineHeight());
  LOGV(line) LCV(baseLine) LCV(y) LCV(rect.asString());
  dataArea.refresh(rect);
  return *this;
}

AgView &AgView::updateCursor(int line) {
  LOGSECTION_OFF("AgView::updateCursor");
  LOGV(line) LCV(cursorLine);
  if (retainCursor && line != cursorLine) {
    int oldCursor = cursorLine;
    if (cursorLine < line) {
      cursorLocation.y = cursorLine = line;
    }
    int bottomLine = line + verticalScrollBar.visibleCount() - 1;
    if (cursorLine > bottomLine) {
      cursorLocation.y = cursorLine = bottomLine;
    }
    LOGV(line) LCV(cursorLine) LCV(bottomLine);
    if (cursorLineHighlight && cursorLine != oldCursor) {
      repaintLine(oldCursor);
      repaintLine(cursorLine);
    }
  }
  reposition();
  cursorLocation.y = cursorLine;
  pixelCursor.y = lineHeight() * cursorLine;
  if (cursorEnabled && dataArea.hasFocus()) {
    setCursorPos(pixelCursor);
  }
  return *this;
}

AgView &AgView::reposition() {
  //LOGSECTION("AgView::reposition", Log::off);
  LOGSECTION("AgView::reposition");

  if (cursorEnabled) hideCursor();
  int verticalOffset = verticalScrollBar.scrollBoxPosition()
                   - prevVertical;

  int horizontalOffset = horizontalScrollBar.scrollBoxPosition()
                   - prevHorizontal;

  IPoint displacement(-horizontalOffset, -lineHeight()*verticalOffset);

  LOGV(displacement.asString());

  dataArea.scrollWindow(displacement);
  dataArea.refresh();
  prevHorizontal = horizontalScrollBar.scrollBoxPosition();
  prevVertical = verticalScrollBar.scrollBoxPosition();
  cursorLocation.y = cursorLine;
  pixelCursor.y = cursorLine*lineHeight();
  if (cursorEnabled) {
    setCursorPos(pixelCursor).showCursor();
  }
  return *this;
}

AgView &AgView::repaintCursor(int line) {
  LOGSECTION("AgView::repaintCursor");
  LOGV(line) LCV(cursorLine);
  cursorLocation.y = cursorLine;
  pixelCursor.y = cursorLine*lineHeight();
  if (cursorLineHighlight && cursorLine != line) {
    repaintLine(line);
    repaintLine(cursorLine);
  }
  return *this;
}

AgView &AgView::updateCursor() {
  LOGSECTION("AgView::updateCursor");
  cursorLocation.y = cursorLine;
  LOGV(cursorLocation);
  pixelCursor.y = cursorLine*lineHeight();
  LOGV(pixelCursor);
  if (cursorEnabled) {
    setCursorPos(pixelCursor);
  }
  return *this;
}

/*
 *
 * Layout considerations:
 *
 * The data view consists of up to four parts: two scroll bars, a heading
 * and a data area.
 *
 * The presence or absence of the scroll bars depends on the relative size
 * of the data area and the size of the table to be displayed.
 */

AgView &AgView::doLayout() {
  LOGSECTION("AgView::doLayout");
  if (cursorEnabled && dataArea.hasFocus()) {
    hideCursor();
  }

  if (tableWidth == 0) {
    tableWidth = findMaxWidth();
  }
  tableHeight = lineHeight()*nLines();

  LOGV(tableWidth);
  LOGV(tableHeight);
  columnHeadTitle.setTabs(tabArray);
  ISize canvasSize = size();
  int canvasWidth  = canvasSize.width();
  int canvasHeight = canvasSize.height();
  if (canvasWidth == 0 && canvasHeight == 0) {
    return *this;
  }

  LOGV(canvasWidth);
  LOGV(canvasHeight);

  int dataWidth    = canvasWidth;
  int dataHeight   = canvasHeight;
  int dataY        = 0;
  if (columnHeadsPresent) {
    dataY          = columnHeadTitle.minimumSize().height();
    dataHeight    -= dataY;
    columnHeadTitle.moveTo(IPoint(0,0));
  }

  LOGV(dataWidth);
  LOGV(dataHeight);

  LOGV(tableWidth);
  LOGV(tableHeight);


  int vsbWidth     = verticalScrollBar.minimumSize().width();
  int hsbHeight    = horizontalScrollBar.minimumSize().height();

  int horizontalPosition = horizontalScrollBar.scrollBoxPosition();
  int verticalPosition   = verticalScrollBar.scrollBoxPosition();

  Boolean vsb      = true;
  Boolean hsb      = true;

  if (tableHeight > canvasHeight
      && tableWidth <= canvasWidth - vsbWidth - 1) {
    // vertical scroll bar only
    vsb = true;
    hsb = false;
  }
  else if (tableWidth > canvasWidth
	   && tableHeight <= canvasHeight - hsbHeight - 1) {
    // horizontal scroll bar only
    vsb = false;
    hsb = true;
  }
  else if (tableWidth <= canvasWidth && tableHeight <= canvasHeight) {
    // no scroll bars
    vsb = hsb = false;
  }
  LOGV(vsb) LCV(vsbShowing);
  LOGV(hsb) LCV(hsbShowing);

  if (vsb && !vsbShowing) {
    verticalScrollBar.moveScrollBoxTo(0);
    prevVertical = 0;
    verticalScrollBar.show();
    vsbShowing = true;
  }
  if (!vsb && vsbShowing) {
    verticalScrollBar.hide();
    vsbShowing = false;
    verticalScrollBar.moveScrollBoxTo(0);
    prevVertical = 0;
  }

  if (hsb && !hsbShowing) {
    horizontalScrollBar.show();
    hsbShowing = true;
    horizontalScrollBar.moveScrollBoxTo(0);
    prevHorizontal = 0;
  }
  if (!hsb && hsbShowing) {
    horizontalScrollBar.hide();
    hsbShowing = false;
    horizontalScrollBar.moveScrollBoxTo(0);
    prevHorizontal = 0;
  }

  if (vsbShowing) {
    dataWidth -= vsbWidth + 1;
    verticalScrollBar.moveTo(IPoint(dataWidth+1, 0));
    verticalScrollBar.sizeTo(ISize(vsbWidth, canvasHeight));
  }

  if (hsbShowing) {
    dataHeight -= hsbHeight + 1;
    horizontalScrollBar.moveTo(IPoint(0, dataHeight+1));
    horizontalScrollBar.sizeTo(ISize(dataWidth, hsbHeight));
  }

  dataHole.sizeTo(ISize(dataWidth, dataHeight));
  dataHole.moveTo(IPoint(0, dataY));

  LOGV(nLines());
  LOGV(lineHeight());
  LOGV(nLines()*lineHeight());

  int desiredWidth = tableWidth;
  if (tableWidth < dataWidth) {
    desiredWidth = dataWidth;
  }
  ISize desiredSize(desiredWidth, dataHeight);

  LOGV(desiredSize.asString());

  dataArea.sizeTo(desiredSize);

  LOGV(dataArea.size().asString());
  LOGV(dataArea.parentSize().asString());

  dataArea.moveTo(IPoint(0, 0));
  LOGV(rect().asString());
  LOGV(dataArea.rect().asString());
  LOGV(horizontalScrollBar.rect().asString());
  LOGV(verticalScrollBar.rect().asString());

  horizontalScrollBar.setScrollableRange(IRange(0,tableWidth-1));
  horizontalScrollBar.setVisibleCount(dataWidth);

  verticalScrollBar.setVisibleCount(dataHeight/lineHeight());
  verticalScrollBar.setScrollableRange(IRange(0, nLines() - 1));

  horizontalPosition = min(
    (int) horizontalScrollBar.scrollBoxRange().upperBound(),
    horizontalPosition
  );
  verticalPosition = min(
    (int) verticalScrollBar.scrollBoxRange().upperBound(),
    verticalPosition
  );

  verticalPosition = max(
    (int)(cursorLine - verticalScrollBar.visibleCount() + 1),
    verticalPosition
  );

  horizontalScrollBar.setMinScrollIncrement(maxCharWidth());
  verticalScrollBar.setMinScrollIncrement(1);

  horizontalScrollBar.moveScrollBoxTo(horizontalPosition);
  prevHorizontal = horizontalPosition;
  verticalScrollBar.moveScrollBoxTo(verticalPosition);
  prevVertical = verticalPosition;
  if (columnHeadsPresent) {
    ISize headSize(dataWidth, columnHeadTitle.minimumSize().height());
    columnHeadTitle.sizeTo(headSize);
    columnHeadTitle.xPos = horizontalPosition;
    columnHeadTitle.refresh();
  }
  refresh();
  LOGV(dataArea.hasFocus());
  LOGV(vsb) LCV(vsbShowing);
  LOGV(hsb) LCV(hsbShowing);
  if (cursorEnabled && dataArea.hasFocus()) {
    setCursorPos(pixelCursor).showCursor();
  }
  return *this;
}

int AgView::charPosition(int xPos, AgString line) {
  int i;
  int enWidth = avgCharWidth();
  int x = enWidth;
  int columnWidth = enWidth*tab_spacing;
  int spaceWidth = charWidth[' '];
  unsigned char *p = (unsigned char *) line.pointer();

  //LOGSECTION("AgView::charPosition", Log::off);
  LOGSECTION_OFF("AgView::charPosition");

  LOGV(p) LCV(xPos);

  if (p == 0) {
    return 0;
  }
  for (i = 0; p[i]; i++) {
    int newX = x + charWidth[p[i]];
    if (p[i] == '\t') {
      LOGSECTION("AgView::charPosition::tab");
      LOGV(x) LCV(columnWidth);
      int j = 0;
      if (nColumns() > 1 && tabArray) {
        while (tabArray[j] && tabArray[j] <= x) {
	  j++;
	}
        if (tabArray[j] > newX) {
	  newX = tabArray[j];
	}
      }
      else {
/*
        int hPos = horizontalScrollBar.scrollBoxPosition();
        newX = x + hPos - enWidth;
        newX = ((newX + columnWidth)/columnWidth)*columnWidth;
        newX -= hPos - enWidth;
*/
        newX = enWidth + ((x-enWidth + columnWidth)/columnWidth) * columnWidth;
      }
      LOGV(x);
    }
    if (p[i] == ' ') {
      LOGV(x);
      newX = x + spaceWidth;
    }
    //if (newX > xPos) {
    if (xPos <= (x+newX)/2) {
      LOGV(i) LCV(xPos) LCV(x) LCV(newX);
      return i;
    }
    x = newX;
  }
  return i ? i - 1 : 0;
}

int AgView::xPosition(int charPos, AgString line) {
  //LOGSECTION("AgView::xPosition", Log::off);
  LOGSECTION_OFF("AgView::xPosition");
  int i;
  int enWidth = avgCharWidth();
  int x = enWidth;
  int columnWidth = enWidth*tab_spacing;
  int spaceWidth = charWidth[' '];
  unsigned char *p = (unsigned char *) line.pointer();

  LOGV(p);
  LOGV(charPos);
  LOGV(line.pointer());

  if (p == 0) {
    return x;
  }
  for (i = 0; p[i] && i < charPos; i++) {
    if (p[i] == '\t') {
      LOGSECTION("AgView::xPosition::tab");
      LOGV(x) LCV(columnWidth);
      int j = 0;
      if (nColumns() > 1 && tabArray) {
        while (tabArray[j] && tabArray[j] <= x) {
	  j++;
	}
        if (tabArray[j] > x) {
	  x = tabArray[j];
	}
      }
      else {
	x = enWidth + ((x-enWidth + columnWidth)/columnWidth) * columnWidth;
      }
/*
      else {
        int hPos = horizontalScrollBar.scrollBoxPosition();
        x += hPos - enWidth;
        x = ((x + columnWidth)/columnWidth)*columnWidth;
        x -= hPos - enWidth;
      }
*/
      LOGV(x);
      continue;
    }
    if (p[i] == ' ') {
      LOGV(x);
      x += spaceWidth;
      continue;
    }
    x += charWidth[p[i]];
  }
  LOGV(x);
  LOGV(i);
  return x;
}

void AgView::checkFocus(void) {
#ifdef AG_WINDOWSX
  if (!dataArea.hasFocus()) {
    dataArea.setFocus();
  }
#endif
}

Boolean AgView::lineDown(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  //LOGSECTION("AgView::lineDown", Log::off);
  LOGSECTION_OFF("AgView::lineDown");
  LOGV(cursorLine);
  checkFocus();
  hideCursor();
  moveScrollBox(event);
  int line = verticalScrollBar.scrollBoxPosition();
  updateCursor(line);
  showCursor();
  return true;
}

Boolean AgView::lineLeft(IScrollEvent &event) {
  if (event.scrollBarWindow() != &horizontalScrollBar) {
    return false;
  }
  //LOGSECTION("AgView::lineLeft", Log::off);
  LOGSECTION_OFF("AgView::lineLeft");
  if (cursorEnabled) {
    hideCursor();
  }
  checkFocus();
  moveScrollBox(event);
  reposition();
  if (cursorEnabled) {
    setCursorPos(pixelCursor).showCursor();
  }
  return true;
}

Boolean AgView::lineRight(IScrollEvent &event) {
  if (event.scrollBarWindow() != &horizontalScrollBar) {
    return false;
  }
  //LOGSECTION("AgView::lineRight", Log::off);
  LOGSECTION_OFF("AgView::lineRight");
  checkFocus();
  if (cursorEnabled) {
    hideCursor();
  }
  moveScrollBox(event);
  reposition();
  if (cursorEnabled) {
    showCursor();
  }
  return true;
}

Boolean AgView::lineUp(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  checkFocus();
  hideCursor();
  moveScrollBox(event);
  int line = verticalScrollBar.scrollBoxPosition();
  updateCursor(line);
  showCursor();
  return true;
}

Boolean AgView::pageDown(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  checkFocus();
  hideCursor();
  moveScrollBox(event);
  int line = verticalScrollBar.scrollBoxPosition();
  updateCursor(line);
  showCursor();
  return true;
}

Boolean AgView::pageLeft(IScrollEvent &event) {
  if (event.scrollBarWindow() != &horizontalScrollBar) {
    return false;
  }
  checkFocus();
  if (cursorEnabled) {
    hideCursor();
  }
  moveScrollBox(event);
  reposition();
  if (cursorEnabled) {
    showCursor();
  }
  return true;
}

Boolean AgView::pageRight(IScrollEvent &event) {
  if (event.scrollBarWindow() != &horizontalScrollBar) {
    return false;
  }
  checkFocus();
  if (cursorEnabled) {
    hideCursor();
  }
  moveScrollBox(event);
  reposition();
  if (cursorEnabled) {
    showCursor();
  }
  return true;
}

Boolean AgView::pageUp(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  checkFocus();
  hideCursor();
  moveScrollBox(event);
  int line = verticalScrollBar.scrollBoxPosition();
  updateCursor(line);
  showCursor();
  return true;
}

Boolean AgView::scrollBoxTrack(IScrollEvent &event) {
  checkFocus();
  if (cursorEnabled) {
    hideCursor();
  }
  moveScrollBox(event);
  reposition();
  if (event.scrollBarWindow() == &verticalScrollBar) {
    int line = verticalScrollBar.scrollBoxPosition();
    updateCursor(line);
    if (cursorEnabled) {
      showCursor();
    }
    return true;
  }
  if (cursorEnabled) {
    showCursor();
  }
  //if (event.scrollBarWindow() != &horizontalScrollBar) return false;
  return true;
}

AgView &AgView::setCursorLocation(cint loc) {
  //LOGSECTION("AgView::setCursorLocation", Log::off);
  LOGSECTION_OFF("AgView::setCursorLocation");
  //LOGSTACK;
  //hideCursor();
  if (loc.y < 0) {
    loc.y = 0;
  }
  if (loc.y >= nLines()) {
    loc.y = nLines() - 1;
  }
  LOGV(cursorLocation);
  LOGV(loc);
  cursorLocation = loc;
  unsigned oldCursor = cursorLine;
  cursorLine = loc.y;
  pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
  pixelCursor.y = lineHeight()*cursorLine;
  int repositionFlag = 0;
  int vPos = verticalScrollBar.scrollBoxPosition();
  int vLimit = vPos + verticalScrollBar.visibleCount() - 1;
  int hPos = horizontalScrollBar.scrollBoxPosition();
  int hLimit = hPos + horizontalScrollBar.visibleCount() - 4;
  if (loc.y < vPos) {
    verticalScrollBar.moveScrollBoxTo(loc.y);
    repositionFlag = 1;
  }
  if (loc.y > vLimit) {
    verticalScrollBar.moveScrollBoxTo(vPos + loc.y - vLimit);
    repositionFlag = 1;
  }
  if (pixelCursor.x < hPos) {
    int x;
    int cursorX = cursorLocation.x - 5;
    if (cursorX <= 0) {
      x = 0;
    }
    else x = xPosition(cursorX, getLine(cursorLine));
    horizontalScrollBar.moveScrollBoxTo(x);
    repositionFlag = 1;
  }
  if (pixelCursor.x > hLimit) {
    int x = xPosition(cursorLocation.x+5, getLine(cursorLine));
    horizontalScrollBar.moveScrollBoxTo(hPos + x - hLimit);
    repositionFlag = 1;
  }
  LOGV(vPos);
  LOGV(vLimit);
  LOGV(repositionFlag);
  if (repositionFlag) reposition();
  if (cursorLineHighlight && cursorLine != oldCursor) {
    repaintLine(oldCursor);
    repaintLine(cursorLine);
  }
  setCursorPos(pixelCursor);
  //showCursor();
  return *this;
}

AgView &AgView::scrollTo(cint loc) {
  //LOGSECTION("AgView::scrollTo", Log::on);
  LOGSECTION_ON("AgView::scrollTo");
  //LOGSTACK;
  //hideCursor();
  if (loc.y < 0) {
    loc.y = 0;
  }
  if (loc.y >=nLines()) {
    loc.y = nLines() - 1;
  }
  LOGV(cursorLocation);
  LOGV(loc);
  cint pixel;
  pixel.x = xPosition(loc.x, getLine(loc.y));
  pixel.y = lineHeight()*loc.y;
  int repositionFlag = 0;
  int vPos = verticalScrollBar.scrollBoxPosition();
  int vLimit = vPos + verticalScrollBar.visibleCount() - 1;
  int hPos = horizontalScrollBar.scrollBoxPosition();
  int hLimit = hPos + horizontalScrollBar.visibleCount() - 4;
  if (loc.y < vPos) {
    verticalScrollBar.moveScrollBoxTo(loc.y);
    repositionFlag = 1;
  }
  if (loc.y > vLimit) {
    verticalScrollBar.moveScrollBoxTo(vPos + loc.y - vLimit);
    repositionFlag = 1;
  }
  if (pixel.x < hPos) {
    int x;
    int cursorX = loc.x - 5;
    if (cursorX <= 0) {
      x = 0;
    }
    else {
      x = xPosition(cursorX, getLine(loc.y));
    }
    horizontalScrollBar.moveScrollBoxTo(x);
    repositionFlag = 1;
  }
  if (pixel.x > hLimit) {
    int x = xPosition(loc.x+5, getLine(loc.y));
    horizontalScrollBar.moveScrollBoxTo(hPos + x - hLimit);
    repositionFlag = 1;
  }
  LOGV(vPos);
  LOGV(vLimit);
  LOGV(repositionFlag);
  if (repositionFlag) {
    reposition();
  }
  return *this;
}



Boolean AgView::virtualKeyPress(IKeyboardEvent &event) {
  //LOGSECTION("AgView::Virtual key press", Log::off);
  LOGSECTION_OFF("AgView::Virtual key press");
  switch (event.virtualKey()) {
    case IKeyboardEvent::up: {
      if (!cursorLineHighlight && !cursorEnabled) {
        int line = verticalScrollBar.scrollBoxPosition() - 1;
        verticalScrollBar.moveScrollBoxTo(line);
        line = verticalScrollBar.scrollBoxPosition();
        updateCursor(line);
        return true;
      }
      int line = cursorLine;
      if (cursorLine <= 0) {
        messageBeep();
        return true;
      }
      if (cursorEnabled) {
	hideCursor();
      }
      cursorLine--;
      LOGV(cursorLine) LCV(verticalScrollBar.scrollBoxPosition());
      if (cursorLine < verticalScrollBar.scrollBoxPosition()) {
        if (cursorLineHighlight) {
	  repaintLine(line);
	}
        verticalScrollBar.moveScrollBoxTo(cursorLine);
        reposition();
      }
      else if (cursorLineHighlight) {
        repaintLine(line);
        repaintLine(cursorLine);
      }
      cursorLocation.y = cursorLine;
      pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
      pixelCursor.y = lineHeight()*cursorLine;
      if (cursorEnabled) {
	setCursorPos(pixelCursor).showCursor();
      }
      return true;
    }
    case IKeyboardEvent::down: {
      LOGSECTION("Cursor down one line");
      if (!cursorLineHighlight && !cursorEnabled) {
        int line = verticalScrollBar.scrollBoxPosition() + 1;
        verticalScrollBar.moveScrollBoxTo(line);
        line = verticalScrollBar.scrollBoxPosition();
        updateCursor(line);
        return true;
      }

      int line = cursorLine;
      if (cursorLine >= nLines() - 1) {
        messageBeep();
        return true;
      }
      if (cursorEnabled) {
	hideCursor();
      }
      cursorLine++;
      LOGV(cursorLine);
      int bottomLine =
        verticalScrollBar.scrollBoxPosition()
        + verticalScrollBar.visibleCount()
        - 1;
      LOGV(line);
      LOGV(cursorLine);
      LOGV(bottomLine);
      if (cursorLine > bottomLine) {
        if (cursorLineHighlight) {
	  repaintLine(line);
	}
        int newLine = cursorLine - verticalScrollBar.visibleCount() + 1;
        verticalScrollBar.moveScrollBoxTo(newLine);
        reposition();
      }
      else if (cursorLineHighlight) {
        repaintLine(line);
        repaintLine(cursorLine);
      }
      cursorLocation.y = cursorLine;
      pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
      pixelCursor.y = lineHeight()*cursorLine;
      if (cursorEnabled) {
	setCursorPos(pixelCursor).showCursor();
      }
      return true;
    }
    case IKeyboardEvent::pageUp: {
      LOGSECTION("AgView::pageUp");
      if (event.isCtrlDown()) {
        if (cursorLine == 0) {
          messageBeep();
          return true;
        }
        verticalScrollBar.moveScrollBoxTo(0);
        int oldCursor = cursorLine;
        cursorLine = 0;
        if (cursorLineHighlight) {
          repaintLine(oldCursor);
          repaintLine(cursorLine);
        }
        reposition();
        cursorLocation.y = pixelCursor.y = 0;
        if (cursorEnabled) {
	  setCursorPos(pixelCursor).showCursor();
	}
        return true;
      }
      if (!cursorLineHighlight && !cursorEnabled) {
        int line = verticalScrollBar.scrollBoxPosition();
        line -= verticalScrollBar.visibleCount();
        verticalScrollBar.moveScrollBoxTo(line);
        line = verticalScrollBar.scrollBoxPosition();
        updateCursor(line);
        return true;
      }
      int line = cursorLine;
      if (cursorLine <= 0) {
	return true;
      }
      if (cursorEnabled) {
	hideCursor();
      }
      cursorLine -= verticalScrollBar.visibleCount();
      if (cursorLine < 0) {
        cursorLine = 0;
      }
      if (cursorLine < verticalScrollBar.scrollBoxPosition()) {
        verticalScrollBar.moveScrollBoxTo(cursorLine);
        reposition();
      }
      else if (cursorLineHighlight) {
	repaintLine(line);
      }
      cursorLocation.y = cursorLine;
      pixelCursor.y = lineHeight()*cursorLine;
      pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
      if (cursorEnabled) {
	setCursorPos(pixelCursor).showCursor();
      }
      return true;
    }
    case IKeyboardEvent::pageDown: {
      LOGSECTION("AgView::pageDown");
      if (event.isCtrlDown()) {
        if (cursorLine >= nLines() - 1) {
          messageBeep();
          return true;
        }
        //cursorLine = verticalScrollBar.scrollableRange().upperBound();
        cursorLine = nLines() - 1;
        LOGV(cursorLine);
        verticalScrollBar.moveScrollBoxTo(cursorLine);
        updateCursor(cursorLine);
        cursorLocation.y = cursorLine;
        pixelCursor.y = cursorLine*lineHeight();
        return true;
      }
      if (!cursorLineHighlight && !cursorEnabled) {
        int line = verticalScrollBar.scrollBoxPosition();
        line += verticalScrollBar.visibleCount();
        verticalScrollBar.moveScrollBoxTo(line);
        line = verticalScrollBar.scrollBoxPosition();
        updateCursor(line);
      }
      int lastLine = nLines() - 1;
      if (cursorLine >= lastLine) {
	return true;
      }
      if (cursorEnabled) {
	hideCursor();
      }
      int line = cursorLine;
      cursorLine += verticalScrollBar.visibleCount();
      pixelCursor.y = lineHeight()*cursorLine;
      if (cursorLine > lastLine) {
	cursorLine = lastLine;
      }
      if (cursorLine > verticalScrollBar.scrollBoxPosition()) {
        int newLine = cursorLine - verticalScrollBar.visibleCount() + 1;
        verticalScrollBar.moveScrollBoxTo(newLine);
        reposition();
      }
      else if (cursorLineHighlight) {
	repaintLine(line);
      }
      cursorLocation.y = cursorLine;
      pixelCursor.y = lineHeight()*cursorLine;
      pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
      if (cursorEnabled) {
	setCursorPos(pixelCursor).showCursor();
      }
      return true;
    }
    case IKeyboardEvent::left: {
      if (cursorEnabled) {
        hideCursor();
        if (cursorLocation.x > 0) {
	  cursorLocation.x--;
	}
        pixelCursor.x = xPosition(cursorLocation.x, getLine(cursorLine));
        if (pixelCursor.x < horizontalScrollBar.scrollBoxPosition()) {
          int hPos = pixelCursor.x;
          if (cursorLocation.x == 0) {
	    hPos = 0;
	  }
          horizontalScrollBar.moveScrollBoxTo(hPos);
          reposition();
        }
        setCursorPos(pixelCursor);
        showCursor();
        return true;
      }
      pixelCursor.x = horizontalScrollBar.scrollBoxPosition();
      pixelCursor.x -= avgCharWidth();
      if (pixelCursor.x < 0) {
	pixelCursor.x = 0;
      }
      horizontalScrollBar.moveScrollBoxTo(pixelCursor.x);
      reposition();
      updateCursor();
      return true;
    }
    case IKeyboardEvent::right: {
      LOGS("cursor right");
      if (cursorEnabled) {
        hideCursor();
        LOGS("cursor hidden");
        AgString line = getLine(cursorLine);
        if (cursorLocation.x < line.size()) {
	  cursorLocation.x++;
	}
        pixelCursor.x = xPosition(cursorLocation.x, line);
        int hPos = horizontalScrollBar.scrollBoxPosition();
        int rightEdge = hPos + horizontalScrollBar.visibleCount() - 4;
        if (pixelCursor.x > rightEdge) {
          hPos += pixelCursor.x - rightEdge;
          horizontalScrollBar.moveScrollBoxTo(hPos);
          reposition();
        }
        setCursorPos(pixelCursor);
        LOGS("cursor moved");
        showCursor();
        LOGS("cursor restored");
        return true;
      }
      pixelCursor.x = horizontalScrollBar.scrollBoxPosition();
      pixelCursor.x += avgCharWidth();
      int netWidth = tableWidth - horizontalScrollBar.visibleCount();
      if (pixelCursor.x > netWidth) {
	pixelCursor.x = netWidth;
      }
      horizontalScrollBar.moveScrollBoxTo(pixelCursor.x);
      reposition();
      cursorLocation.x = pixelCursor.x/avgCharWidth();
      updateCursor();
      return true;
    }
    case IKeyboardEvent::home: {
      if (event.isCtrlDown()) {
        if (cursorLine == 0) {
          messageBeep();
          return true;
        }
        verticalScrollBar.moveScrollBoxTo(0);
        int oldCursor = cursorLine;
        cursorLine = 0;
        if (cursorLineHighlight) {
          repaintLine(oldCursor);
          repaintLine(cursorLine);
        }
        reposition();
        cursorLocation.y = pixelCursor.y = 0;
        if (cursorEnabled) {
	  setCursorPos(pixelCursor).showCursor();
	}
        return true;
      }
      pixelCursor.x = avgCharWidth();
      cursorLocation.x = 0;
      horizontalScrollBar.moveScrollBoxTo(0);
      reposition();
      updateCursor();
      return true;
    }
    case IKeyboardEvent::end: {
      if (event.isCtrlDown()) {
        if (cursorLine >= nLines() - 1) {
          messageBeep();
          return true;
        }
        //cursorLine = verticalScrollBar.scrollableRange().upperBound();
        cursorLine = nLines() - 1;
        LOGV(cursorLine);
        verticalScrollBar.moveScrollBoxTo(cursorLine);
        updateCursor(cursorLine);
        cursorLocation.y = cursorLine;
        pixelCursor.y = cursorLine*lineHeight();
        return true;
      }
      if (cursorEnabled) {
        LOGSECTION("AgView::end");
        hideCursor();
        AgString line = getLine(cursorLocation.y);
        cursorLocation.x = line.size();
        LOGV(cursorLocation);
        LOGV(line.size());
        pixelCursor.x = xPosition(cursorLocation.x, line);
        LOGV(charPosition(pixelCursor.x, line));
        int hPos = horizontalScrollBar.scrollBoxPosition();
        int rightEdge = hPos + horizontalScrollBar.visibleCount() - 4;
        if (pixelCursor.x > rightEdge) {
          hPos += pixelCursor.x - rightEdge;
          horizontalScrollBar.moveScrollBoxTo(hPos);
          reposition();
        }
        setCursorPos(pixelCursor);
        LOGS("cursor moved");
        showCursor();
        return true;
      }
      pixelCursor.x = tableWidth - horizontalScrollBar.visibleCount();
      horizontalScrollBar.moveScrollBoxTo(pixelCursor.x);
      reposition();
      updateCursor();
      return true;
    }
  }
  return false;
}

Boolean AgView::mouseClicked(IMouseClickEvent &event) {
  //LOGSECTION("AgView::mouseClickEvent", Log::off);
  LOGSECTION_OFF("AgView::mouseClickEvent");
  LOGV(event.mouseButton());
  LOGV(event.mouseAction());

  if (event.controlWindow() == &columnHeadTitle
      || event.controlWindow() == &verticalScrollBar
      || event.controlWindow() == &horizontalScrollBar) {
    if (event.mouseAction() == IMouseClickEvent::down) {
      dataArea.setFocus();
    }
    return false;
  }

  if (ControlPanel::helpCursorSet) {
    if (event.mouseAction() == IMouseClickEvent::down) {
      ControlPanel::helpCursorSet = 0;
      ControlPanel::resetCursor();
    }
  }

  int line = event.mousePosition().y()/lineHeight();
  LOGV(line) LCV(verticalScrollBar.scrollBoxPosition());
  line += verticalScrollBar.scrollBoxPosition();
  LOGV(line);
  //if (line >= nLines()) line = nLines() - 1;

  if (event.mouseButton() == IMouseClickEvent::button2) {
    if (event.mouseAction() == IMouseClickEvent::down) {
      rightButtonDown = true;
    }
    else if (event.mouseAction() == IMouseClickEvent::up) {
      rightButtonDown = false;
    }
    return false;
/*
    else if (event.mouseAction() == IMouseClickEvent::click) {
      if (line == cursorLine) {
        return true;
      }
      if (cursorLineHighlight) {
        int oldLine = cursorLine;
        cursorLine = line;
        LOGV(oldLine) LCV(cursorLine);
        repaintLine(oldLine);
        repaintLine(cursorLine);
      }
      return true;
    }
*/
  }
  if (event.mouseButton() != IMouseClickEvent::button1) {
    return false;
  }
  if (line >= nLines() && !mouseDown) {
    if (!dataArea.hasFocus()) {
      dataArea.setFocus();
    }
    return false;
  }
  switch (event.mouseAction()) {
    case IMouseClickEvent::down: {
      if (event.windowUnderPointer() != dataArea.handle()) {
	return false;
      }
      if (!cursorLineHighlight) {
	return false;
      }
      dataArea.capturePointer(true);
      mouseDownCursorLine = cursorLine;
      //int line = event.mousePosition().y()/lineHeight();
      int offset = horizontalScrollBar.scrollBoxPosition();
      int whereX = event.mousePosition().x() + offset;
      LOGV(cursorLine) LCV(offset) LCV(whereX);
      AgString text = getLine(line);
      cint loc(charPosition(whereX, text), line);
      LOGV(loc);
      cursorLocation = loc;
      if (line != cursorLine) {
        cursorLine = line;
        if (cursorLineHighlight) {
          LOGV(mouseDownCursorLine) LCV(cursorLine);
          repaintLine(mouseDownCursorLine);
          repaintLine(cursorLine);
        }
        onSelect();
      }
      mouseDown = true;
      return false;
    }
    case IMouseClickEvent::up: {
      mouseDown = false;
      dataArea.capturePointer(false);
      return false;
    }
    case IMouseClickEvent::click: {
      if (event.windowUnderPointer() != dataArea.handle()) {
	return false;
      }
      dataArea.setFocus();
      //if (line == mouseDownCursorLine) {
      //  onSelect();
      //}
      return false;
    }
    case IMouseClickEvent::doubleClick: {
      dataArea.setFocus();
      //onSelect();
      onEnter();
      return false;
    }
  }
  return false;
}

Boolean AgView::mouseMoved(IMouseEvent &event) {
  if (!mouseDown) {
    return false;
  }
  if (!cursorLineHighlight) {
    return false;
  }
  if (mouseTimer.isStarted()) {
    mouseTimer.stop();
  }
  int mouseY = event.mousePosition().y();
  if (mouseY < 0) {
    mouseY -= lineHeight();
  }
  int mouseLine = mouseY/lineHeight();
  int topLine = verticalScrollBar.scrollBoxPosition();
  //LOGSECTION("Mouse moved", Log::off);
  LOGSECTION_OFF("Mouse moved");
  LOGV(topLine) LCV(mouseLine);
  dragMouse(topLine, mouseLine);
  topLine = verticalScrollBar.scrollBoxPosition();
  int belowLine = topLine + verticalScrollBar.visibleCount();
  if (mouseLine >= topLine && mouseLine < belowLine) {
    return true;
  }
  IReference<ITimerFn> timerFn(new MouseDragTimer(this, topLine, mouseLine));
  mouseTimer.start(timerFn, 50);
  LOGS("timer started");
  return false;
}

void AgView::dragMouse(int topLine, int mouseLine) {
  //LOGSECTION("Agview::dragMouse", Log::off);
  LOGSECTION_OFF("Agview::dragMouse");
  int bottomLine = topLine + verticalScrollBar.visibleCount() - 1;
  int line = topLine + mouseLine;
  if (line < 0) {
    line = 0;
  }
  if (line >= nLines()) {
    line = nLines() - 1;
  }
  if (line == cursorLine) {
    return;
  }
  int oldLine = cursorLine;
  cursorLocation.y = cursorLine = line;
  pixelCursor.y = lineHeight()*cursorLine;
  if (cursorLine < topLine) {
    if (cursorLineHighlight) repaintLine(oldLine);
    verticalScrollBar.moveScrollBoxTo(cursorLine);
    reposition();
    return;
  }
  if (cursorLine > bottomLine) {
    if (cursorLineHighlight) {
      repaintLine(oldLine);
    }
    verticalScrollBar.moveScrollBoxTo(topLine + cursorLine - bottomLine);
    reposition();
    return;
  }

  if (cursorLineHighlight) {
    repaintLine(oldLine);
    repaintLine(cursorLine);
  }
  return;
}

void AgView::mouseDragTimerInterrupt(int &topLine, int mouseLine) {
  //LOGSECTION("AgView::mouseDragTimer", Log::off);
  LOGSECTION_OFF("AgView::mouseDragTimer");
  LOGV(topLine) LCV(mouseLine) LCV(cursorLine);
  if (!mouseDown || topLine + mouseLine == cursorLine) {
    mouseTimer.stop();
    LOGV(mouseDown);
    LOGV(topLine) LCV(verticalScrollBar.scrollBoxPosition());
    return;
  }
  if (topLine + mouseLine != cursorLine) {
    dragMouse(topLine, mouseLine);
  }
  topLine = verticalScrollBar.scrollBoxPosition();
}

AgView::MouseDragTimer::MouseDragTimer(AgView * window_, int topLine_,
				       int mouseLine_)
  : window(window_)
  , topLine(topLine_)
  , mouseLine(mouseLine_)
{}

void AgView::MouseDragTimer::timerExpired(unsigned long) {
  //LOGSECTION("AgView::MouseDragTimer::timerExpired", Log::off);
  LOGSECTION_OFF("AgView::MouseDragTimer::timerExpired");
  window->mouseDragTimerInterrupt(topLine, mouseLine);
}