view anagram/vaclgui/helpview.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.
 *
 * helpview.cpp
 */

#include <icoordsy.hpp>
#include <windows.h>

#include "agcstack.h"
#include "agstack.h"
#include "agstring.h"
#include "ctrlpanel.hpp"
#include "dspar.hpp"
#include "dview.hpp"
#include "file.h"
#include "help.h"
#include "helpview.hpp"
#include "minmax.h"
#include "stacks.h"
#include "vaclgui-res.h"
#include "vaclgui.hpp"

//#define INCLUDE_LOGGING
#include "log.h"


const int AgHelpView::defaultWindowHeight = 15;
AgBalancedTree<AgString> DrawingArea::traversedLinks;
AgBalancedTree<DrawingArea *> DrawingArea::activeViews;


DigSetter::Style DrawingArea::displayStyle[4] = {
  DigSetter::Style(FontSpec::help, ColorSpec::helpText),
  DigSetter::Style(FontSpec::help, ColorSpec::helpLink),
  DigSetter::Style(FontSpec::help, ColorSpec::helpUsedLink),
  DigSetter::Style(FontSpec::helpTitle, ColorSpec::helpText)
};

static int leading(IFont &f) {
  return f.maxSize().height() + f.externalLeading();
}

HelpWord::HelpWord(char *text, int nChar, cint where, Style index)
  : DigSetter::Dig(text, nChar, where, index)
  , linktopic("")
  , indent(0)
  , bullet(0)
  , forceBreak(0)
  , noBreak(0)
  , linkTraversed(0)
  , highlight(0)
{
  //LOGSECTION("HelpWord::HelpWord");
  //LOGV(AgString(text, nChar));
  //LOGV(nChar);
}

HelpWord::HelpWord(const HelpWord &word)
  : DigSetter::Dig(word)
  , linktopic(word.linktopic)
  , indent(word.indent)
  , bullet(word.bullet)
  , forceBreak(word.forceBreak)
  , noBreak(word.noBreak)
  , linkTraversed(word.linkTraversed)
  , highlight(word.highlight)
{
  //LOGSECTION("HelpWord::HelpWord(const HelpWord &)");
}

IRectangle HelpWord::rect(DigSetter::Style *style) {
  IFont &font = style[styleIndex].font;
  int above = font.maxAscender();
  int below = font.maxDescender();
  IPoint location(where.x, where.y - above);
  ISize size(width, above+below);
  return IRectangle(location,size);
}

DrawingArea::DrawingArea(IWindow *owner_, AgString topic_)
  : IStaticText(nextChildId(), owner_, owner_)
  , windowFont(FontSpec::help)
  , margin(windowFont.charWidth('M'))
  , paraLeading((windowFont.maxSize().height() + 
		 windowFont.externalLeading())/2)
  , topic(topic_)
  , setter(this, displayStyle)
  , painting(0)
  , popUpMenu(this, nextChildId())
  , refreshAction(*this, refreshAllLinks)
  , frameWindow(0)
  , highlightText(0)
  , nHighlight(0)
  , fingerCursorSet(0)
  , fingerCursor(IResourceLibrary().loadPointer(IDI_FINGER_CUR))
  , helpShowing(0)
  , rightButtonState(buttonIdle)
{
  LOGSECTION("DrawingArea::DrawingArea");
  IWindow *f;
  for (f = parent(); !f->isFrameWindow(); f=f->parent()) {
    /* nothing */
  }
  frameWindow = (AgFrame *) f;
  //LOGV(int(f));

  int i;
  for (i = 0; i < 256; i++) {
    charWidth[i] = windowFont.charWidth(i);
  }
  activeViews.insert(this);
  LOGV((int) this);
#ifdef INCLUDE_LOGGING
  for (i = 0; i < activeViews.size(); i++) {
    //LOGV((int) activeViews.sortedItem(i));
    //LOGV(activeViews.sortedItem(i)->topic);
    LOGV((int) activeViews[i]) LCV(activeViews[i]->topic);
  }
#endif
  //paraIndent = windowFont.charWidth('M') + margin;
  paraIndent = margin;
  helpText = getHelpText(topic.pointer());
  links = findLinks(helpText);
  LOGV(links.size()) LCV(activeViews.size()) LCV(topic);
  sortedLinks = sortLinks(links);
  setMinimumSize(calcMinimumSize());
  LOGV(minimumSize().asString());
  int minWidth = minimumSize().width() + 2*margin;;
  int maxWidth = IWindow::desktopWindow()->size().width();
  measure = 56*avgCharWidth() + 2*margin;
  if (measure < minWidth) {
    measure = minWidth;
  }
  if (measure > maxWidth) {
    measure = maxWidth;
  }
  LOGV(measure);
  measureText();
  preferredSize = ISize(longestLine, nLines);

  initPopUp();

  ICommandHandler ::handleEventsFor(this);
  IMenuHandler    ::handleEventsFor(this);
  //IMenuHandler    ::handleEventsFor(frameWindow);
  IMouseHandler   ::handleEventsFor(this);
  IPaintHandler   ::handleEventsFor(this);
  //AgHelpHandler   ::handleEventsFor(&popUpMenu);
  AgHelpHandler   ::handleEventsFor(frameWindow);
  //LOGS("Help handler attached");
}


DrawingArea::~DrawingArea() {
  LOGSECTION("DrawingArea::~DrawingArea");
  LOGV((int) this);
#ifdef INCLUDE_LOGGING
  for (int i = 0; i < activeViews.size(); i++) {
    //LOGV((int) activeViews.sortedItem(i));
    //LOGV(activeViews.sortedItem(i)->topic);
    LOGV((int) activeViews[i]) LCV(activeViews[i]->topic);
  }
#endif
  int flag = activeViews.remove(this);
  assert(flag);
#ifdef INCLUDE_LOGGING
  for (i = 0; i < activeViews.size(); i++) {
    //LOGV((int) activeViews.sortedItem(i));
    //LOGV(activeViews.sortedItem(i)->topic);
    LOGV((int)activeViews[i]) LCV(activeViews[i]->topic);
  }
#endif
  LOGV(topic) LCV(activeViews.size());
  ICommandHandler ::stopHandlingEventsFor(this);
  IMenuHandler    ::stopHandlingEventsFor(this);
  //IMenuHandler    ::stopHandlingEventsFor(frameWindow);
  IMouseHandler   ::stopHandlingEventsFor(this);
  IPaintHandler   ::stopHandlingEventsFor(this);
  //AgHelpHandler   ::stopHandlingEventsFor(&popUpMenu);
  AgHelpHandler   ::stopHandlingEventsFor(frameWindow);
}

void DrawingArea::reset() {
  LOGSECTION("DrawingArea::reset");
  disableUpdate();
  windowFont = FontSpec::help;
  setFont(FontSpec::help);
  for (int i = 0; i < 256; i++) {
    charWidth[i] = windowFont.charWidth(i);
  }
  margin = windowFont.charWidth('M');
  //paraIndent = windowFont.charWidth('M') + margin;
  paraIndent = margin;
  paraLeading = (windowFont.maxSize().height() + 
		 windowFont.externalLeading())/2;
  LOGV(nWords);
  LOGV(word.size());
  int minWidth = minimumSize().width() + 2*margin;;
  int maxWidth = IWindow::desktopWindow()->size().width();
  measure = 56*avgCharWidth() + 2*margin;
  if (measure < minWidth) {
    measure = minWidth;
  }
  if (measure > maxWidth) {
    measure = maxWidth;
  }
/*
  for (int j = 0; j < nWords; j++) {
    setter.measureDig(word[j]);
  }
  remeasureText();
*/
  helpText = getHelpText(topic.pointer());
  measureText();
  //remeasureText();
  enableUpdate();
  show();
}

DrawingArea &DrawingArea::copyTo(IClipboard &clipboard) {
  LOGSECTION("DrawingArea::copyTo");
  int i;
  AgCharStack charStack;
  LOGV(nWords);
  for (i = 0; i < nWords; i++) {
    if (word[i].indent) {
      charStack.push("   ");
    }
    charStack.push(word[i].text, word[i].textLength);
    if (word[i].forceBreak) {
      charStack.push("\r\n\r\n   ");
    }
    else {
      charStack.push(' ');
    }
  }
  clipboard.setText(charStack.popString().pointer());
  return *this;
}

void AgHelpView::onFontChange() {
  LOGSECTION("AgHelpView::onFontChange");
  disableUpdate();
  dataArea.reset();
  doLayout();
  enableUpdate();
  show();
}

void DrawingArea::refreshAllLinks() {
  LOGSECTION("DrawingArea::refreshAllLinks");
  int i;
  LOGV(topic);
  LOGV(activeViews.size());
  for (i = 0; i < activeViews.size(); i++) {
    //LOGV(activeViews.sortedItem(i)->topic);
    LOGV(activeViews[i]->topic);
    //activeViews.sortedItem(i)->refreshLinks();
    activeViews[i]->refreshLinks();
  }
}

DrawingArea &DrawingArea::refreshLinks() {
  LOGSECTION("DrawingArea::refreshLinks");
  AgStack<AgString> tLinks;
  int n = links.size();
  LOGV(topic);
  LOGV(links.size());
  int i;
  for (i = 0; i < n; i++) if (traversedLinks.includes(links[i])) {
    tLinks.push(links[i]);
  }
  n = tLinks.size();
  LOGV(tLinks.size());
  if (n == 0) {
    return *this;
  }
  for (i = 0; i < n; i++) {
    int j;
    AgString link = tLinks[i];
    int nLinkedWords = linkedWords.size();
    for (j = 0; j < nLinkedWords; j++) {
      int k = linkedWords[j];
      if (word[k].linktopic != link) {
	continue;
      }
      if (word[k].linkTraversed) {
	continue;
      }
      word[k].linkTraversed = 1;
      word[k].styleIndex = HelpWord::usedStyle;
      setter.refresh(word[k]);
    }
  }
  return *this;
}

Boolean DrawingArea::paintWindow(IPaintEvent &event) {
  //LOGSECTION("DrawingArea::paintWindow");
  if (painting) {
    return true;
  }
  painting++;
  IPresSpaceHandle handle = event.presSpaceHandle();    //cookie
  SetBkMode(handle, TRANSPARENT);
  IRectangle invalidRect(
    ICoordinateSystem::isConversionNeeded()
    ? ICoordinateSystem::convertToApplication(event.rect(),size())
    : event.rect()
  );
  //LOGV(invalidRect.asString());
  IColor bgColor(ColorSpec::helpText.bg());
  setter.setEvent(event);
  setter.clear(invalidRect);
  int top = invalidRect.minY();
  int bot = invalidRect.maxY();
  IFont &font = FontSpec::help;
  int descender = font.maxDescender();
  int ascender  = font.maxAscender();
  //LOGV(ascender) LCV(descender);
  int first = 0;
  int last = nWords - 1;
  //LOGV(top) LCV(bot);
  while (first < last) {
    int middle = (first + last) /2;
    //LOGV(first) LCV(middle) LCV(last);
    //LOGV(word[middle].where.y + descender);
    if (setter.bottom(word[middle]) < top) {
      first = middle + 1;
    }
    else {
      last = middle - 1;
    }
  }
  //LOGS("found first dig");
  char *endHighlight = highlightText + nHighlight;

  while (first < nWords && setter.top(word[first]) <= bot) {
    HelpWord &aWord = word[first];
    if (endHighlight && aWord.text < endHighlight
        && aWord.text + aWord.textLength >= highlightText)
    {
      HelpWord highlightWord = aWord;
      int n = highlightText - highlightWord.text;
      if (n > 0) {
        DigSetter::Dig dig = highlightWord;
        dig.textLength = n;
        setter.measureDig(dig);
        setter.setDig(dig);
        highlightWord.where.x += dig.width;
        highlightWord.text += n;
        highlightWord.textLength -= n;
      }
      n = highlightWord.text + highlightWord.textLength - 
	(highlightText + nHighlight);
      if (n > 0) {
	highlightWord.textLength -= n;
      }
      setter.measureDig(highlightWord);
      setter.setDig(highlightWord, 1);
      if (n > 0) {
        DigSetter::Dig dig = highlightWord;
        dig.textLength = n;
        dig.text += highlightWord.textLength;
        setter.measureDig(highlightWord);
        dig.where.x += highlightWord.width;
        setter.setDig(dig);
      }
      //LOGV(aWord.where) LCV(aWord.width) LCV(aWord.forceBreak);
      if (first+1 < word.size()) {
	HelpWord &bWord = word[first+1];
	//LOGV(bWord.where) LCV(bWord.width) LCV(bWord.forceBreak);
	if (aWord.where.y == bWord.where.y && 
	    aWord.text + aWord.textLength < endHighlight) {
	  //LOGSECTION("BridgeHighlights");
	  cint where = aWord.where;
	  where.x += aWord.width;
	  where.y -= ascender;
	  int bWidth = bWord.where.x - where.x;
	  int aWidth = bWidth/2;
	  bWidth -= aWidth;
	  int height = ascender+descender;
	  //LOGV(aWidth) LCV(bWidth) LCV(height);
	  //LOGV(where);
	  DigSetter::Hole firstHalf(where, cint(aWidth, height),
				    aWord.styleIndex);
	  where.x += aWidth;
	  //LOGV(where);
	  DigSetter::Hole secondHalf(where, cint(bWidth, height),
				     bWord.styleIndex);
	  setter.reverse(firstHalf);
	  setter.reverse(secondHalf);
	}
      }
    }
    else {
      setter.setDig(aWord);
    }
    first++;
    //LOGV(first);
  }
  //LOGS("loop done");
  setter.closeEvent();
  painting = 0;
  return true;
}

DrawingArea &DrawingArea::refreshWords(char *p, int n) {
  LOGSECTION("DrawingArea::refreshWords");
  LOGV((int) p) LCV(n);
  if (p == 0 || n == 0) {
    return *this;
  }
  int first = 1;
  int middle;
  int last = nWords - 1;
  while (first < last) {
    middle = (first + last) /2;
    if (p < word[middle].text) {
      last = middle - 1;
    }
    else if (p < word[middle].text + word[middle].textLength) {
      first = middle;
      break;
    }
    else {
      first = middle + 1;
    }
  }
  char *end = p + nHighlight;
  last = first;
  DigSetter::Hole hole = setter.makeHole(word[first]);
  while (end > word[last].text + word[last].textLength) {
    last++;
    if (word[last].where.y != word[first].where.y) {
      setter.refresh(hole);
      hole = setter.makeHole(word[last]);
      LOGV(hole.where) LCV(hole.size);
    }
    else {
      hole.size.x = word[last].width + word[last].where.x - hole.where.x;
    }
  }
  setter.refresh(hole);
  LOGV(hole.where) LCV(hole.size);
  LOGV(first) LCV(middle) LCV(last);
  LOGV((int) word[first].text) LCV(word[first].textLength);
  LOGV((int) word[middle].text) LCV(word[middle].textLength);
  LOGV((int) word[last].text) LCV(word[last].textLength);
  return *this;
}

AgHelpView &AgHelpView::positionWords(char *p, int n) {
  LOGSECTION("AgHelpView::positionWords");
  if (p == 0 || n == 0) {
    return *this;
  }
  int first = 1;
  int middle;
  int last = dataArea.nWords - 1;
  while (first < last) {
    middle = (first + last) /2;
    if (p < dataArea.word[middle].text) {
      last = middle - 1;
    }
    else if (p < 
	     dataArea.word[middle].text + dataArea.word[middle].textLength) {
      first = middle;
      break;
    }
    else {
      first = middle + 1;
    }
  }
  char *end = p + dataArea.nHighlight;
  last = first;
  while (end > dataArea.word[last].text + dataArea.word[last].textLength) {
    last++;
  }
  LOGV(first) LCV(middle) LCV(last);
  HelpWord &firstWord = dataArea.word[first];
  IFLOG(HelpWord &middleWord = ) dataArea.word[middle];
  HelpWord &lastWord = dataArea.word[last];
  LOGV((int) firstWord.text) LCV(firstWord.textLength);
  LOGV((int) middleWord.text) LCV(middleWord.textLength);
  LOGV((int) lastWord.text) LCV(lastWord.textLength);
  IFont &font = dataArea.displayStyle[firstWord.styleIndex].font;
  int descender = font.maxDescender();
  int ascender  = font.maxAscender();
  int bottom = verticalScrollBar.scrollBoxPosition() + size().height();
  int top = verticalScrollBar.scrollBoxPosition();
  LOGV(top) LCV(bottom);
  LOGV(firstWord.where.y - ascender);
  LOGV(lastWord.where.y + descender);
  if (lastWord.where.y + descender > bottom) {
    int yPos = lastWord.where.y + descender - size().height();
    verticalScrollBar.moveScrollBoxTo(yPos);
    repositionWindow();
  }
  else if (firstWord.where.y - ascender < top) {
    int yPos = firstWord.where.y - ascender;
    verticalScrollBar.moveScrollBoxTo(yPos);
    repositionWindow();
  }
  return *this;
}

Boolean AgHelpView::findNext(AgString s) {
  LOGSECTION("AgHelpView::findNext");
  LOGV(s);
  searchProcess.setKey(s);

  char *start = dataArea.highlightText;
  if (start == 0) {
    start = dataArea.helpText.pointer();
  }
  else if (s.size() == dataArea.nHighlight
	   && strnicmp(s.pointer(), dataArea.highlightText, s.size()) == 0) {
    start++;
  }

  int length = dataArea.myTextLength - (start - dataArea.helpText.pointer());
  LOGV((int) start) LCV(length);
  if (length <= 0) {
    return 0;
  }
  char *p = searchProcess.scanForward(start, length);
  if (p == 0) {
    return 0;
  }
  char *oldHighlight = dataArea.highlightText;
  int oldCount = dataArea.nHighlight;
  dataArea.highlightText = p;
  dataArea.nHighlight = s.size();
  dataArea.refreshWords(oldHighlight, oldCount);
  dataArea.refreshWords(dataArea.highlightText, dataArea.nHighlight);
  positionWords(dataArea.highlightText, dataArea.nHighlight);
  return 1;
}

Boolean AgHelpView::findPrev(AgString s) {
  LOGSECTION("AgHelpView::findPrev");
  LOGV(s);
  searchProcess.setKey(s);

  char *start = dataArea.helpText.pointer();
  int length = dataArea.helpText.size();
  if (dataArea.highlightText) {
    length = dataArea.highlightText + dataArea.nHighlight - start;
  }

  if (dataArea.nHighlight) {
    if (s.size() == dataArea.nHighlight &&
	!strnicmp(s.pointer(), dataArea.highlightText, s.size())) {
      length--;
    }
  }
  char *p = searchProcess.scanReverse(start, length);
  if (p == 0) {
    return 0;
  }
  char *oldHighlight = dataArea.highlightText;
  int oldCount = dataArea.nHighlight;
  dataArea.highlightText = p;
  dataArea.nHighlight = s.size();
  dataArea.refreshWords(oldHighlight, oldCount);
  dataArea.refreshWords(dataArea.highlightText, dataArea.nHighlight);
  positionWords(dataArea.highlightText, dataArea.nHighlight);
  return 1;
}

Boolean DrawingArea::mouseMoved(IMouseEvent &event) {
  //LOGSECTION("DrawingArea::mouseMoved");
  IPoint pWhere(event.mousePosition());
  cint where(pWhere.x(), pWhere.y());
  //LOGV(where);
  //IFont &font = FontSpec::help;
  int first = 0;
  int last = nWords - 1;
  while (first < last) {
    int middle = (first + last) /2;
    if (setter.bottom(word[middle]) < where.y) {
      first = middle + 1;
    }
    else if (setter.top(word[middle]) > where.y) {
      last = middle - 1;
    }
    else if (word[middle].where.x + word[middle].width < where.x) {
      first = middle + 1;
    }
    else if (word[middle].where.x > where.x) {
      last = middle - 1;
    }
    else {
      first = middle;
      break;
    }
  }
  //LOGV(first);
  int haslink = 0;
  if (first < nWords && word[first].linktopic != "") {
    haslink = 1;
  }
  if (where.x < word[first].where.x
      && (first <= 0
          || setter.bottom(word[first-1]) < where.y)) {
    haslink = 0;
  }
  if (where.x > word[first].where.x + word[first].width
      && (first+1 >= nWords
          || setter.top(word[first+1]) > where.y)) {
    haslink = 0;
  }
  //LOGV(haslink);
  //IMousePointerEvent pointerEvent(event);
  if (haslink && !fingerCursorSet) {
    //LOGS("Set fingerCursor");
    frameWindow->setMousePointer(fingerCursor);
    //pointerEvent.setMousePointer(fingerCursor);
    //SetCursor(fingerCursor);
    fingerCursorSet = true;
  }
  else if (!haslink && fingerCursorSet) {
    //LOGS("Reset activeCursor");
    frameWindow->setMousePointer(ControlPanel::activeCursor);
    //pointerEvent.setMousePointer(ControlPanel::activeCursor);
    //SetCursor(ControlPanel::activeCursor);
    fingerCursorSet = false;
  }
  return true;
}

Boolean DrawingArea::mouseClicked(IMouseClickEvent &event) {
  LOGSECTION("DrawingArea::mouseClicked");
  if (event.mouseButton() == IMouseClickEvent::button2) {
    if (event.mouseAction() == IMouseClickEvent::down) {
      rightButtonState = buttonDown;
    }
    else if (event.mouseAction() == IMouseClickEvent::up) {
      rightButtonState = waitingForClick;
    }
    else if (event.mouseAction() == IMouseClickEvent::click) {
      rightButtonState = buttonIdle;
    }
    return false;
  }
  if (event.mouseButton() != IMouseClickEvent::button1) {
    return false;
  }
  if (ControlPanel::helpCursorSet) {
    if (event.mouseAction() == IMouseClickEvent::down) {
      ControlPanel::helpCursorSet = 0;
      ControlPanel::resetCursor();
    }
  }
  switch (event.mouseAction()) {
    case IMouseClickEvent::doubleClick:
    case IMouseClickEvent::click: {
      if (event.windowUnderPointer() != handle()) {
	return false;
      }
      IPoint pWhere(event.mousePosition());
      cint where(pWhere.x(), pWhere.y());
      //LOGV(where);
      //IFont &font = FontSpec::help;
      int first = 0;
      int last = nWords - 1;
      while (first < last) {
        int middle = (first + last) /2;
        //LOGV(first) LCV(middle) LCV(last);
        //LOGV(word[middle].where.x) LCV(word[middle].where.y);
        //LOGV(setter.top(word[middle])) LCV(setter.bottom(word[middle]));
        if (setter.bottom(word[middle]) < where.y) {
	  first = middle + 1;
	}
        else if (setter.top(word[middle]) > where.y) {
	  last = middle - 1;
	}
        else if (word[middle].where.x + word[middle].width < where.x) {
	  first = middle + 1;
	}
        else if (word[middle].where.x > where.x) {
	  last = middle - 1;
	}
        else {
          first = middle;
          break;
        }
      }
      AgString linktopic = "";
      //LOGV(first);
      for (;first < nWords; first++) {
        if (setter.top(word[first]) > where.y) {
	  return false;
	}
        //LOGV(word[first].where) LCV(word[first].link);
        int xr = word[first].where.x + word[first].width;
        //LOGV(word[first].width) LCV(xr);
        if (xr < where.x) {
          linktopic = word[first].linktopic;
          continue;
        }
        int xl = word[first].where.x;
        //LOGS("contact") LV(xr) LCV(xl) LCV(link) LCV(word[first].link);
        if ( where.x < xl && linktopic != word[first].linktopic) {
	  return false;
	}
        if (word[first].linktopic == "") {
	  return false;
	}
        break;
      }
      if (first >= nWords) {
	return false;
      }
      //LOGV(first) LCV(word[first].linktopic);

      word[first].linkTraversed = 1;
      linktopic = word[first].linktopic;
      traversedLinks.insert(linktopic);

      int i;

      int iMin = first, iMax = first;
      for (i = first; i >= 0 && word[i].linktopic == linktopic; i--) {
        word[i].styleIndex = HelpWord::usedStyle;
        iMin = i;
      }
      for (i = first + 1 ; i < nWords && word[i].linktopic == linktopic; i++) {
        iMax = i;
        word[i].styleIndex = HelpWord::usedStyle;
      }
      HelpWord &wmn = word[iMin];
      HelpWord &wmx = word[iMax];

      int minX = min(wmn.where.x,wmx.where.x);
      int maxX = max(wmn.where.x + wmn.width, wmx.where.x + wmx.width);
      int minY = min(setter.top(wmn), setter.top(wmx));
      int maxY = max(setter.bottom(wmn), setter.bottom(wmx));

      IRectangle r(IPoint(minX, minY), IPoint(maxX, maxY));
      //LOGV(r.asString());
      refresh(r);

      const char *newTopic = word[first].linktopic.pointer();
      //LOGV(newTopic);
      AgString title = AgString::format("Help - %s", newTopic);
      IFrameWindow *helpWindow = AgFrame::windowRegistry.find(title);
      if (helpWindow == 0) {
        helpWindow = new AgHelpWindow(newTopic);
        helpWindow->setAutoDeleteObject();
      }
      helpWindow->show().setFocus();

      refreshAction.performDeferred();
      return true;
    }
  }
  return false;
}

ISize DrawingArea::calcMinimumSize() const {
  LOGSECTION("DrawingArea::calcMinimumSize");
  unsigned char *p = (unsigned char *) helpText.pointer();
  IFont &font = FontSpec::help;
  int enWidth = font.avgCharWidth();
  int minWidth = font.minTextWidth((char *)p);
  LOGV(minWidth);
  LOGV(margin);
  LOGV(enWidth);

  IFont &titleFont = FontSpec::helpTitle;
  int width = titleFont.textWidth(topic.pointer());
  if (width > minWidth) {
    minWidth = width;
  }
  while (*p) {
    if (*p == ' ') {
      unsigned char *q = p;
      int k = 0;
      while (*q == ' ') {
	k++;
	q++;
      }
      p = q;
      int width = 0;
      while (*q && *q != '\n') {
	width += charWidth[*q++];
      }
      width += k*enWidth;
      if (width > minWidth) {
	minWidth = width;
      }
      p = ++q;
      continue;
    }
    while (*p && *p != '\n') {
      p++;
    }
    if (*p) {
      p++;
    }
  }
  minWidth += 2*margin;
  int height = windowFont.maxCharHeight() + windowFont.externalLeading();
  return ISize(minWidth, 5*height);
}

#define BULLET 7

DrawingArea &DrawingArea::measureText() {
  LOGSECTION("DrawingArea::measureText");
  IFont &textFont = FontSpec::help;
  IFont &titleFont = FontSpec::helpTitle;
  AgString auxText(helpText.pointer());


  AgStack<int> linkedWordStack;

  unsigned char *p = (unsigned char *) helpText.pointer();
  unsigned char *aux = (unsigned char *) auxText.pointer();
  *aux = 0;

  int lineHeight = textFont.maxCharHeight() + textFont.externalLeading();

  int spaceWidth = charWidth[' '];
  int whereX = paraIndent;
  int whereY = leading(titleFont);
  nWords = 0;
  word.discardData();

  //LOGFont(windowFont);
  //LOGV(margin);
  //LOGV(paraIndent);

  int titleWidth = titleFont.textWidth(topic.pointer());
  //LOGV((char *) titleFont.name()) LCV(titleFont.pointSize());
  //LOGV(titleWidth);

  whereX = (measure - titleWidth)/2;
  LOGV(whereX) LCV(whereY);

  word.push(HelpWord(topic.pointer(), strlen(topic.pointer()), 
		     cint(whereX, whereY), HelpWord::titleStyle));
  word[0].width = titleWidth;
  nWords = 1;
  whereY += titleFont.maxDescender();
  whereY += paraLeading;
  word[0].forceBreak = 1;

  whereX = paraIndent;

  int indentedLine = 0;
  int tabbedLine = 0;

  int linkIndex = 0;

  int highlightBegin = 0;
  int highlightEnd = 0;
  longestLine = 0;
  int enWidth = avgCharWidth();
  int emWidth = font().charWidth('M');
  LOGV(enWidth) LCV(emWidth);

  LOGV(whereY);
  //int indentLevel = 0;
  int forceBreak = 0;
  int bulletedLine = 0;
  while (*p) {
    if (*p == ' ') {
      int k = 0;
      while (*p == ' ') {
	p++;
	k++;
      }
      whereX = k*enWidth + margin;
      word[nWords-1].forceBreak = 1;
      indentedLine = 1;
      //indentLevel = k;
    }
    else if (*p == '\t') {
      p++;
      whereX = 2*emWidth + margin;
      word[nWords-1].forceBreak = 1;
      tabbedLine = 1;
      LOGS("tabbed Line");
    }
    else if (*p == BULLET) {
      whereX = emWidth + margin;
      if (nWords > 0) {
	word[nWords-1].forceBreak = 1;
      }
      bulletedLine = 1;
      LOGS("bulleted Line") LCV(whereX);
    }
    int wordLength = 0;
    unsigned char *beginWord = aux;
    unsigned char *q = beginWord;
    int dotFlag = 0;
    while (*p && *p != ' ' && *p != '\t' && *p != '\n') {
      if (*p == (unsigned char) 169) {
        //LOGV(nWords) LCV(whereX) LCV(whereY);
        highlightBegin++;
        p++;
        continue;
      }
      if (*p == (unsigned char) 170) {
        //LOGV(nWords) LCV(whereX) LCV(whereY);
        highlightEnd++;
        p++;
        continue;
      }
      //*q++ = *p++;

      *q++ = *p;
      if (*p == 0 || *p++ != '.') {
	continue;
      }
      if (strchr(" \r\n\t", *p) == 0) {
	dotFlag = 1;
      }
      break;
    }

    int nChars = q-beginWord;
    *q = 0;
    wordLength = windowFont.textWidth((char *) beginWord);
    LOGV(nWords) LCV(wordLength) LCV(nChars) LCV(beginWord);
    *q++ = ' ';
    aux = q;
    while (*p == ' ' || (*p == '\t' && !tabbedLine)) {
      p++;
    }
    //forceBreak = (indentedLine || tabbedLine)  && *p == '\n';
    //forceBreak = (bulletedLine || tabbedLine)  && *p == '\n';
    forceBreak = tabbedLine && *p == '\n';
    int endLine = *p == '\n';
    if (endLine) {
      p++;
    }
    if (*p == '\n') {                 // More than one newline?
      forceBreak = 1;                 // Yes
      while (*p == '\n') {
	p++;
      }
    }
    //LOGV(forceBreak) LCV(indentLevel) LCV(indentedLine) LCV(dotFlag);
    LOGV(whereX);
    if (!dotFlag &&
	!tabbedLine &&
	!indentedLine &&
	whereX + wordLength > measure - margin) {
      if (whereX > longestLine) longestLine = whereX;
      LOGV(longestLine);
      whereX = margin;
      whereY += lineHeight;
    }
    AgString temp((char *) beginWord, 10);
    LOGV(word.size()) LCV(temp.pointer()) LCV(nChars) LCV(whereX) LCV(whereY);
    HelpWord thisWord((char *) beginWord, nChars, cint(whereX, whereY));
    if (*beginWord == BULLET) {
      //whereX = margin + 2*emWidth;
      *beginWord = 183;
      LOGS("replaced bullet") LCV(whereX) LCV(emWidth);
    }
    else {
      whereX += wordLength;
      if (!dotFlag) {
	whereX += spaceWidth;
      }
      LOGV(whereX);
      if (tabbedLine && !endLine && *p == '\t') {
	p++;
      	int newX = margin;
      	while (newX < whereX) {
	  newX += 2*emWidth;
	}
	LOGV(newX);
      	whereX = newX;
	while (*p == '\t') {
	  whereX += 2*emWidth;
	  p++;
	}
      }
    }
    LOGV(whereX);
    thisWord.indent = indentedLine || tabbedLine;
    thisWord.bullet = !thisWord.indent && bulletedLine != 0;
    thisWord.forceBreak = forceBreak;
    thisWord.noBreak = dotFlag;
    thisWord.width = wordLength;
    LOGV(thisWord.bullet) LCV(thisWord.forceBreak) LCV(thisWord.indent);
    if (highlightBegin) {
      //LOGS("set up links");
      //LOGV(linkIndex);
      LOGV(links.size()) LCV(topic);
      thisWord.linktopic = links[linkIndex];
      //LOGV(thisWord.link);
      if (traversedLinks.includes(thisWord.linktopic)) {
	thisWord.linkTraversed = 1;
      }
      //LOGV(dict_str(help_dict, thisWord.link));
      //LOGV(thisWord.where);
      if (thisWord.linkTraversed) {
	thisWord.styleIndex = HelpWord::usedStyle;
      }
      else {
	thisWord.styleIndex = HelpWord::linkStyle;
      }
    }
    if (highlightEnd) {
      highlightBegin = highlightEnd = 0;
      linkIndex++;
    }
    if (forceBreak) {
      LOGS("forced break");
      if ((tabbedLine || indentedLine ) && whereX > longestLine) {
	longestLine = whereX;
	LOGV(longestLine);
      }
      whereX = indentedLine || tabbedLine ? margin : paraIndent;
      if ((tabbedLine || bulletedLine) && *p == ' ') {
	whereX = paraIndent;
	while (*p == ' ') p++;
      }
      //whereX = paraIndent;
      whereY += lineHeight;
      if (!indentedLine && !tabbedLine) {
	whereY += paraLeading;
      }
    }
    word.push(thisWord);
    linkedWordStack.push(nWords);
    nWords++;
    if (forceBreak) {
      indentedLine = tabbedLine = bulletedLine = 0;
    }
    //LOGV(nWords);
  }
  whereY += 2*lineHeight;
  longestLine += margin;
  LOGV(longestLine);
  nLines = whereY/lineHeight;
  //LOGV(nLines);
  //LOGV(nWords);
  spaceRequired = measure * whereY;
  linkedWords = AgArray<int>(linkedWordStack);
  *aux = 0;
  helpText = auxText;
  myTextLength = helpText.size();
  return *this;
}

DrawingArea &DrawingArea::remeasureText() {
  LOGSECTION("DrawingArea::remeasureText");
  //LOGV(measure);
  IFont &textFont = FontSpec::help;
  IFont &titleFont = FontSpec::helpTitle;
  int lineHeight = textFont.maxCharHeight() + textFont.externalLeading();
  //int emWidth = textFont.maxSize().width();
  int emWidth = textFont.charWidth('M');

  LOGV(emWidth);
  //int paraIndent = margin + emWidth;
  int paraIndent = margin;

  int spaceWidth = charWidth[' '];
  //LOGV(spaceWidth);
  cint where(paraIndent, paraLeading);
  int forceBreak = 0;
  int k = 0;
  HelpWord &title = word[0];
  int titleWidth = titleFont.textWidth(topic.pointer());
  //LOGV((char *) titleFont.name()) LCV(titleFont.pointSize());
  //LOGV(titleWidth);
  word[0].width = titleWidth;
  title.where.x = (measure - titleWidth)/2;
  title.where.y = leading(titleFont);
  {
    //IFont &f = displayStyle[title.styleIndex].font;
    //LOGV(f.name()) LCV(f.pointSize());
  }
  where.y += 3*leading(titleFont)/2;
  where.y += titleFont.maxDescender();
  word[1].where.y = where.y;
  int indentedLine = 0;
  LOGV(paraLeading) LCV(lineHeight);
  for (k = 1; k < nWords; k++) {
    HelpWord &textWord = word[k];
    int width = word[k].width;
    //int i = k;
    //while (i < nWords-1 && word[i++].noBreak) {
    //  width += word[i].width;
    //}
    char buf[100];
    strncpy(buf, textWord.text, textWord.textLength);
    buf[textWord.textLength] = 0;
    LOGV(k) LCV(textWord.indent) LCV(textWord.where) 
      LCV(*textWord.text) LCV(buf);
    LOGV(textWord.bullet);
    int rightIndent = textWord.bullet ? 2*emWidth : 0;
    if (forceBreak) {
      where.y += lineHeight;
      where.x = textWord.where.x;
      if (textWord.bullet) {
	where.x = margin + 2*emWidth;
      }
      //if (!textWord.indent && where.x > margin) {
      //  where.y += paraLeading;
      //}
      if (!textWord.indent) {
	where.y += paraLeading;
      }
      indentedLine = textWord.indent;
    }
    else if (!indentedLine &&
	     where.x + width > measure - margin - rightIndent) {
      where.x = margin;
      where.y += lineHeight;
      if (textWord.bullet) {
	where.x += 2*emWidth;
      }
    }
    textWord.where.y = where.y;

    //if (!forceBreak || !textWord.indent)
    if (!forceBreak && !textWord.indent) {
      textWord.where.x = where.x;
    }

    LOGV(where) LCV(forceBreak) LCV(indentedLine);
    if (textWord.linktopic != "") {
      //LOGV(textWord.linktopic);
      //LOGV(k) LV(where);
      //LOGV(where.x + textWord.width);
    }
    if (*textWord.text == (char) 183) {
      //where.x += emWidth;     //bullet
    } 
    else {
      where.x += textWord.width + spaceWidth;
    }
    forceBreak = textWord.forceBreak;
    AgString temp((char *)textWord.text, 10);
    LOGV(k) LCV(temp) LCV(textWord.where);
  }
  where.y += 2*lineHeight;

  nLines = where.y/lineHeight;
  //LOGV(nLines);
  return *this;
}

AgString DrawingArea::getHelpText(const char *topic) {
  LOGSECTION("getHelpText");
  const HelpTopic *ht = help_topic(topic);
  LOGV((int)ht);
  AgString ret = helptopic_gettext(ht);
  LOGV(ret);
  return ret;
}

AgArray<AgString> DrawingArea::findLinks(AgString msg) {
  char *t = msg.pointer();
  LOGSECTION("DrawingArea::findLinks");
  LOGV(topic);
  AgStack<AgString> stack;
  while(1) {
    t = strchr(t, 169);
    if (t != NULL && t[1] == '\'') {
      t = strchr(t+1, 169);
    }
    if (t == NULL) {
      break;
    }
    AgCharStack charStack;
    t++;
    while (1) {
      unsigned char c = *t++;
      switch (c) {
	//case '.':
        case '\n':
        case '\r':
        case ' ' :
        case '\t': {
          charStack.push(' ');
          while (1) {
            switch (*t) {
              case '\n':
              case '\r':
              case ' ' :
              case '\t': {
                t++;
                continue;
              }
              default: break;
            }
            break;
          }
          continue;
        }
        case 0:
        case 170:
	  break;
        default:
	  charStack.push(c);
	  continue;
      }
      break;
    }
    if (charStack.size() == 0) {
      continue;
    }
    AgString word = charStack.popString();
    LOGV(word.pointer());

    if (!help_topicexists(word.pointer())) {
      int n = word.size();
      int k = word[n-1];
      if (tolower(k) == 's') {
	word[n-1] = 0;
      }
    }
    if (!help_topicexists(word.pointer())) {
      LOGS("Dangling help link: ") LV(word);
    }

    // Canonicalize the name of the topic.
    const HelpTopic *ht = help_topic(word.pointer());
    word = helptopic_gettitle(ht);

    stack.push(word);
    LOGV(AgString(t, 10).pointer());
  }
  stack.push("Using Help");
  LOGV(stack.size());
  return AgArray<AgString>(stack);
}


static int OPTLINK linksortfunc(const void *av, const void *bv) {
  const AgString &as = *(const AgString *)av;
  const AgString &bs = *(const AgString *)bv;

  int r = stricmp(as.pointer(), bs.pointer());
  if (r==0) {
    r = strcmp(as.pointer(), bs.pointer());
  }
  return r;
}

AgArray<AgString> DrawingArea::sortLinks(AgArray<AgString> links) {
  LOGSECTION("DrawingArea::sortLinks");

  // This used to sort and uniq by inserting into an AgBalancedTree.
  // The problem (apart from overelaboration) is that this requires a
  // custom wrapper class to insert the desired sort function, and
  // thus its own template instantiation. Which is a hassle, and
  // fairly silly...

  int i, n = links.size();
  LOGV(n);
  AgArray<AgString> tmp(n);
  for (i=0; i<n; i++) {
    tmp[i] = links[i];
  }
  qsort(tmp.pointer(), tmp.size(), sizeof(AgString), linksortfunc);

  // Now uniq. Since AgArray isn't expandable, count first.
  int j, ct;

  for (i=ct=0; i<n; i++) {
    if (i==0 || tmp[i] != tmp[i-1]) {
      ct++;
    }
  }

  AgArray<AgString> result(ct);
  for (i=j=0; i<n; i++) {
    if (i==0 || tmp[i] != tmp[i-1]) {
      result[j++] = tmp[i];
    }
  }

  return result;
}

AgHelpView &AgHelpView::layout() {
  LOGSECTION("AgHelpView::layout");
  if (size().width() == 0 || layoutActive) {
    return *this;
  }
  layoutActive++;
  doLayout();
  setLayoutDistorted(0, IWindow::layoutChanged);
  layoutActive--;
  return *this;
}

AgHelpView::AgHelpView(IWindow *ownerWindow_,
		       AgString topic)
  : ICanvas(nextChildId(), ownerWindow_, ownerWindow_)
  , verticalScrollBar(nextChildId(), this, this, IRectangle(),
                      IScrollBar::vertical | IWindow::visible)
  , dataArea(this, topic)
  , vsbShowing(false)
  , layoutActive(0)
  , colorChange(this, onColorChange)
  , fontChange(this, onFontChange)
  , highlightIndex(0)
{
  LOGSECTION("AgHelpView::AgHelpView");
  windowId = id();
  LOGV(id());

  colorChange.attach(&ColorSpec::helpText);
  colorChange.attach(&ColorSpec::helpLink);
  colorChange.attach(&ColorSpec::helpUsedLink);

  fontChange.attach(&FontSpec::help);
  fontChange.attach(&FontSpec::helpTitle);


  setMinimumSize(calcMinimumSize());
  verticalScrollBar.setScrollableRange(IRange(0,100));
  verticalScrollBar.moveScrollBoxTo(0);

  IKeyboardHandler::handleEventsFor(this);
  IResizeHandler  ::handleEventsFor(this);
  IScrollHandler  ::handleEventsFor(this);

  dataArea.show();
  show().setFocus();
}

AgHelpView::~AgHelpView() {
  IKeyboardHandler::stopHandlingEventsFor(this);
  IResizeHandler  ::stopHandlingEventsFor(this);
  IScrollHandler  ::stopHandlingEventsFor(this);
}

Boolean AgHelpView::windowResize(IResizeEvent &event){
  setLayoutDistorted(IWindow::layoutChanged, 0);
  return false;
}

ISize AgHelpView::calcMinimumSize() const {
  int width = dataArea.minimumSize().width()
              + verticalScrollBar.minimumSize().width();
  return ISize(width, dataArea.minimumSize().height());
}

ISize AgHelpView::suggestSize() {
  LOGSECTION("AgHelpView::suggestSize");
  ISize preferredSize  = dataArea.preferredSize;
  int width = preferredSize.width();
  int nLines = preferredSize.height();
  if (nLines > defaultWindowHeight) {
    nLines = defaultWindowHeight;
    width += verticalScrollBar.minimumSize().width();
  }
  return ISize(width, nLines * lineHeight());
}

AgHelpView &AgHelpView::repositionWindow() {
  int y = -verticalScrollBar.scrollBoxPosition();
  dataArea.moveTo(IPoint(0,y));
  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.
 */

AgHelpView &AgHelpView::doLayout() {
  LOGSECTION("AgHelpView::doLayout");
  LOGV(id());

  ISize canvasSize = size();
  int canvasWidth  = canvasSize.width();
  int canvasHeight = canvasSize.height();

  LOGV(canvasWidth);
  LOGV(canvasHeight);

  int dataWidth    = canvasWidth;
  int dataHeight   = canvasHeight;
  //int dataY        = 0;

  LOGV(dataWidth);
  LOGV(dataHeight);



  int vsbWidth     = verticalScrollBar.minimumSize().width();

  int verticalPosition   = verticalScrollBar.scrollBoxPosition();

  Boolean vsb = dataWidth * dataHeight < dataArea.spaceRequired;

  dataArea.measure = dataWidth;
  if (vsb) {
    dataArea.measure -= vsbWidth+1;
  }
  dataArea.remeasureText();
  dataHeight = dataArea.nLines*lineHeight();
  if (dataHeight <= canvasHeight && vsb) {
    vsb = 0;
    dataArea.measure = dataWidth;
    dataArea.remeasureText();
    dataHeight = canvasHeight;
  }
  else if (dataHeight > canvasHeight && !vsb) {
    vsb = 1;
    dataArea.measure = dataWidth - vsbWidth+1;
    dataArea.remeasureText();
    dataHeight = dataArea.nLines*lineHeight();
  }
  else if (dataHeight <= canvasHeight) {
    dataHeight = canvasHeight;
  }
  dataArea.sizeTo(ISize(dataArea.measure, dataHeight));

  if (vsb && !vsbShowing) {
    verticalScrollBar.moveScrollBoxTo(0);
    verticalScrollBar.show();
    vsbShowing = true;
  }
  if (!vsb && vsbShowing) {
    verticalScrollBar.hide();
    vsbShowing = false;
    verticalScrollBar.moveScrollBoxTo(0);
  }

  if (vsbShowing) {
    dataWidth -= vsbWidth + 1;
    verticalScrollBar.moveTo(IPoint(dataWidth+1, 0));
    verticalScrollBar.sizeTo(ISize(vsbWidth, canvasHeight));
  }

  LOGV(dataArea.size().asString());
  LOGV(dataArea.parentSize().asString());

  LOGV(rect().asString());
  LOGV(dataArea.rect().asString());


  LOGV(dataArea.size().asString());

  verticalScrollBar.setScrollableRange(IRange(0, dataHeight-1));
  verticalScrollBar.setVisibleCount(canvasHeight);
  verticalPosition = min((int) verticalScrollBar.scrollBoxRange().upperBound(),
                         verticalPosition);

  verticalScrollBar.setMinScrollIncrement(lineHeight());

  verticalScrollBar.moveScrollBoxTo(verticalPosition);
  LOGV(verticalPosition);
  repositionWindow();
  return *this;
}

Boolean AgHelpView::lineDown(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  moveScrollBox(event);
  repositionWindow();
  return true;
}

Boolean AgHelpView::lineUp(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  moveScrollBox(event);
  repositionWindow();
  return true;
}

Boolean AgHelpView::pageDown(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  moveScrollBox(event);
  repositionWindow();
  return true;
}

Boolean AgHelpView::pageUp(IScrollEvent &event) {
  if (event.scrollBarWindow() != &verticalScrollBar) {
    return false;
  }
  moveScrollBox(event);
  repositionWindow();
  return true;
}

Boolean AgHelpView::scrollBoxTrack(IScrollEvent &event) {
  moveScrollBox(event);
  repositionWindow();
  return true;
}

Boolean AgHelpView::virtualKeyPress(IKeyboardEvent &event) {
  LOGSECTION("AgHelpView::Virtualkeypress");
  switch (event.virtualKey()) {
    case IKeyboardEvent::up: {
      int topLine = verticalScrollBar.scrollBoxPosition()
                  - verticalScrollBar.minScrollIncrement();
      verticalScrollBar.moveScrollBoxTo(topLine);
      repositionWindow();
      return true;
    }
    case IKeyboardEvent::down: {
      LOGSECTION("Cursor down one line");
      int topLine = verticalScrollBar.scrollBoxPosition()
                  + verticalScrollBar.minScrollIncrement();
      verticalScrollBar.moveScrollBoxTo(topLine);
      repositionWindow();
      return true;
    }
    case IKeyboardEvent::home: {
      if (!event.isCtrlDown()) {
        messageBeep();
        return true;
      }
    }
    case IKeyboardEvent::pageUp: {
      int topLine = verticalScrollBar.scrollBoxPosition();
      topLine -= verticalScrollBar.visibleCount();
      if (event.isCtrlDown()) {
	topLine = 0;
      }
      verticalScrollBar.moveScrollBoxTo(topLine);
      repositionWindow();
      return true;
    }
    case IKeyboardEvent::end: {
      if (!event.isCtrlDown()) {
        messageBeep();
        return true;
      }
    }
    case IKeyboardEvent::pageDown: {
      int topLine = verticalScrollBar.scrollBoxPosition();
      if (event.isCtrlDown()) {
	topLine = verticalScrollBar.scrollBoxRange().upperBound();
      }
      topLine += verticalScrollBar.visibleCount();
      verticalScrollBar.moveScrollBoxTo(topLine);
      repositionWindow();
      return true;
    }
  }
  return false;
}

Boolean DrawingArea::command(ICommandEvent &event) {
  LOGSECTION("DrawingArea::command");
  unsigned index = event.commandId();
  if (index >= sortedLinks.size()) {
    LOGS("bad index");
    messageBeep();
    return true;
  }
  char *topic = sortedLinks[index].pointer();
  LOGV(topic);
  AgString title = AgString::format("Help - %s", topic);
  IFrameWindow *helpWindow = AgFrame::windowRegistry.find(title);
  if (helpWindow) {
    helpWindow->setFocus();
    return true;
  }
  helpWindow = new AgHelpWindow(topic);
  helpWindow->setAutoDeleteObject();
  return true;
}

DrawingArea &DrawingArea::initPopUp() {
  LOGSECTION("DrawingArea::initPopUp");
  LOGV((int) &popUpMenu);
  int n = sortedLinks.size();
  int i;
  for (i = 0; i < n; i++) {
    AgString topic = sortedLinks[i];
    LOGV(i) LCV(topic);
    popUpMenu.addText(i, topic.pointer());
  }
  return *this;
}

Boolean DrawingArea::makePopUpMenu(IMenuEvent &event ) {
  LOGSECTION("DrawingArea::makePopUpMenu called");
  //if (helpShowing) return true;
  //AgHelpHandler::handleEventsFor(frameWindow);
  //AgHelpHandler::handleEventsFor(&popUpMenu);
  //helpShowing = true;
  IPoint where(0,0);
  AgHelpWindow *frame = (AgHelpWindow *) frameWindow;
  where.setY(frame->helpView.verticalScrollBar.scrollBoxPosition());
  if (rightButtonState == waitingForClick) {
    LOGV(event.mousePosition().asString());
    where = event.mousePosition();
  }
  //popUpMenu.show(event.mousePosition());
  popUpMenu.show(where);
  //AgHelpHandler::stopHandlingEventsFor(frameWindow);
  //AgHelpHandler::stopHandlingEventsFor(&popUpMenu);
  //helpShowing = false;

  return true;
}

Boolean DrawingArea::showHelp(IEvent &event) {
  LOGSECTION("DrawingArea::showHelp");
  AgString topic;
  LOGV((int) frameWindow);
  LOGV((int) this);
  LOGV((int) event.controlWindow());
  LOGV((int) event.dispatchingWindow());

  if (event.controlWindow() == frameWindow) {
    if (helpShowing) {
      return true;
    }
    helpShowing = true;
    //IPoint where = rect().minXMinY();
    IPoint where(0, 0);
    AgHelpWindow *frame = (AgHelpWindow *) frameWindow;
    where.setY(frame->helpView.verticalScrollBar.scrollBoxPosition());
    LOGV(where.asString());
    popUpMenu.show(where);
    helpShowing = false;
    return true;
  }
  return false;
}


AgHelpWindow::AgHelpWindow(AgString topic)
  : AgFrame(IFrameWindow::systemMenu
	    | IFrameWindow::maximizeButton
	    | IFrameWindow::sizingBorder)
  , helpView(this, topic)
{
  LOGSECTION("AgHelpWindow::AgHelpWindow");
  AgString titleString = 
    AgString::format("AnaGram Help - %s", topic.pointer());
  AgString simpleTitle = 
    AgString::format("Help - %s", topic.pointer());

  windowTitle.setObjectText(titleString.pointer());

  registerTitle(simpleTitle.pointer());
  copyTitleText = simpleTitle;

  LOGV(simpleTitle.pointer());
  setClient(&helpView);


  ISize minSize = frameRectFor(helpView.minimumSize()).size();
  setMinimumSize(minSize);

  LOGV(helpView.minimumSize().asString());
  LOGV(minSize.asString());
  ISize tableSize = helpView.suggestSize();

  int width = 56*font().avgCharWidth();
  int testWidth = tableSize.width();

  int titleWidth
    = windowTitle.displaySize(windowTitle.text()).width()
      + windowTitle.minimumSize().width();

  LOGV(windowTitle.text());
  LOGV(titleWidth);
  LOGV(testWidth);

  if (testWidth < titleWidth) {
    testWidth = titleWidth;
  }
  LOGV(width);
  LOGV(testWidth);

  if (testWidth < width/2) {
    width = width/2;
  }
  else if (testWidth > 2*width) {
    width = 2*width;
  }
  else {
    width = testWidth;
  }

  ISize clientSize(width, tableSize.height());
  IRectangle frameRect(frameRectFor(clientSize));
  LOGV(clientSize.asString());
  LOGV(frameRect.size().asString());
  sizeTo(frameRect.size());

  positionFrame();
  show();
}

AgHelpWindow::~AgHelpWindow() {}

Boolean AgHelpWindow::showHelp(AgString topic) {
  LOGSECTION("AgHelpWindow::showHelp");
  LOGV(topic);

  AgString windowTitle;

  if (topic.exists()) {
    windowTitle = AgString::format("Help - %s", topic.pointer());
  }
  if (!windowTitle.exists()) {
    return false;
  }

  LOGV(windowTitle);

  IFrameWindow *helpWindow = AgFrame::windowRegistry.find(windowTitle);
  LOGV((int) helpWindow);
  if (helpWindow == 0) {
    helpWindow = new AgHelpWindow(topic);
    helpWindow->setAutoDeleteObject();
  }
  helpWindow->show();
  helpWindow->setFocus();
  //BringWindowToTop(helpWindow->handle());
  return true;
}

Boolean AgHelpWindow::showHelpCentered(AgString topic) {
  LOGSECTION("AgHelpWindow::showHelp");
  AgString windowTitle;

  if (topic.exists()) {
    windowTitle = AgString::format("Help - %s", topic.pointer());
  }
  if (!windowTitle.exists()) {
    return false;
  }

  LOGV(topic.pointer());

  IFrameWindow *helpWindow = AgFrame::windowRegistry.find(windowTitle);
  LOGV((int) helpWindow);
  if (helpWindow == 0) {
    helpWindow = new AgHelpWindow(topic);
    helpWindow->setAutoDeleteObject();
  }
  IPoint where = (IPair) place(IWindow::desktopWindow()->size(),
			       helpWindow->size(), 11);
  helpWindow->moveTo(where);
  helpWindow->show().setFocus();
  return true;
}