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

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

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