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

Add lint-style FALLTHROUGH annotations to fallthrough cases. (in the parse engine and thus the output code) Document this, because the old output causes warnings with gcc10.
author David A. Holland
date Mon, 13 Jun 2022 00:04:38 -0400
parents 13d2b8934445
children
line wrap: on
line source

/*
 * AnaGram, A System for Syntax Directed Programming
 * Copyright 1997-2002 Parsifal Software. All Rights Reserved.
 * See the file COPYING for license and usage terms.
 *
 * 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;
}