view anagram/vaclgui/agview.cpp @ 21:1c9dac05d040

Add lint-style FALLTHROUGH annotations to fallthrough cases. (in the parse engine and thus the output code) Document this, because the old output causes warnings with gcc10.
author David A. Holland
date Mon, 13 Jun 2022 00:04:38 -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);
}