diff anagram/vaclgui/helpview.cpp @ 0:13d2b8934445

Import AnaGram (near-)release tree into Mercurial.
author David A. Holland
date Sat, 22 Dec 2007 17:52:45 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/anagram/vaclgui/helpview.cpp	Sat Dec 22 17:52:45 2007 -0500
@@ -0,0 +1,1772 @@
+/*
+ * 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;
+}
+
+