diff anagram/vaclgui/agview.cpp @ 0:13d2b8934445

Import AnaGram (near-)release tree into Mercurial.
author David A. Holland
date Sat, 22 Dec 2007 17:52:45 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/anagram/vaclgui/agview.cpp	Sat Dec 22 17:52:45 2007 -0500
@@ -0,0 +1,1952 @@
+/*
+ * 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);
+}