view anagram/vaclgui/dview.cpp @ 14:a02e9434072e

Fix friend declaration for gcc10. XXX: did not check it against the IBM compiler, might end up needing XXX: to be conditional.
author David A. Holland
date Tue, 31 May 2022 00:59:42 -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;
}