diff anagram/vaclgui/dview.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/dview.cpp	Sat Dec 22 17:52:45 2007 -0500
@@ -0,0 +1,1980 @@
+/*
+ * AnaGram, A System for Syntax Directed Programming
+ * Copyright 1997-2002 Parsifal Software. All Rights Reserved.
+ * See the file COPYING for license and usage terms.
+ *
+ * dview.cpp
+ */
+
+#include <icoordsy.hpp>
+#include <windows.h>
+#include "port.h"
+
+#include "agcstack.h"
+#include "arrays.h"
+#include "ctrlpanel.hpp"
+#include "dview.hpp"
+#include "dvplug.hpp"
+#include "helpview.hpp"
+#include "minmax.h"
+#include "myalloc.h"
+#include "vaclgui.hpp"
+
+//#define INCLUDE_LOGGING
+#include "log.h"
+
+
+const int AgDataView::defaultWindowHeight = 12;
+
+void logFont(IFont activeFont, char *p) {
+  LOGV(activeFont.name()) LV(activeFont.pointSize());
+  LOGV(activeFont.isBold()) LCV(activeFont.isItalic());
+  LOGV(activeFont.textWidth(p)) LCV(p);
+}
+
+void logFont(IFont activeFont) {
+  LOGV(activeFont.name()) LV(activeFont.pointSize());
+  LOGV(activeFont.isBold()) LCV(activeFont.isItalic());
+}
+
+AgDataView::AgDataView(IWindow *ownerWindow_, WindowData *windowData_)
+  : ICanvas(nextChildId(), ownerWindow_, ownerWindow_)
+  , ownerWindow(ownerWindow_)
+  , windowData(windowData_)
+  , horizontalScrollBar(nextChildId(), this, this, IRectangle(),
+                        IScrollBar::horizontal | IWindow::visible)
+  , verticalScrollBar(nextChildId(), this, this, IRectangle(),
+                      IScrollBar::vertical | IWindow::visible)
+  , dataArea(this)
+  , vsbShowing(false)
+  , hsbShowing(false)
+  , columnHeadTitle(nextChildId(), this)
+  , columnHeadsPresent(0)
+  , cursorLine(0)
+  , mouseDown(0)
+  , tabArray(0)
+  , popUpMenu(this, nextChildId())
+  , popUpInitialized(0)
+  , tableWidth(0)
+  , color(&ColorSpec::data)
+  , cursorColor(&ColorSpec::inactiveCursor)
+  , prevHorizontal(0)
+  , prevVertical(0)
+  //, rightButtonDown(0)
+  , rightButtonState(buttonIdle)
+  , layoutActive(0)
+  , dataColorChange(this, onColorChange)
+  , fontChange(this, onFontChange)
+  , enterAction(actionObject(messageBeep))
+  , verticalLineCount(0)
+  , layoutComplete(0)
+  , helpTopic(0)
+{
+  windowId = id();
+  LOGSECTION("AgDataView::AgDataView");
+  LOGV(windowId);
+  LOGV((int) this);
+  setFont(FontSpec::dataTable);
+  dataArea.setFont(FontSpec::dataTable);
+  LOGV(dataArea.font().name());
+  columnHeadTitle.setFont(FontSpec::columnHead);
+  dataColorChange.attach(&ColorSpec::data);
+  dataColorChange.attach(&ColorSpec::inactiveCursor);
+  dataColorChange.attach(&ColorSpec::activeCursor);
+  fontChange.attach(&FontSpec::dataTable);
+  fontChange.attach(&FontSpec::columnHead);
+  fontChange.attach(&FontSpec::markedToken);
+
+  horizontalScrollBar.setScrollableRange(IRange(0,100));
+  horizontalScrollBar.moveScrollBoxTo(0);
+  verticalScrollBar.setScrollableRange(IRange(0,100));
+  verticalScrollBar.moveScrollBoxTo(0);
+  cursorLine = 0;
+
+  IWindow *f;
+  for (f = ownerWindow; !f->isFrameWindow(); f=f->parent());
+  frameWindow = (AgFrame *) f;
+  frameWindow->helpCursorSupported = 1;
+  LOGV(frameWindow->windowTitle.text());
+
+  ok_ptr(windowData);
+  AgString title = windowData->columnHeadTitle();
+  LOGV(title.pointer());
+  if (title.exists()) {
+    columnHeadTitle.setTitles(windowData->nColumns(), title);
+    columnHeadTitle.setMargin(avgCharWidth());
+    columnHeadTitle.enableFillBackground();
+    columnHeadTitle.show();
+    columnHeadsPresent = 1;
+  }
+
+
+  int nCols = windowData->nColumns();
+  tabArray = new int[nCols + 1];
+  memset(tabArray, 0, (nCols+1)*sizeof(int));
+  columnHeadTitle.setTabs(tabArray);
+  LOGV(tabArray[0]);
+  tableHeight  = lineHeight()*windowData->nLines();
+  LOGV(windowData->nLines());
+  LOGV(tableHeight);
+
+  LOGV(auxMenu.exists());
+
+  ICommandHandler ::handleEventsFor(this);
+  IKeyboardHandler::handleEventsFor(this);
+  IMouseHandler   ::handleEventsFor(&dataArea);
+  IMouseHandler   ::handleEventsFor(&columnHeadTitle);
+  IMouseHandler   ::handleEventsFor(&verticalScrollBar);
+  IMouseHandler   ::handleEventsFor(&horizontalScrollBar);
+  IPaintHandler   ::handleEventsFor(&dataArea);
+  IResizeHandler  ::handleEventsFor(this);
+  IScrollHandler  ::handleEventsFor(this);
+  AgHelpHandler   ::handleEventsFor(frameWindow);
+  AgFocusHandler  ::handleEventsFor(&dataArea);
+
+  ((AgDataViewPlug *)windowData)->connect(this);
+
+  auxMenu = windowData->auxMenu();
+  if (auxMenu.exists()) {
+    IMenuHandler::handleEventsFor(&dataArea);
+  }
+  LOGV(auxMenu.exists());
+  LOGS("menu handler attached");
+
+  initPopUp();
+
+  show();
+  windowData->synchCursor(cursorLine);
+}
+
+void AgDataView::disconnect() {
+  LOGSECTION("AgDataView::disconnect");
+  LOGV((int) frameWindow);
+}
+
+
+AgDataView &AgDataView::setColumnTitles(AgString title) {
+  LOGSECTION("AgDataView::setColumnTitles");
+  LOGV((int) frameWindow);
+  if (title.exists()) {
+    LOGV(title.pointer());
+    columnHeadTitle.setTitles(windowData->nColumns(), title);
+    columnHeadTitle.setMargin(avgCharWidth());
+    columnHeadTitle.enableFillBackground();
+    columnHeadsPresent = 1;
+  }
+  return *this;
+}
+
+Boolean AgDataView::gotFocus(IEvent &) {
+  LOGSECTION("AgDataView::gotFocus");
+  LOGV(windowId);
+  LOGV((int) this);
+  focusWindowId = windowId;
+  columnHeadTitle.color = &ColorSpec::activeTitle;
+  columnHeadTitle.refresh();
+  cursorColor = &ColorSpec::activeCursor;
+  if (windowData == 0) {
+    return true;
+  }
+  repaintLine(cursorLine);
+  windowData->synchCursor(cursorLine);
+  return false;
+}
+
+Boolean AgDataView::lostFocus(IEvent &) {
+  LOGSECTION("AgDataView::lostFocus");
+  LOGV(windowId);
+  LOGV((int) this) LCV((int) windowData);
+  columnHeadTitle.color = &ColorSpec::inactiveTitle;
+  columnHeadTitle.refresh();
+  cursorColor = &ColorSpec::inactiveCursor;
+  if (windowData == 0) {
+    return true;
+  }
+  ok_ptr(windowData);
+  repaintLine(cursorLine);
+  return false;
+}
+
+
+AgDataView &AgDataView::init(WindowData *windowData_) {
+  LOGSECTION("AgDataView::init");
+  LOGV(windowId);
+  LOGV((int) this);
+  horizontalScrollBar.setScrollableRange(IRange(0,100));
+  horizontalScrollBar.moveScrollBoxTo(0);
+  verticalScrollBar.setScrollableRange(IRange(0,100));
+  verticalScrollBar.moveScrollBoxTo(0);
+  cursorLine = 0;
+
+  ok_ptr(windowData_);
+  windowData = windowData_;
+  LOGV((int) frameWindow);
+  LOGV((int) windowData);
+  LOGV(windowId);
+  AgString title = windowData->columnHeadTitle();
+  if (title.exists()) {
+    LOGV(title.pointer());
+    columnHeadTitle.setTitles(windowData->nColumns(), title);
+    columnHeadTitle.setMargin(avgCharWidth());
+    columnHeadTitle.enableFillBackground();
+    columnHeadTitle.show();
+    columnHeadsPresent = 1;
+  }
+
+
+  if (tabArray == NULL) {
+    int nCols = windowData->nColumns();
+    tabArray = new int[nCols + 1];
+    memset(tabArray, 0, (nCols+1)*sizeof(int));
+  }
+  columnHeadTitle.setTabs(tabArray);
+  LOGV(tabArray[0]);
+  tableHeight  = lineHeight()*windowData->nLines();
+  LOGV(windowData->nLines());
+  LOGV(tableHeight);
+  setLayoutDistorted(IWindow::layoutChanged,0);
+
+  ((AgDataViewPlug *)windowData)->connect(this);
+  auxMenu = windowData->auxMenu();
+  if (auxMenu.exists()) {
+    IMenuHandler::handleEventsFor(&dataArea);
+    LOGS("menu handler attached");
+    initPopUp();
+  }
+
+  windowData->synchCursor(cursorLine);
+  return *this;
+}
+
+
+AgDataView &AgDataView::reset() {
+  LOGSECTION("AgDataView::reset");
+  LOGV(windowId);
+  LOGV((int) this);
+  ok_ptr(windowData);
+  int nLines  = windowData->nLines();
+  LOGV(nLines);
+  int newTableHeight  = lineHeight()*nLines;
+  LOGV(windowData->nLines());
+  LOGV(tableHeight);
+  if (cursorLine >= nLines) {
+    cursorLine = nLines-1;
+  }
+  windowData->synchCursor(cursorLine);
+  //if (tableHeight != newTableHeight) {
+    tableHeight = newTableHeight;
+    doLayout();
+    refresh();
+  //}
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  LOGV(verticalScrollBar.scrollBoxPosition());
+  return *this;
+}
+
+AgDataView::AgDataView(IWindow *ownerWindow_)
+  : ICanvas(nextChildId(), ownerWindow_, ownerWindow_)
+  , ownerWindow(ownerWindow_)
+  , windowData(NULL)
+  , horizontalScrollBar(nextChildId(), this, this, IRectangle(),
+                        IScrollBar::horizontal | IWindow::visible)
+  , verticalScrollBar(nextChildId(), this, this, IRectangle(),
+                      IScrollBar::vertical | IWindow::visible)
+  , dataArea(this)
+  , vsbShowing(false)
+  , hsbShowing(false)
+  , columnHeadTitle(nextChildId(), this)
+  , columnHeadsPresent(0)
+  , cursorLine(0)
+  , mouseDown(0)
+  , tabArray(0)
+  , popUpMenu(this, nextChildId())
+  , popUpInitialized(0)
+  , tableWidth(0)
+  , color(&ColorSpec::data)
+  , cursorColor(&ColorSpec::inactiveCursor)
+  , prevHorizontal(0)
+  , prevVertical(0)
+  //, rightButtonDown(0)
+  , rightButtonState(buttonIdle)
+  , layoutActive(0)
+  , dataColorChange(this, onColorChange)
+  , fontChange(this, onFontChange)
+  , enterAction(actionObject(messageBeep))
+  , verticalLineCount(0)
+  , layoutComplete(0)
+  , helpTopic(0)
+{
+  windowId = id();
+  LOGSECTION("AgDataView::AgDataView");
+  LOGV(windowId);
+  LOGV((int) this);
+  setFont(FontSpec::dataTable);
+  dataArea.setFont(FontSpec::dataTable);
+  LOGV(dataArea.font().name());
+  columnHeadTitle.setFont(FontSpec::columnHead);
+
+  dataColorChange.attach(&ColorSpec::data);
+  dataColorChange.attach(&ColorSpec::inactiveCursor);
+  dataColorChange.attach(&ColorSpec::activeCursor);
+  fontChange.attach(&FontSpec::dataTable);
+  fontChange.attach(&FontSpec::columnHead);
+  fontChange.attach(&FontSpec::markedToken);
+  IWindow *f;
+  for (f = ownerWindow; !f->isFrameWindow(); f=f->parent());
+  frameWindow = (AgFrame *) f;
+  frameWindow->helpCursorSupported = 1;
+  LOGV(frameWindow->windowTitle.text());
+  LOGV((int) frameWindow);
+  LOGV((int) this);
+
+  LOGV(id());
+
+  ICommandHandler ::handleEventsFor(this);
+  IKeyboardHandler::handleEventsFor(this);
+  if (auxMenu.exists()) {
+    IMenuHandler::handleEventsFor(&dataArea);
+  }
+  LOGV(auxMenu.exists());
+  LOGS("menu handler attached");
+  IMouseHandler   ::handleEventsFor(&dataArea);
+  IMouseHandler   ::handleEventsFor(&columnHeadTitle);
+  IMouseHandler   ::handleEventsFor(&verticalScrollBar);
+  IMouseHandler   ::handleEventsFor(&horizontalScrollBar);
+  IPaintHandler   ::handleEventsFor(&dataArea);
+  IResizeHandler  ::handleEventsFor(this);
+  IScrollHandler  ::handleEventsFor(this);
+  AgHelpHandler   ::handleEventsFor(frameWindow);
+  AgFocusHandler  ::handleEventsFor(&dataArea);
+  show();
+}
+
+AgDataView::~AgDataView() {
+  LOGSECTION("AgDataView::~AgDataView");
+  LOGV(windowId) LCV((int) this) LCV((int) windowData);
+  ok_ptr(windowData);
+  delete [] tabArray;
+  //if (windowData == 0) return;
+  ICommandHandler ::stopHandlingEventsFor(this);
+  IKeyboardHandler::stopHandlingEventsFor(this);
+  if (auxMenu.exists()) {
+    IMenuHandler::stopHandlingEventsFor(&dataArea);
+  }
+  IMouseHandler   ::stopHandlingEventsFor(&dataArea);
+  IMouseHandler   ::stopHandlingEventsFor(&columnHeadTitle);
+  IMouseHandler   ::stopHandlingEventsFor(&verticalScrollBar);
+  IMouseHandler   ::stopHandlingEventsFor(&horizontalScrollBar);
+  IPaintHandler   ::stopHandlingEventsFor(&dataArea);
+  IResizeHandler  ::stopHandlingEventsFor(this);
+  IScrollHandler  ::stopHandlingEventsFor(this);
+  AgHelpHandler   ::stopHandlingEventsFor(frameWindow);
+  AgFocusHandler  ::stopHandlingEventsFor(&dataArea);
+  ok_ptr(windowData);
+  delete windowData;
+}
+
+void AgDataView::onFontChange() {
+  LOGSECTION("AgDataView::onFontChange");
+  disableUpdate();
+  LOGS("dataTableFont");
+  logFont(FontSpec::dataTable);
+  setFont(FontSpec::dataTable);
+  dataArea.setFont(FontSpec::dataTable);
+  columnHeadTitle.setFont(FontSpec::columnHead);
+  LOGS("font()");
+  logFont(font());
+  logFont(dataArea.font());
+  tableWidth = 0;
+  doLayout();
+  enableUpdate();
+  show();
+}
+
+int AgDataView::findMaxWidth() {
+  LOGSECTION("AgDataView::findMaxWidth");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+
+  int maxWidth = 0;
+  ok_ptr(windowData);
+  int nLines = windowData->nLines();
+  int nCols = windowData->nColumns();
+  char *columnTitles = columnHeadTitle.title.pointer();
+  int i;
+
+  LOGV(nLines) LCV(nCols);
+  if (tabArray == NULL) {
+    tabArray = new int[nCols + 1];
+  }
+  memset(tabArray, 0, (nCols+1)*sizeof(int));
+  IFont windowFont = font();
+  LOGV(windowFont.name());
+  LOGV(windowFont.pointSize());
+
+  int lastColumnTitleWidth = 0;
+  //int *columnWidth = new int[nCols];
+  //int *columnWidth = local_array(nCols, int);
+  LocalArray<int> columnWidth(nCols);
+  memset(columnWidth, 0, nCols*sizeof(int));
+  LOGV(columnTitles);
+  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 = columnHeadTitle.font().textWidth(p);
+      columnWidth[i] = width;
+      LOGV(width) LCV(p);
+      lastColumnTitleWidth = 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;
+    }
+  }
+  LOGV(tabArray[0]);
+  LOGV(columnWidth[0]);
+  //int *width = new int[nCols];
+  //int *width = local_array(nCols, int);
+  LocalArray<int> width(nCols);
+  int partialWidth = 0;
+
+  while (nLines--) {
+    //LOGSECTION("AgDataView::findMaxWidth line detail", Log::off);
+    LOGSECTION_OFF("AgDataView::findMaxWidth line detail");
+    AgString line = windowData->getLine(nLines);
+    LOGV(line.pointer());
+    FindTabs tabs(line.pointer());
+    char *p = tabs.getField();
+    LOGV(nLines);
+    LOGV(p);
+    i = 0;
+    while(i < nCols && p) {
+      width[i] = windowFont.textWidth(p);
+      LOGV(i) LCV(width[i]) LCV(p);
+      if (width[i] > columnWidth[i]) {
+	columnWidth[i] = width[i];
+      }
+      if (++i >= nCols) {
+	break;
+      }
+      p = tabs.getField();
+    }
+    LOGV(width[0]) LCV(line.pointer());
+    if (i < nCols) {
+      int w = 0;
+      while (i--) {
+	w += width[i];
+      }
+      if (w > partialWidth) {
+	partialWidth = w;
+      }
+      //continue;
+    }
+  }
+
+  //delete [] width;
+  for (i = 0; i < nCols; i++) {
+    columnWidth[i] += 2*avgCharWidth();
+    if (columnWidth[i] > tabArray[i]) {
+      tabArray[i] = columnWidth[i];
+    }
+    maxWidth += tabArray[i];
+    LOGV(tabArray[i]) LCV(maxWidth);
+  }
+  LOGV(tabArray[0]);
+  maxWidth += 2*avgCharWidth();
+  if (maxWidth < partialWidth) {
+    maxWidth = partialWidth;
+  }
+  //delete [] columnWidth;
+  columnHeadWidth = maxWidth - tabArray[nCols-1]
+    + lastColumnTitleWidth+2*avgCharWidth();
+  LOGV(columnHeadWidth);
+  return maxWidth;
+}
+
+AgDataView &AgDataView::adjustMaxWidth(AgString text) {
+  int nCols    = windowData->nColumns();
+  int maxWidth = 0;
+  int margin   = 2*avgCharWidth();
+  char *line = text.pointer();
+
+  LOGSECTION("AgDataView::adjustMaxWidth");
+  LOGV((int) frameWindow);
+  LOGV(line);
+  if (tableWidth == 0) {
+    tableWidth = findMaxWidth();
+  }
+  FindTabs tabs(line);
+  char *p = tabs.getField();
+
+  LOGV(p);
+
+  if (tabArray == NULL) {
+    tabArray = new int[nCols + 1];
+    memset(tabArray, 0, (nCols+1)*sizeof(int));
+  }
+  int i = 0;
+  while(i < nCols && p) {
+    int width = font().textWidth(p) + margin;
+    if (width > tabArray[i]) {
+      tabArray[i] = width;
+    }
+    LOGV(p) LCV(width);
+    if (++i >= nCols) {
+      break;
+    }
+    p = tabs.getField();
+  }
+  for (i = 0; i < nCols; i++) {
+    maxWidth += tabArray[i];
+    LOGV(tabArray[i]) LCV(maxWidth);
+  }
+  if (tableWidth < maxWidth) {
+    tableWidth = maxWidth;
+  }
+  LOGV(maxWidth);
+  LOGV(tabArray[0]);
+  return *this;
+}
+
+AgDataView &AgDataView::layout() {
+  LOGSECTION("AgDataView::layout");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+  if (layoutActive) {
+    return *this;
+  }
+  layoutActive++;
+  doLayout();
+  ICanvas::setLayoutDistorted(0, IWindow::layoutChanged);
+  ICanvas::layout();
+  layoutActive--;
+  return *this;
+}
+
+AgDataView &AgDataView::setLayoutDistorted(unsigned long i, unsigned long j) {
+  LOGSECTION("AgDataView::setLayoutDistorted");
+  LOGV((int) frameWindow);
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV(i) LCV(j);
+  LOGSTACK;
+
+  ICanvas::setLayoutDistorted(i, j);
+  return *this;
+}
+
+Boolean AgDataView::windowResize(IResizeEvent &event){
+  LOGSECTION("AgDataView::windowResize");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+
+  //if (event.controlWindow() != this) {
+  //  return false;
+  //}
+
+  LOGV(id());
+
+  //setLayoutDistorted(IWindow::layoutChanged,0);
+  doLayout();
+  return false;
+}
+
+ISize AgDataView::calcMinimumSize() const {
+  int minWidth = 10*font().avgCharWidth();
+  int minHeight = 3*lineHeight();
+  if (columnHeadsPresent) {
+    minHeight += 2 + columnHeadTitle.minimumSize().height();
+  }
+  return ISize(minWidth, minHeight);
+}
+
+ISize AgDataView::suggestSize() {
+  LOGSECTION("AgDataView::suggestSize");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+
+  if (tableWidth == 0) {
+    tableWidth = findMaxWidth();
+/*
+    int minWidth = 20*font().avgCharWidth();
+    int minHeight = 5*lineHeight();
+    if (columnHeadsPresent) {
+      minHeight += 2 + columnHeadTitle.minimumSize().height();
+    }
+    setMinimumSize(ISize(minWidth, minHeight));
+*/
+  }
+  int width = tableWidth;
+  int height = tableHeight;
+  ok_ptr(windowData);
+  LOGV(windowData->nLines());
+  LOGV(tableHeight);
+  LOGV(columnHeadsPresent);
+  LOGV(columnHeadTitle.minimumSize().height());
+  if (windowData->nLines() > defaultWindowHeight) {
+    width += verticalScrollBar.minimumSize().width() + 1;
+    height = lineHeight() * defaultWindowHeight;
+  }
+  else if (windowData->nLines() < 5) {
+    height = lineHeight() * 5;
+  }
+  if (columnHeadsPresent) {
+    height += 2 + columnHeadTitle.minimumSize().height();
+  }
+  LOGV(height);
+
+  LOGV(width) LCV(height);
+
+  return ISize(width, height);
+}
+
+ColorSpec *AgDataView::getLineColor(int k) {
+  if (k == cursorLine) {
+    return cursorColor;
+  }
+  return color;
+}
+
+char *findDelimiter(char *p, char c) {
+
+  // XXX: are we guaranteed that '\\' never occurs immediately before
+  // the end of a string, or do we just zoom off into the beyond when
+  // that happens?
+
+  while (*p && *p != c) {
+    if (*p == '\'') {
+      p++;
+      while (*p != '\'') {
+	if (*p++ == '\\') {
+	  p++;
+	}
+      }
+    }
+    else if (*p == '"') {
+      p++;
+      while (*p != '"') {
+	if (*p++ == '\\') {
+	  p++;
+	}
+      }
+    }
+    p++;
+  }
+  return p;
+}
+
+Boolean AgDataView::paintWindow(IPaintEvent &event) {
+  if (event.controlWindow() != &dataArea) {
+    return false;
+  }
+  if (windowData == 0) {
+    return false;
+  }
+  int nLines = windowData->nLines() - 1;
+  int height = lineHeight();
+  int leadingBias = 0;
+  IFont markFont(FontSpec::markedToken);
+
+  LOGSECTION("AgDataView::paintWindow");
+  LOGV(tabArray[0]);
+  LOGV(windowId);
+  LOGV((int) this);
+  IPresSpaceHandle presSpaceHandle = event.presSpaceHandle();
+
+  font().beginUsingFont(presSpaceHandle);
+  LOGV(font().name()) LCV(font().pointSize());
+  leadingBias = font().maxAscender() - markFont.maxAscender();
+  SetBkMode(presSpaceHandle, TRANSPARENT);
+  LOGV((int) frameWindow);
+
+  LOGV(nLines);
+  IRectangle invalidRect(
+    ICoordinateSystem::isConversionNeeded()
+    ? ICoordinateSystem::convertToApplication(event.rect(),dataArea.size())
+    : event.rect()
+  );
+
+  LOGV(invalidRect.asString());
+  LOGV(dataArea.size().asString());
+
+  int baseLine  = verticalScrollBar.scrollBoxPosition();
+  int firstLine = baseLine + invalidRect.minY()/height;
+  int lastLine  = baseLine + (invalidRect.maxY()+height - 1)/height - 1;
+
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  LOGV(verticalScrollBar.scrollBoxPosition());
+  LOGV(baseLine);
+  LOGV(firstLine);
+  LOGV(lastLine);
+
+  int yBias =
+    ICoordinateSystem::isConversionNeeded()
+    ? height - maxDescender()
+    : 0;
+
+  int xMargin = avgCharWidth();
+
+
+  LOGV(yBias);
+
+  int whereX = -horizontalScrollBar.scrollBoxPosition();
+  int whereY = (firstLine - baseLine)*height;  //baseline location
+
+  LOGV(baseLine);
+  LOGV(firstLine);
+  LOGV(lastLine);
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+
+  int lastInvalidLine = lastLine;
+  if (lastLine > nLines) {
+    lastLine = nLines;
+  }
+  int windowHeight = dataArea.rect().size().height();
+  int dataAreaWidth = dataArea.rect().size().width() - whereX;
+  ISize lineSize(dataAreaWidth, height);
+  IPoint offset(xMargin, 0);
+  int k;
+
+  LOGV(firstLine);
+  LOGV(lastLine);
+  for (k = firstLine; k >= 0 && k <= lastLine; k++, whereY += height) {
+    LOGSECTION("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;
+    ColorSpec *lineColor = getLineColor(k);
+    IColor bgndColor = lineColor->bg();
+    IColor fgndColor = lineColor->fg();
+
+    LOGV(lineRect.asString());
+    LOGV(where.asString());
+    event.clearBackground(lineRect,bgndColor);
+
+    AgString line = windowData->getLine(k);
+    char *p = line.pointer();
+    LOGV((int)p);
+    LOGV(p);
+    while (*p && *p != '<') {
+      p++;
+    }
+    if (*p == '<') {
+      while (*p && *p != '>') {
+	p++;
+      }
+    }
+    int bracketFlag = *p == '>';
+
+    LOGV(line.pointer());
+    FindTabs tabs(line.pointer());
+    p = tabs.getField();
+    int nCols = windowData->nColumns();
+    where += offset;
+    int i = 0;
+    char *star = 0;
+    IPoint starWhere = where;
+    if (*p == '*') {
+      *p = ' ';
+      star = p;
+    }
+    while (i < nCols && p) {
+      LOGV(p - line.pointer());
+      LOGV(p);
+      IPoint drawPoint(where);
+      int j = 0;
+      while (p[j] && p[j] == ' ') {
+	j++;
+      }
+      int flag = bracketFlag && p[j] == 'R' && p[j+7] == '<' && p[j+8] != '<';
+      if (!flag) {
+        char *q = p;
+        while (*q && strncmp(q, ", <", 3) != 0) {
+	  q++;
+	}
+        //LOGV(q);
+        if (*q == ',') {
+          q += 2;
+          *q = 0;
+          logFont(font(), p);
+          //LOGV(drawPoint.asString());
+          event.drawText(p, drawPoint, fgndColor);
+          drawPoint += IPair(font().textWidth(p), 0);
+          //drawPoint += IPair(gcFont.textWidth(p), 0);
+          //LOGV(font().textWidth(p)) LCV(p);
+          //LOGV(drawPoint.asString());
+          p = q;
+          *p = '<';
+        }
+      }
+      else {
+        p[j+7] = 0;
+        logFont(font(), p);
+        //LOGV(drawPoint.asString());
+        event.drawText(p, drawPoint, fgndColor);
+        drawPoint += IPair(font().textWidth(p), 0);
+        //LOGV(font().textWidth(p)) LCV(p);
+        //LOGV(drawPoint.asString());
+        p += j+7;
+        *p = '<';
+      }
+      //LOGV(flag);
+      //LOGV(p);
+      if (bracketFlag && *p == '<' && p[1] != '<') {
+        p += 2;
+        IFont markFont(FontSpec::markedToken);
+        char *q = p;
+        q = findDelimiter(q, '>');
+        if (q) {
+	  *q = 0;
+	}
+        //LOGV(p);
+        //LOGV(font().textWidth(p));
+        //LOGV(markFont.name()) LCV(markFont.pointSize());
+        //LOGV(markFont.textWidth(p));
+        logFont(font(), p);
+        //LOGV(drawPoint.asString());
+        int saveY = drawPoint.y();
+        drawPoint.setY(saveY +leadingBias);
+        //LOGV(drawPoint.asString());
+        markFont.beginUsingFont(presSpaceHandle);
+        logFont(font(), p);
+        event.drawText(p, drawPoint, fgndColor);
+        drawPoint.setY(saveY);
+        drawPoint += IPair(markFont.textWidth(p), 0);
+        //LOGV(font().textWidth(p)) LCV(p);
+        //LOGV(drawPoint.asString());
+        markFont.endUsingFont(presSpaceHandle);
+        p = q+1;
+        //LOGS("emphasis complete");
+      }
+      logFont(font(), p);
+      LOGV(drawPoint.asString());
+      event.drawText(p,drawPoint, fgndColor);
+      where += IPoint(tabArray[i], 0);
+      LOGV(font().textWidth(p)) LCV(p);
+      LOGV(tabArray[i]);
+      LOGV(where);
+      if (++i >= nCols) {
+	break;
+      }
+      p = tabs.getField();
+    }
+    if (star) {
+      event.drawText("*", starWhere, fgndColor);
+    }
+  }
+  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,color->bg());
+  }
+
+  font().endUsingFont(presSpaceHandle);
+  return true;
+}
+
+AgDataView &AgDataView::repaintLine(int line) {
+  if (windowData == 0) {
+    return *this;
+  }
+  if (line < 0 || line >= windowData->nLines()) {
+    return *this;
+  }
+  int topLine = verticalScrollBar.scrollBoxPosition();
+  int y = (line - topLine)*lineHeight();
+  LOGSECTION("AgDataView::repaintLine");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  LOGV(verticalScrollBar.scrollBoxPosition());
+  LOGV(line);
+  LOGV(cursorLine);
+  IRectangle rect(0,y,dataArea.rect().size().width(), y + lineHeight());
+  LOGV(rect.asString());
+  //dataArea.refresh(rect, true);
+  dataArea.refresh(rect);
+  return *this;
+}
+
+AgDataView &AgDataView::copyTo(IClipboard &clipboard) {
+  int i;
+  int nLines = windowData->nLines();
+  AgCharStack charStack;
+  if (copyTitle.exists()) {
+    //charStack.push(copyTitle).push("\r\n\r\n");
+    charStack << copyTitle << "\r\n\r\n";
+  }
+  //charStack.push(windowData->columnHeadTitle());
+  charStack << windowData->columnHeadTitle();
+  charStack.push("\r\n");
+  for (i = 0; i < nLines; i++) {
+    //charStack.push(windowData->getLine(i));
+    charStack << windowData->getLine(i);
+    charStack.push("\r\n");
+  }
+  clipboard.setText(charStack.popString().pointer());
+  return *this;
+}
+
+AgDataView &AgDataView::repositionWindow() {
+  LOGSECTION("AgDataView::repositionWindow");
+  LOGV(windowId);
+  LOGV((int) this);
+  int horizontalOffset = horizontalScrollBar.scrollBoxPosition()
+                   - prevHorizontal;
+  int verticalOffset = verticalScrollBar.scrollBoxPosition()
+                   - prevVertical;
+
+  IPoint displacement(-horizontalOffset, -lineHeight()*verticalOffset);
+
+  LOGV(displacement.asString());
+
+  int oldCursor = cursorLine;
+  int line = verticalScrollBar.scrollBoxPosition();
+  if (cursorLine < line) {
+    cursorLine = line;
+  }
+  else {
+    LOGV(windowData->nLines());
+    LOGV(verticalScrollBar.scrollableRange().asString());
+    LOGV(verticalScrollBar.visibleCount());
+    //line += verticalScrollBar.visibleCount() - 1;
+    line += verticalLineCount - 1;
+    if (line < 0) {
+      line = 0;
+    }
+    if (cursorLine > line) {
+      cursorLine = line;
+    }
+  }
+  if (columnHeadsPresent) {
+    int pos = horizontalScrollBar.scrollBoxPosition();
+    if (pos != columnHeadTitle.xPos) {
+      columnHeadTitle.xPos = pos;
+      columnHeadTitle.refresh();
+    }
+  }
+  LOGV(cursorLine);
+  dataArea.scrollWindow(displacement);
+  dataArea.refresh();
+  prevHorizontal = horizontalScrollBar.scrollBoxPosition();
+  prevVertical = verticalScrollBar.scrollBoxPosition();
+  if (cursorLine != oldCursor) {
+    repaintLine(oldCursor);
+    repaintLine(cursorLine);
+    windowData->synchCursor(cursorLine);
+    selectAction.performDeferred();
+  }
+
+  return *this;
+}
+
+Boolean AgDataView::findNext(AgString s) {
+  LOGSECTION("AgDataView::findNext");
+  LOGV(s);
+  searchProcess.setKey(s);
+  int ln = cursorLine + 1;
+  while (ln < windowData->nLines()) {
+    AgString line = windowData->getLine(ln);
+    LOGV(line);
+    char *p = searchProcess.scanForward(line.pointer(), line.size());
+    if (p) {
+      LOGV(ln) LCV(line);
+      setCursorLine(ln).synchCursor(ln);
+      return true;
+    }
+    ln++;
+  }
+  return false;
+}
+
+Boolean AgDataView::findPrev(AgString s) {
+  searchProcess.setKey(s);
+  int ln = cursorLine - 1;
+  while (ln >= 0) {
+    AgString line = windowData->getLine(ln);
+    char *p = searchProcess.scanReverse(line.pointer(), line.size());
+    if (p) {
+      setCursorLine(ln).synchCursor(ln);
+      return true;
+    }
+    ln--;
+  }
+  return false;
+}
+
+
+AgDataView &AgDataView::setCursorLine(int ln) {
+  LOGSECTION("AgDataView::setCursorLine");
+  LOGV(ln);
+  LOGV(windowId);
+  LOGV(frameWindow->windowTitle.text());
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+  LOGV(layoutComplete);
+
+  int lastLine = windowData->nLines() - 1;
+  if (ln > lastLine) {
+    ln = lastLine;
+  }
+  if (ln < 0) {
+    ln = 0;
+  }
+  int oldLine = cursorLine;
+  LOGV(windowData->nLines());
+  LOGV(verticalScrollBar.scrollableRange().asString());
+  LOGV(verticalScrollBar.visibleCount());
+  //int count = verticalScrollBar.visibleCount();
+  int count = verticalLineCount;
+  cursorLine = ln;
+  if (!layoutComplete) {
+    return *this;
+  }
+
+  LOGV(oldLine);
+  LOGV(cursorLine);
+  LOGV(count);
+
+  int firstLine = verticalScrollBar.scrollBoxPosition();
+  lastLine = firstLine + count - 1;
+
+  LOGV(firstLine);
+  LOGV(lastLine);
+  int repositionFlag = 0;
+  if (cursorLine < firstLine) {
+    firstLine = cursorLine;
+    verticalScrollBar.moveScrollBoxTo(firstLine);
+    repositionFlag = 1;
+  }
+  if (cursorLine > lastLine) {
+    firstLine = cursorLine - (count - 1);
+    if (firstLine < 0) {
+      firstLine = 0;
+    }
+    verticalScrollBar.moveScrollBoxTo(firstLine);
+    repositionFlag = 1;
+  }
+  if (repositionFlag) {
+    repositionWindow();
+  }
+  if (cursorLine == oldLine) {
+    return *this;
+  }
+  //windowData->synchCursor(cursorLine);
+  repaintLine(oldLine);
+  repaintLine(cursorLine);
+  LOGV(firstLine);
+  return *this;
+}
+
+AgDataView &AgDataView::synchCursor(int ln) {
+  windowData->synchCursor(ln);
+  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.
+ */
+
+AgDataView &AgDataView::doLayout() {
+  LOGSECTION("AgDataView::doLayout");
+  LOGV((int) frameWindow);
+  LOGV(id());
+
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV(font().name());
+  LOGV(font().pointSize());
+  ISize canvasSize = size();
+  int canvasWidth  = canvasSize.width();
+  int canvasHeight = canvasSize.height();
+  if (canvasHeight == 0) {
+    return *this;
+  }
+  //if (tableWidth == 0) tableWidth = findMaxWidth();
+  tableWidth = findMaxWidth();
+  tableHeight = lineHeight()*windowData->nLines();
+
+  LOGV(canvasWidth);
+  LOGV(canvasHeight);
+
+  int dataWidth  = canvasWidth;
+  int dataHeight = canvasHeight;
+  int dataY      = 0;
+  ISize columnHeadSize = columnHeadTitle.minimumSize();
+  if (columnHeadsPresent) {
+    dataY = columnHeadSize.height() + 2;
+    columnHeadSize.setHeight(dataY);
+    dataHeight -= dataY;
+    columnHeadTitle.moveTo(IPoint(0, 0));
+  }
+
+  LOGV(dataWidth);
+  LOGV(dataHeight);
+
+
+  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 > dataHeight
+      && tableWidth <= canvasWidth - vsbWidth - 1) {
+    // vertical scroll bar only
+    vsb = true;
+    hsb = false;
+  }
+  else if (tableWidth > canvasWidth
+	   && tableHeight <= dataHeight - hsbHeight - 1) {
+    // horizontal scroll bar only
+    vsb = false;
+    hsb = true;
+  }
+  else if (tableWidth <= canvasWidth && tableHeight <= dataHeight) {
+    // no scroll bars
+    vsb = hsb = false;
+  }
+
+  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, canvasHeight - hsbHeight));
+    horizontalScrollBar.sizeTo(ISize(dataWidth, hsbHeight));
+  }
+
+  dataArea.sizeTo(ISize(dataWidth, dataHeight));
+  dataArea.moveTo(IPoint(0, dataY));
+
+
+  LOGV(dataArea.size().asString());
+  LOGV(dataArea.parentSize().asString());
+
+  LOGV(rect().asString());
+  LOGV(dataArea.rect().asString());
+  LOGV(horizontalScrollBar.rect().asString());
+  LOGV(verticalScrollBar.rect().asString());
+
+  //int widthUnit = charWidth();
+
+  horizontalScrollBar.setScrollableRange(IRange(0,tableWidth-1));
+  horizontalScrollBar.setVisibleCount(dataWidth);
+  LOGV(horizontalScrollBar.scrollableRange().asString()) LCV(tableWidth);
+  LOGV(horizontalScrollBar.visibleCount()) LCV(dataWidth);
+
+
+  verticalScrollBar.setScrollableRange(IRange(0, windowData->nLines() - 1));
+  verticalLineCount = dataHeight/lineHeight();
+  //verticalScrollBar.setVisibleCount(dataHeight/lineHeight());
+  verticalScrollBar.setVisibleCount(verticalLineCount);
+  LOGV(dataHeight/lineHeight());
+  LOGV(windowData->nLines());
+  LOGV(verticalScrollBar.scrollableRange().asString());
+  LOGV(verticalScrollBar.visibleCount());
+  LOGV(verticalLineCount);
+  horizontalPosition = min(
+    (int) horizontalScrollBar.scrollBoxRange().upperBound(),
+    horizontalPosition
+  );
+  verticalPosition = min(
+    (int) verticalScrollBar.scrollBoxRange().upperBound(),
+    verticalPosition
+  );
+
+  horizontalScrollBar.setMinScrollIncrement(charWidth());
+  verticalScrollBar.setMinScrollIncrement(1);
+
+  prevHorizontal = horizontalScrollBar.scrollBoxPosition();
+  horizontalScrollBar.moveScrollBoxTo(horizontalPosition);
+
+  prevVertical = verticalScrollBar.scrollBoxPosition();
+  verticalScrollBar.moveScrollBoxTo(verticalPosition);
+
+  LOGV(prevHorizontal);
+  LOGV(prevVertical);
+  LOGV(horizontalPosition);
+  LOGV(verticalPosition);
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  LOGV(verticalScrollBar.scrollBoxPosition());
+  LOGV(verticalPosition);
+
+  if (columnHeadsPresent) {
+    columnHeadSize.setWidth(dataWidth);
+    columnHeadTitle.sizeTo(columnHeadSize);
+    columnHeadTitle.xPos = horizontalPosition;
+    columnHeadTitle.refresh();
+  }
+  layoutComplete = 1;
+  setCursorLine(cursorLine).synchCursor(cursorLine);
+  return *this;
+}
+
+Boolean AgDataView::lineDown(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &verticalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::lineDown");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::lineLeft(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &horizontalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::lineLeft");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::lineRight(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &horizontalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::lineRight");
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  moveScrollBox(event);
+  repositionWindow();
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  return true;
+}
+
+Boolean AgDataView::lineUp(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &verticalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::lineUp");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::pageDown(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &verticalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::pageDown");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::pageLeft(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &horizontalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::pageLeft");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::pageRight(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &horizontalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::pageRight");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::pageUp(IScrollEvent &event) {
+  if (event.scrollBarWindow() != &verticalScrollBar) {
+    return false;
+  }
+  LOGSECTION("AgDataView::pageUp");
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::scrollBoxTrack(IScrollEvent &event) {
+  LOGSECTION("AgDataView::scrollBoxTrack");
+  LOGV(verticalScrollBar.scrollBoxPosition());
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  moveScrollBox(event);
+  //if (!event.controlWindow()->hasPointerCaptured()) {
+  //  event.controlWindow()->capturePointer(true);
+  //}
+  LOGV(verticalScrollBar.scrollBoxPosition());
+  LOGV(horizontalScrollBar.scrollBoxPosition());
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::scrollBoxTrackEnd(IScrollEvent &event) {
+  //event.controlWindow()->releasePointer();
+  moveScrollBox(event);
+  repositionWindow();
+  return true;
+}
+
+Boolean AgDataView::key(IKeyboardEvent &event) {
+  return false;
+}
+
+
+Boolean AgDataView::virtualKeyPress(IKeyboardEvent &event) {
+  LOGSECTION("AgDataView::virtualKeyPress");
+  LOGV((int) frameWindow);
+  LOGV((int) event.virtualKey());
+  LOGV(event.isShiftDown()) LCV(event.isCtrlDown()) LCV(event.isAltDown());
+  switch (event.virtualKey()) {
+    case IKeyboardEvent::newLine:
+    case IKeyboardEvent::enter: {
+      LOGSECTION("AgDataView::enter");
+      if (enterAction.exists()) {
+	enterAction.performDeferred();
+      }
+      return true;
+    }
+    case IKeyboardEvent::up: {
+      int line = cursorLine;
+      if (cursorLine > 0) {
+	cursorLine--;
+      }
+      else {
+	messageBeep();
+      }
+      if (line != cursorLine) {
+        windowData->synchCursor(cursorLine);
+        selectAction.performDeferred();
+      }
+      LOGV(cursorLine);
+      if (cursorLine < verticalScrollBar.scrollBoxPosition()) {
+        repaintLine(line);
+        verticalScrollBar.moveScrollBoxTo(cursorLine);
+        repositionWindow();
+        return true;
+      }
+      repaintLine(line);
+      repaintLine(cursorLine);
+      return true;
+    }
+    case IKeyboardEvent::down: {
+      LOGSECTION("Cursor down one line");
+      int line = cursorLine;
+      if (cursorLine < windowData->nLines() - 1) {
+	cursorLine++;
+      }
+      else {
+	messageBeep();
+      }
+      if (line != cursorLine) {
+        selectAction.performDeferred();
+        windowData->synchCursor(cursorLine);
+      }
+      LOGV(line) LCV(cursorLine);
+      LOGV((int) this);
+      LOGV((int) &cursorLine);
+      LOGV(cursorLine);
+      int bottomLine = verticalScrollBar.scrollBoxPosition()
+                       + verticalLineCount - 1;
+                       //+ verticalScrollBar.visibleCount() - 1;
+
+      LOGV(line);
+      LOGV(cursorLine);
+      LOGV(bottomLine);
+
+      if (cursorLine > bottomLine) {
+        LOGS("scrolling");
+        verticalScrollBar.moveScrollBoxTo(
+          cursorLine
+          - verticalLineCount + 1
+          //- verticalScrollBar.visibleCount() + 1
+        );
+        repositionWindow();
+      }
+      repaintLine(line);
+      repaintLine(cursorLine);
+      return true;
+    }
+    case IKeyboardEvent::pageUp: {
+      int line = cursorLine;
+      if (cursorLine == 0) {
+        messageBeep();
+        return true;
+      }
+      //cursorLine -= verticalScrollBar.visibleCount();
+      cursorLine -= verticalLineCount;
+      if (cursorLine < 0 || event.isCtrlDown()) {
+        cursorLine = 0;
+        repaintLine(line);
+        repaintLine(cursorLine);
+      }
+      if (cursorLine < verticalScrollBar.scrollBoxPosition()) {
+        verticalScrollBar.moveScrollBoxTo(cursorLine);
+        repositionWindow();
+      }
+      if (line != cursorLine) {
+        windowData->synchCursor(cursorLine);
+        selectAction.performDeferred();
+      }
+      return true;
+    }
+    case IKeyboardEvent::home: {
+      if (cursorLine && event.isCtrlDown()) {
+        int line = cursorLine;
+        cursorLine = 0;
+        windowData->synchCursor(cursorLine);
+        selectAction.performDeferred();
+        if (cursorLine < verticalScrollBar.scrollBoxPosition()) {
+          repaintLine(line);
+          verticalScrollBar.moveScrollBoxTo(cursorLine);
+          repositionWindow();
+          return true;
+        }
+        repaintLine(line);
+        repaintLine(cursorLine);
+        return true;
+      }
+      else {
+	messageBeep();
+      }
+      return true;
+    }
+    case IKeyboardEvent::end: {
+      int lastLine = windowData->nLines() - 1;
+      if (cursorLine < lastLine && event.isCtrlDown()) {
+        int line = cursorLine;
+        cursorLine = lastLine;
+        windowData->synchCursor(cursorLine);
+        selectAction.performDeferred();
+        if (cursorLine < 
+	    verticalScrollBar.scrollBoxPosition()-verticalLineCount) {
+          repaintLine(line);
+          verticalScrollBar.moveScrollBoxTo(cursorLine);
+          repositionWindow();
+          return true;
+        }
+        repaintLine(line);
+        repaintLine(cursorLine);
+        return true;
+      }
+      else {
+	messageBeep();
+      }
+      return true;
+    }
+    case IKeyboardEvent::pageDown: {
+      int line = cursorLine;
+      cursorLine += verticalLineCount;
+      int lastLine = windowData->nLines() - 1;
+      if (cursorLine == lastLine) {
+        messageBeep();
+        return true;
+      }
+      if (cursorLine > lastLine || event.isCtrlDown()) {
+        cursorLine = lastLine;
+        repaintLine(line);
+        repaintLine(cursorLine);
+      }
+      if (cursorLine > verticalScrollBar.scrollBoxPosition()) {
+        verticalScrollBar.moveScrollBoxTo(
+          cursorLine - verticalLineCount + 1
+        );
+        repositionWindow();
+      }
+      if (line != cursorLine) {
+        windowData->synchCursor(cursorLine);
+        selectAction.performDeferred();
+      }
+      return true;
+    }
+    case IKeyboardEvent::left: {
+      int position = horizontalScrollBar.scrollBoxPosition() - charWidth();
+      if (position >= 0) {
+        horizontalScrollBar.moveScrollBoxTo(position);
+        repositionWindow();
+      }
+      else {
+	messageBeep();
+      }
+      return true;
+    }
+    case IKeyboardEvent::right: {
+      int position = horizontalScrollBar.scrollBoxPosition() + charWidth();
+      if (position <= horizontalScrollBar.scrollableRange().upperBound()) {
+        horizontalScrollBar.moveScrollBoxTo(position);
+        repositionWindow();
+      }
+      else {
+	messageBeep();
+      }
+      return true;
+    }
+    //default: return false;
+  }
+  return false;
+}
+
+Boolean AgDataView::mouseClicked(IMouseClickEvent &event) {
+  //LOGSECTION("AgDataView::mouseClicked", Log::off);
+  LOGSECTION_OFF("AgDataView::mouseClicked");
+  LOGV(frameWindow->activeFlag);
+
+  unsigned line = event.mousePosition().y()/lineHeight();
+  line += verticalScrollBar.scrollBoxPosition();
+  unsigned nLines = windowData->nLines();
+
+  if (ControlPanel::helpCursorSet) {
+    if (event.mouseButton() == IMouseClickEvent::button2) {
+      return false;
+    }
+    if (event.mouseAction() == IMouseClickEvent::down) {
+      if (line < nLines && line != cursorLine) {
+        setCursorLine(line).synchCursor(line);
+        selectAction.performDeferred();
+      }
+      showHelp(event);
+      ControlPanel::helpCursorSet = 0;
+      ControlPanel::resetCursor();
+    }
+    return true;
+  }
+
+  if (event.controlWindow() == &columnHeadTitle
+      || event.controlWindow() == &verticalScrollBar
+      || event.controlWindow() == &horizontalScrollBar)
+  {
+    if (event.mouseAction() == IMouseClickEvent::down) {
+      dataArea.setFocus();
+    }
+    return false;
+  }
+
+  LOGV(event.mouseButton());
+  LOGV(event.mouseAction());
+  LOGV(line);
+
+  if (event.mouseButton() == IMouseClickEvent::button2) {
+    if (!dataArea.hasFocus()) {
+      dataArea.setFocus();
+    }
+    if (line >= nLines) {
+      return true;
+    }
+/*
+    if (event.mouseAction() == IMouseClickEvent::down) {
+      rightButtonDown = true;
+    }
+    else if (event.mouseAction() == IMouseClickEvent::up) {
+      rightButtonDown = false;
+    }
+    else if (event.mouseAction() == IMouseClickEvent::click) {
+      LOGS("right click");
+      if (line == cursorLine) {
+	//return true;
+	return false;
+      }
+      setCursorLine(line).synchCursor(line);
+      selectAction.performDeferred();
+      return false;
+    }
+*/
+    if (event.mouseAction() == IMouseClickEvent::down) {
+      rightButtonState = buttonDown;
+    }
+    else if (event.mouseAction() == IMouseClickEvent::up) {
+      rightButtonState = waitingForClick;
+      if (line == cursorLine) {
+	return false;
+      }
+      setCursorLine(line).synchCursor(line);
+      selectAction.performDeferred();
+      return false;
+    }
+    else if (event.mouseAction() == IMouseClickEvent::click) {
+      rightButtonState = buttonIdle;
+      return false;
+    }
+  }
+  if (line >= nLines && event.mouseAction() == IMouseClickEvent::down) {
+    if (!dataArea.hasFocus()) {
+      dataArea.setFocus();
+    }
+    return false;
+  }
+  if (!mouseDown && line >= nLines) {
+    return false;
+  }
+  if (event.mouseButton() != IMouseClickEvent::button1) {
+    return false;
+  }
+  switch (event.mouseAction()) {
+    case IMouseClickEvent::down: {
+      if (event.windowUnderPointer() != dataArea.handle()) {
+	return false;
+      }
+      LOGS("mouse down");
+      if (!dataArea.hasFocus()) {
+	dataArea.setFocus();
+      }
+      dataArea.capturePointer(true);
+      mouseDownCursorLine = cursorLine;
+      if (line != cursorLine) {
+        setCursorLine(line).synchCursor(line);
+        selectAction.performDeferred();
+      }
+      mouseDown = true;
+      return true;
+    }
+    case IMouseClickEvent::up: {
+      LOGS("mouse up");
+      mouseDown = false;
+      dataArea.capturePointer(false);
+      return true;
+    }
+    case IMouseClickEvent::click: {
+      if (event.windowUnderPointer() != dataArea.handle()) {
+	return false;
+      }
+      LOGS("mouse click");
+      return true;
+    }
+    case IMouseClickEvent::doubleClick: {
+      LOGS("double click");
+      enterAction.performDeferred();
+      return true;
+    }
+  }
+  return false;
+}
+
+Boolean AgDataView::mouseMoved(IMouseEvent &event) {
+  if (!mouseDown) {
+    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("AgDataView::mouseMoved");
+  LOGV((int) frameWindow);
+  LOGV(topLine) LCV(mouseLine);
+  dragMouse(topLine, mouseLine);
+  if (mouseLine >= 0 && mouseLine < verticalLineCount) {
+    return true;
+  }
+  topLine = verticalScrollBar.scrollBoxPosition();
+  IReference<ITimerFn> timerFn(new MouseDragTimer(this, topLine, mouseLine));
+  mouseTimer.start(timerFn, 50);
+  LOGS("timer started");
+  return true;
+}
+
+void AgDataView::dragMouse(int topLine, int mouseLine) {
+  LOGSECTION("AgDataView::DragMouse");
+  LOGV((int) frameWindow);
+  int bottomLine = topLine + verticalLineCount - 1;
+  int line = topLine + mouseLine;
+  if (line < 0) {
+    line = 0;
+  }
+  if (line >= windowData->nLines()) {
+    line = windowData->nLines() - 1;
+  }
+  if (line == cursorLine) {
+    return;
+  }
+  int oldLine = cursorLine;
+  cursorLine = line;
+  windowData->synchCursor(cursorLine);
+  selectAction.performDeferred();
+  LOGV(topLine);
+  LOGV(mouseLine);
+  LOGV(oldLine);
+  LOGV(cursorLine);
+  if (cursorLine < topLine) {
+    repaintLine(oldLine);
+    verticalScrollBar.moveScrollBoxTo(cursorLine);
+    repositionWindow();
+    return;
+  }
+  //if (cursorLine > bottomLine + 1) {
+  if (cursorLine > bottomLine) {
+    repaintLine(oldLine);
+    verticalScrollBar.moveScrollBoxTo(topLine + cursorLine - bottomLine);
+    repositionWindow();
+    return;
+  }
+  else {
+    repaintLine(oldLine);
+    repaintLine(cursorLine);
+  }
+  LOGV(cursorLine);
+  windowData->synchCursor(cursorLine);
+  selectAction.performDeferred();
+  return;
+}
+
+void AgDataView::mouseDragTimerInterrupt(int &topLine, int mouseLine) {
+  LOGSECTION("AgDataView::mouseDragTimerInterrupt");
+  LOGV((int) frameWindow);
+  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();
+}
+
+AgDataView::MouseDragTimer::MouseDragTimer(AgDataView * window_,
+					   int topLine_,
+					   int mouseLine_)
+  : window(window_)
+  , topLine(topLine_)
+  , mouseLine(mouseLine_)
+{}
+
+void AgDataView::MouseDragTimer::timerExpired(unsigned long) {
+  LOGSECTION("AgDataView::MouseDragTimer::timerExpired");
+  window->mouseDragTimerInterrupt(topLine, mouseLine);
+}
+
+Boolean AgDataView::command(ICommandEvent &event) {
+  unsigned id = event.commandId() - 1;
+  LOGSECTION("AgDataView::command");
+  LOGV((int) event.controlWindow());
+  LOGV((int) event.dispatchingWindow());
+  LOGV((int) frameWindow);
+  LOGV((int) id);
+
+  if (id >= auxMenu.size()) {
+    return false;
+  }
+  LOGV(ControlPanel::helpCursorSet) LCV(AgFrame::menuShowingFlag);
+  if (ControlPanel::helpCursorSet) {
+    ControlPanel::helpCursorSet = 0;
+    ControlPanel::resetCursor();
+    AgHelpWindow::showHelp(auxMenu[id].text);
+    return true;
+  }
+
+  auxMenu[id].action.perform();
+  return true;
+}
+
+AgDataView &AgDataView::initPopUp() {
+  if (popUpInitialized) {
+    return *this;
+  }
+  popUpInitialized = 1;
+
+  LOGSECTION("AgDataView::initPopUp");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+  LOGV((int) &popUpMenu);
+
+  int n = auxMenu.size();
+  int i;
+  for (i = 0; i < n; i++) {
+    char *text = auxMenu[i].text.pointer();
+    LOGV(text);
+    LOGV((int) auxMenu[i].displayControl);
+    popUpMenu.addText(i+1, text);
+  }
+  return *this;
+}
+
+AgDataView &AgDataView::validatePopUp() {
+  LOGSECTION("AgDataView::validatePopUp");
+  LOGV(windowId) LCV((int) this);
+  LOGV((int) frameWindow) LCV((int) windowData);
+  LOGV(windowData->getCursorLine());
+  LOGV(ControlPanel::helpCursorSet) LCV(AgFrame::menuShowingFlag);
+
+  if (ControlPanel::helpCursorSet) {
+    return *this;
+  }
+  int n = auxMenu.size();
+  LOGV(n);
+  int i;
+  for (i = 0; i < n; i++) {
+    //char *text = auxMenu[i].text.pointer();
+    //LOGV(text);
+    dc *d = auxMenu[i].displayControl;
+    LOGV((int) d);
+    ok_ptr(d);
+    dc::MenuOption **option = d->getAuxWinMenu();
+#ifdef INCLUDE_LOGGING
+    LOGV((int) option) LCV((int) option[i]);
+    union { int(dc::*fun)(); int address; } kluge;
+    kluge.fun = option[i]->ok_action;
+    LOGV(kluge.address);
+#endif
+    int flag = ((*d).*(option[i]->ok_action))();
+    LOGV(flag);
+    popUpMenu.enableItem(i+1, flag);
+  }
+  return *this;
+}
+
+Boolean AgDataView::makePopUpMenu(IMenuEvent &event ) {
+  LOGSECTION("AgDataView::makePopUpMenu");
+  LOGV(windowId);
+  LOGV((int) this);
+  LOGV((int) frameWindow);
+  if (!auxMenu.exists()) {
+    return false;
+  }
+  IPoint where = dataArea.position();
+  if (rightButtonState == waitingForClick) {
+    LOGV(event.mousePosition().asString());
+    where = event.mousePosition();
+  }
+  LOGV(auxMenu.exists());
+
+  int n = auxMenu.size();
+  int i;
+  for (i = 0; i < n; i++) {
+    popUpMenu.enableItem(i+1);
+  }
+  validatePopUp();
+
+  AgFrame::activeAuxMenu = auxMenu;
+
+  LOGS("activeAuxMenu has been set");
+
+  AgFrame::activePopUpMenu = &popUpMenu;
+
+  LOGS("activePopUpMenu has been set");
+  LOGS("ready to display popUpMenu");
+
+  //popUpMenu.show(event.mousePosition());
+  popUpMenu.show(where);
+
+  LOGS("popUpMenu should be visible");
+
+  AgFrame::activeAuxMenu.discardData();
+  AgFrame::activePopUpMenu = 0;
+
+  return true;
+}
+
+Boolean AgDataView::showHelp(IEvent &event) {
+  showHelp();
+  return true;
+}
+
+void AgDataView::showHelp() {
+  AgString topic;
+  LOGSECTION("AgDataView::showHelp");
+  int n = popUpMenu.numberOfItems();
+  for (int i = 0; i++ < n;) {
+    IMenuItem item = popUpMenu.menuItem(i);
+    if (item.isHighlighted()) {
+      topic = auxMenu[i-1].text;
+      break;
+    }
+  }
+  LOGV(i) LCV(n) LCV(topic);
+  if (!topic.exists() && helpTopic != 0) {
+    topic = helpTopic;
+  }
+  if (!topic.exists()) {
+    topic = windowData->findHelpTopic();
+  }
+  if (!topic.exists()) {
+    topic = windowData->headTitle();
+  }
+  AgHelpWindow::showHelp(topic);
+}
+
+Boolean AgDataView::menuEnded(IMenuEvent &event) {
+  LOGSECTION("AgDataView::menuEnded");
+  LOGV((int) frameWindow);
+  LOGV(ControlPanel::helpCursorSet) LCV(AgFrame::menuShowingFlag);
+  if (!ControlPanel::helpCursorSet) {
+    frameWindow->activeAuxMenu.discardData();
+  }
+  return true;
+}
+