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

Fix friend declaration for gcc10. XXX: did not check it against the IBM compiler, might end up needing XXX: to be conditional.
author David A. Holland
date Tue, 31 May 2022 00:59:42 -0400
parents 13d2b8934445
children
line wrap: on
line source

/*
 * AnaGram, A System for Syntax Directed Programming
 * Copyright 1997-2002 Parsifal Software. All Rights Reserved.
 * See the file COPYING for license and usage terms.
 *
 * gtview.cpp
 */

//#include <commctrl.h>
//#include <imsgbox.hpp>
#include <windows.h>

#include "agstring.h"
#include "arrays.h"
#include "ctrlpanel.hpp"
#include "data.h"
#include "dc.h"
#include "dspar.hpp"
#include "dvplug.hpp"
#include "gtview.hpp"
#include "helpview.hpp"
#include "minmax.h"
#include "myalloc.h"
#include "p.h"
#include "rule.h"
#include "tracedc.h"
#include "vaclgui-res.h"
#include "vaclgui.hpp"

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


#define RESET       "&Reset"
#define HELP        "&Help"
#define PROCEED     "&Proceed"
#define SINGLE_STEP "&Single step"

//static char *buttonLabel[] = {
//  RESET, HELP, PROCEED, SINGLE_STEP
//};

GTView::GTView(GTWindow *owner_, DcRef<FtParserDc> parserDc_)
  : ICanvas(nextChildId(), owner_, owner_, IRectangle(),
	    ICanvas::classDefaultStyle | IWindow::clipChildren)
  , parserDc(parserDc_)
  , parser(parserDc->parser)
  , stackViewData(new AgDataViewPlug(parserDc))
  , mainSplitter(nextChildId(), this, this,
                 IRectangle(), ISplitCanvas::horizontal | IWindow::visible)
  , tracePanels(nextChildId(), &mainSplitter, &mainSplitter)
  , leftPanel(nextChildId(), &tracePanels, &tracePanels)
  , stackView(&leftPanel)
  , stackTitle(nextChildId(), &leftPanel, &leftPanel)
  , rightPanel(nextChildId(), &tracePanels, &tracePanels)
  , tokenListView(&rightPanel)
  , tokenListTitle(nextChildId(), &rightPanel, &rightPanel)
  , bottomPanel(nextChildId(), &mainSplitter, &mainSplitter,
                IRectangle(), ISplitCanvas::vertical | visible)
  , reductionChoiceView(&bottomPanel)
  , ruleView(&bottomPanel)
  , focusControl(GTWindow::tokenTab)
  , activePanel(GTWindow::tokenTab)
  , frame(owner_)
  , dataColorChange(this, onColorChange)
  , fontChange(this, onFontChange)
  , leftButtonState(buttonIdle)
  , itemStack(parser.x1x_new())
{
  LOGSECTION("GTView::GTView");
  LOGV(id());

  LOGV((int) &stackView);
  LOGV((int) &stackView.dataArea);
  LOGV((int) &tokenListView);
  LOGV((int) &tokenListView.dataArea);

  setFont(tokenListView.dataArea.font());

  dataColorChange.attach(&ColorSpec::inactiveCursor);
  dataColorChange.attach(&ColorSpec::activeCursor);
  fontChange.attach(&FontSpec::columnHead);

  ruleView.setEnterAction(AgAction());
  stackView.helpTopic = "Token Stack";
  tokenListView.helpTopic = "Allowable Input";
  tokenListView.copyTitle = "Grammar Trace: Allowable Input";
  ruleView.copyTitle = "Grammar Trace: Rule Stack";
  reductionChoiceView.copyTitle = "Grammar Trace: Reduction Choices";
  stackView.copyTitle = "Grammar Trace: Parser Stack";

  stackView.init(stackViewData);
  parserDc->windowConnector = stackViewData;

  reductionMenu = new FtParserReductionDc(parser);
  AgDataViewPlug *connector = new AgDataViewPlug(reductionMenu);
  reductionMenu->windowConnector = connector;
  reductionChoiceView.init(connector);

  bottomPanel.setSplitWindowPercentage(&reductionChoiceView, 0);
  bottomPanel.setSplitWindowPercentage(&ruleView, 100);

  stackTitle
   . setText("Parser Stack")
   . setAlignment(IStaticText::centerCenter)
   . setBackgroundColor(ColorSpec::inactiveTitle.bg())
   . setForegroundColor(ColorSpec::inactiveTitle.fg())
   ;
  AgString tokenTitle = 
    AgString::format("Allowable input - State %d", parser.state.number);
  tokenListTitle
   . setText(tokenTitle.pointer())
   . setAlignment(IStaticText::centerCenter)
   . setBackgroundColor(ColorSpec::inactiveTitle.bg())
   . setForegroundColor(ColorSpec::inactiveTitle.fg())
   ;
  LOGS("titles set up");
  int width = stackTitle.minimumSize().width();
  int height = font().externalLeading() + font().maxSize().height();
  ISize minSize(width, 5*height);

  leftPanel
   . addToCell(&stackView, 1,1)
   . addToCell(&stackTitle, 1,2)
   . setColumnWidth(1,width,true)
   . setRowHeight(1,0,true)
   . setMinimumSize(minSize)
   ;
  width = tokenListTitle.minimumSize().width();
  minSize= ISize(width, 5*height);
  rightPanel
   . addToCell(&tokenListView, 1,1)
   . addToCell(&tokenListTitle,1,2)
   . setColumnWidth(1,width,true)
   . setRowHeight(1,0,true)
   . setMinimumSize(minSize)
   ;

  //dc_ref ruleWindow = parserDc->rule_stack(parser.stateStack.size());
  itemStack = parser.x1x_new();
  //dc_ref ruleWindow = new rule_stack_dc(itemStack, parser.stateStack.size(),
  //                                    parserDc->head_title);
  ruleControl = new rule_stack_dc(itemStack,parser.stateStack.size(), 
				  parserDc->head_title);
  //parser.ruleControl = ruleWindow;
  //ruleConnector = new AgDataViewPlug(ruleWindow);
  ruleConnector = new AgDataViewPlug(ruleControl);
  //ruleWindow->windowConnector = ruleConnector;
  ruleControl->windowConnector = ruleConnector;
  ruleView.init(ruleConnector);

  mainSplitter.setSplitWindowPercentage(&tracePanels, 55);
  mainSplitter.setSplitWindowPercentage(&bottomPanel, 45);
  tracePanels.setSplitWindowPercentage(&leftPanel, 25);
  tracePanels.setSplitWindowPercentage(&rightPanel, 75);

  AgFocusHandler::handleEventsFor(&stackView.dataArea);
  IMouseHandler::handleEventsFor(&stackTitle);
  AgFocusHandler::handleEventsFor(&tokenListView.dataArea);
  IMouseHandler::handleEventsFor(&tokenListTitle);
  AgFocusHandler::handleEventsFor(&reductionChoiceView.dataArea);
  AgFocusHandler::handleEventsFor(&ruleView.dataArea);
  //AgFocusHandler::handleEventsFor(&frame->comboBox);
  //AgFocusHandler::handleEventsFor(&frame->containerBar);
  IFocusHandler::handleEventsFor(&frame->comboBox);
  //IFocusHandler::handleEventsFor(&frame->containerBar);
  IPaintHandler::handleEventsFor(this);
  LOGS("all done");
}

GTView::~GTView() {
  AgFocusHandler::stopHandlingEventsFor(&stackView.dataArea);
  IMouseHandler::stopHandlingEventsFor(&stackTitle);
  AgFocusHandler::stopHandlingEventsFor(&tokenListView.dataArea);
  IMouseHandler::stopHandlingEventsFor(&tokenListTitle);
  AgFocusHandler::stopHandlingEventsFor(&reductionChoiceView.dataArea);
  AgFocusHandler::stopHandlingEventsFor(&ruleView.dataArea);
  //AgFocusHandler::stopHandlingEventsFor(&frame->comboBox);
  //AgFocusHandler::stopHandlingEventsFor(&frame->containerBar);
  IFocusHandler::stopHandlingEventsFor(&frame->comboBox);
  //IFocusHandler::stopHandlingEventsFor(&frame->containerBar);
  IPaintHandler::stopHandlingEventsFor(this);
}


Boolean GTView::paintWindow(IPaintEvent &event) {
  LOGSECTION("GTView::paintWindow");
  LOGV(event.rect().asString());
  event.clearBackground(IGUIColor::dialogBgnd);
  return false;
}

GTView &GTView::refreshRules(int rule) {
  LOGSECTION("GTView::refreshRules");
  LOGV(rule);
  delete_tsd(itemStack);
  itemStack = parser.x1x_new();
  //parser.ruleControl->parser_stack = parser.itemStack;
  ruleControl->parser_stack = itemStack;
  int k = itemStack->nt;
  int ln = parser.stateStack.size();
  LOGV(itemStack->nt);
  LOGV(parser.stateStack.size());
  //parser.ruleControl->des->d_size.y = k;
  ruleControl->des->d_size.y = k;
  //ruleWindow->des->d_size.y = k;
  ruleControl->des->d_size.y = k;
  LOGV(k);
  while (k--) {
    int sx, sn, fn, fx;
    xtxf(itemStack, k, &sx, &sn, &fn, &fx);
    int length = Rule(fn)->length();
    LOGV(length);
    if (sx == ln && rule == 0) {
      break;
    }
    if (rule && fn == rule && fx == 0 && sx+length >= ln) {
      break;
    }
  }
  k = itemStack->nt - 1 - k;
  LOGV(k);

  ruleView
    . reset()
    . setCursorLine(itemStack->nt-1)
    . setCursorLine(k).synchCursor(k)
    . refresh();
  frame->ruleViewLine = k;
  return *this;
}

Boolean GTView::mouseClicked(IMouseClickEvent &event) {
  LOGSECTION("GTView::mouseClicked");
  if (event.mouseAction() != IMouseClickEvent::down) {
    return false;
  }
  if (event.controlWindow() == &tokenListTitle) {
    LOGS("tokenList should get focus");
    if (!tokenListView.dataArea.hasFocus()) {
      tokenListView.dataArea.setFocus();
    }
    return false;
  }
  else if (event.controlWindow() == &stackTitle) {
    LOGS("stackView should get focus");
    if (!stackView.dataArea.hasFocus()) {
      stackView.dataArea.setFocus();
    }
    return false;
  }
  return false;
}


Boolean GTView::gotFocus(IControlEvent &event) {
  LOGSECTION("GTView::gotFocus(c)");
  return gotFocus((IEvent &)event);
}

Boolean GTView::gotFocus(IEvent &event) {
  LOGSECTION("GTView::gotFocus");
  LOGV(ruleView.windowId);
  if (event.controlWindow() == 0) {
    return false;
  }
#ifdef INCLUDE_LOGGING
  IWindowHandle handle = (void *) event.parameter1();
  IWindow *lastWindow;
  do {
    lastWindow = IWindow::windowWithHandle(handle);
    LOGV((int) lastWindow);
    handle = GetParent(handle);
  } while (lastWindow == 0 && handle != 0);
  LOGV((int) event.controlWindow()) LCV((int)event.dispatchingWindow());
#endif
  if (event.controlWindow() == &stackView.dataArea) {
    stackTitle.setBackgroundColor(ColorSpec::activeTitle.bg());
    stackTitle.setForegroundColor(ColorSpec::activeTitle.fg());
    stackTitle.refresh();
    focusControl = activePanel = GTWindow::stackTab;
  }
  else if (event.controlWindow() == &tokenListView.dataArea) {
    tokenListTitle.setBackgroundColor(ColorSpec::activeTitle.bg());
    tokenListTitle.setForegroundColor(ColorSpec::activeTitle.fg());
    tokenListTitle.refresh();
    focusControl = GTWindow::tokenTab;
  }
  else if (event.controlWindow() == &reductionChoiceView.dataArea) {
    focusControl = activePanel = GTWindow::choiceTab;
  }
  else if (event.controlWindow() == &ruleView.dataArea) {
    focusControl = activePanel = GTWindow::ruleTab;
  }
  else if (event.controlWindow() == &frame->comboBox) {
    focusControl = activePanel = GTWindow::comboTab;
  }
  else if (event.controlWindow() == &frame->containerBar) {
    focusControl = activePanel = GTWindow::comboTab;
  }
  else if (event.dispatchingWindow() == &frame->comboBox) {
    focusControl = activePanel = GTWindow::comboTab;
  }
  else if (event.dispatchingWindow() == &frame->containerBar) {
    focusControl = activePanel = GTWindow::comboTab;
  }
  else {
    return false;
  }

  // Can't field mouse clicks reliably on combobox, so we show
  // help using gotFocus().
  LOGV(leftButtonState);
  if (focusControl == GTWindow::comboTab
      && leftButtonState == buttonIdle
      && ControlPanel::helpCursorSet) {
    //ControlPanel::helpCursorSet = 0;
    //AgHelpWindow::showHelp("Text Entry");
    //ControlPanel::resetCursor();
    followUpPending = 1;
    defer(this, followUpFocusMsg);
    return true;
  }


  frame->stepButton.disableDefault();
  frame->resetButton.disableDefault();
  frame->helpButton.disableDefault();
  frame->proceedButton.enableDefault();
  LOGV(ruleView.windowId);
  LOGV(focusControl);
  return false;
}

void GTView::followUpFocusMsg() {
  if (!followUpPending) {
    return;
  }
  ControlPanel::helpCursorSet = 0;
  AgHelpWindow::showHelp("Text Entry");
  ControlPanel::resetCursor();
  followUpPending = 0;
}

Boolean GTView::lostFocus(IControlEvent &event) {
  LOGSECTION("GTView::lostFocus(c)");
  return lostFocus((IEvent &) event);
}

Boolean GTView::lostFocus(IEvent &event) {
  LOGSECTION("GTView::lostFocus");
  LOGV(focusControl);
#ifdef INCLUDE_LOGGING
  IWindowHandle handle = (void *) event.parameter1();
  IWindow *nextWindow;
  do {
    nextWindow = IWindow::windowWithHandle(handle);
    LOGV((int) nextWindow);
    handle = GetParent(handle);
  } while (nextWindow == 0 && handle != 0);

  LOGV((int) nextWindow);
  LOGV((int) event.controlWindow()) LCV((int)event.dispatchingWindow());
#endif
  if (event.controlWindow() == &stackView.dataArea) {
    stackTitle.setBackgroundColor(ColorSpec::inactiveTitle.bg());
    stackTitle.setForegroundColor(ColorSpec::inactiveTitle.fg());
    stackTitle.refresh();
  }
  if (event.controlWindow() == &tokenListView.dataArea) {
    tokenListTitle.setBackgroundColor(ColorSpec::inactiveTitle.bg());
    tokenListTitle.setForegroundColor(ColorSpec::inactiveTitle.fg());
    tokenListTitle.refresh();
  }
  LOGV(focusControl);
  return false;
}


Boolean GTWindow::virtualKeyPress(IKeyboardEvent &event) {
  LOGSECTION("GTWindow::virtualKeyPress");
  ok_ptr(this);
  int increment = 0;
  int &focusControl = canvas.focusControl;
  int &activePanel = canvas.activePanel;
  LOGV(focusControl);
  LOGV((int) event.controlWindow()) LCV((int) event.dispatchingWindow());
  LOGV(event.virtualKey());
  switch (event.virtualKey()) {
    case IKeyboardEvent::enter:
    case IKeyboardEvent::newLine: {
      //activePanel = focusControl;
      if (focusControl >= proceedTab) {
	tabControl[focusControl].action.performDeferred();
	IPushButton *button = (IPushButton *)tabControl[focusControl].window;
	button->disableDefault();
	button->unhighlight();
      }
      else {
	activePanel = focusControl;
	proceed();
      }
      return true;
    }
    case IKeyboardEvent::right:
    case IKeyboardEvent::down:
      if (focusControl < proceedTab) {
	return AgFrame::virtualKeyPress(event);
      }
      if (focusControl + 1 == nTabs) {
	increment = proceedTab - focusControl;
      }
      else {
	increment = 1;
      }
      break;
    case IKeyboardEvent::tab:
      increment = 1;
      break;
    case IKeyboardEvent::left:
    case IKeyboardEvent::up:
      if (focusControl < proceedTab) {
	return AgFrame::virtualKeyPress(event);
      }
      if (focusControl == proceedTab) {
	increment = nTabs - 1 - proceedTab;
      }
      else {
	increment = nTabs - 1;
      }
      break;
    case IKeyboardEvent::backTab:
      increment = nTabs - 1;
      break;
    default:
      return AgFrame::virtualKeyPress(event);
  }
  if (event.controlWindow() != event.dispatchingWindow()) {
    return true;
  }
  IPushButton *button;
  if (focusControl >= proceedTab) {
    button = (IPushButton *) tabControl[focusControl].window;
    button->unhighlight();
    button->disableDefault();
  }
  do {
    focusControl = (focusControl + increment) % nTabs;
  } while (!tabControl[focusControl].enabled);
  LOGV(focusControl) LCV(increment);
  if (focusControl >= proceedTab) {
    button = (IPushButton *) tabControl[focusControl].window;
  }
  else {
    button = &proceedButton;
  }
  button->enableDefault();
  setFocus();
  return true;
}

Boolean GTWindow::mouseClicked(IMouseClickEvent &event) {
  LOGSECTION("GTWindow::mouseClicked");
  GTView::ButtonState &lbs = canvas.leftButtonState;
  ok_ptr(this);
  LOGV(event.mouseButton()) LCV(event.mouseAction());
  if (event.mouseButton() != 0) {
    return false;
  }
  LOGV(lbs);
  if (event.mouseAction() == IMouseClickEvent::down) {
    lbs = GTView::buttonDown;
  }
  else if (event.mouseAction() == IMouseClickEvent::up) {
    lbs = GTView::waitingForClick;
  }
  else if (event.mouseAction() == IMouseClickEvent::click) {
    lbs = GTView::buttonIdle;
  }
  LOGV(lbs);
  int &focusControl = canvas.focusControl;
  int &activePanel = canvas.activePanel;
  LOGV(focusControl) LCV(activePanel);
  IWindow *controlWindow = event.controlWindow();
  LOGV((int)controlWindow) LCV((int)event.dispatchingWindow());
  if (event.controlWindow() == &statusField) {
    return false;
  }

  int tab;
  for (tab = 0; tab < nTabs; tab++) {
    LOGV(tab) LCV((int) tabControl[tab].window);
    if (controlWindow == tabControl[tab].window) {
      break;
    }
  }
  if (tab == nTabs) {
    return False;
  }

  LOGV(ControlPanel::helpCursorSet);
  if (ControlPanel::helpCursorSet) {
    if (event.mouseAction() == IMouseClickEvent::up) {
      ControlPanel::helpCursorSet = 0;
      canvas.followUpPending = 0;
      LOGV(tabControl[tab].helpTopic);
      AgHelpWindow::showHelp(tabControl[tab].helpTopic);
      ControlPanel::resetCursor();
    }
    return true;
  }
  LOGV(focusControl) LCV(activePanel);
  if (tab < proceedTab) {
    return false;
  }
  if (focusControl >= proceedTab) {
    IPushButton *button = (IPushButton *) tabControl[focusControl].window;
    button->unhighlight();
    button->disableDefault();
  }
  IPushButton *button = (IPushButton *) tabControl[tab].window;
  if (event.mouseAction() == IMouseClickEvent::down) {
    if (focusControl < proceedTab) activePanel = focusControl;
    button->highlight();
    button->setFocus();
    focusControl = tab;
    button->capturePointer();
    return true;
  }
  if (event.mouseAction() == IMouseClickEvent::up) {
    button->unhighlight();
    button->releasePointer();
    AgAction &action = tabControl[tab].action;
    IWindowHandle h = tabControl[tab].window->handle();
    if (h == event.windowUnderPointer()) {
      action.performDeferred();
    }
    else if (activePanel < proceedTab) {
      button->disableDefault();
      proceedButton.enableDefault();
      focusControl = activePanel;
      setFocus();
    }
    else {
      button->disableDefault();
      proceedButton.enableDefault();
      focusControl = proceedTab;
      setFocus();
    }
  }
  return true;
}

Boolean GTWindow::characterKeyPress(IKeyboardEvent &event) {
  LOGSECTION("GTWindow::characterKeyPress");
  ok_ptr(this);
  if (event.isCtrlDown()) {
    return true;
  }
  if (canvas.focusControl < proceedTab) {
    canvas.activePanel = canvas.focusControl;
  }
  char character = event.character();
  switch (character) {
    case 'p':
    case 'P':
      proceed();
      return true;
    case 's':
    case 'S':
      doStep();
      return true;
    case 'r':
    case 'R':
      resetParser();
      return true;
    case 'h':
    case 'H':
      AgHelpWindow::showHelp("Grammar Trace");
      helpButton.disableDefault();
      helpButton.unhighlight();
      stepButton.enableDefault();
      //canvas.focusControl = tokenTab;
      if (comboBox.text().length()) {
	canvas.focusControl = comboTab;
      }
      else {
	canvas.focusControl = tokenTab;
      }
      return true;
  }
  return false;
}

GTWindow &GTWindow::setFocus() {
  LOGSECTION("GTWindow::setFocus");
  //int protectHelp = ControlPanel::helpCursorSet;
  //ControlPanel::helpCursorSet = 0;
  ok_ptr(this);
  int &focusControl = canvas.focusControl;
  if (focusControl < proceedTab) {
    canvas.activePanel = focusControl;
  }
  LOGV(focusControl);
  LOGV(canvas.ruleView.windowId);
  assert(focusControl >= 0 && focusControl < nTabs);
  LOGV(canvas.ruleView.windowId);
  tabControl[focusControl].window->setFocus();
  LOGV(canvas.ruleView.windowId);
  IPushButton *button;
  for (int i = proceedTab; i < nTabs; i++) {
    button = (IPushButton *)tabControl[i].window;
    LOGV((int) button);
    button->disableDefault();
    button->unhighlight();
  }
  LOGV(canvas.ruleView.windowId);
  if (focusControl >= proceedTab) {
    button = (IPushButton *)tabControl[focusControl].window;
  }
  else {
    button = &proceedButton;
  }
  LOGV(canvas.ruleView.windowId);
  LOGV((int) button);
  button->enableDefault();
  LOGV(canvas.ruleView.windowId);
  //ControlPanel::helpCursorSet = protectHelp;
  LOGS("all done");
  return *this;
}

void GTWindow::init() {
  LOGSECTION("GTWindow::init");
  ok_ptr(this);
  tabControl[stackTab] = TabControl(&canvas.stackView.dataArea,
				    "Parser Stack Pane");
  tabControl[tokenTab] = TabControl(&canvas.tokenListView.dataArea,
				    "Allowable Input Pane");
  tabControl[choiceTab] = TabControl(&canvas.reductionChoiceView.dataArea,
				     "Reduction Choices Pane");
  tabControl[choiceTab].enabled = 0;
  tabControl[ruleTab] = TabControl(&canvas.ruleView.dataArea,
				   "Rule Stack Pane");
  tabControl[comboTab]
    = TabControl(&comboBox, "Text Entry Field",
		 actionObject(this, comboBoxEnter));
  tabControl[proceedTab]
    = TabControl(&proceedButton, "Proceed", actionObject(this, proceed));
  tabControl[stepTab]
    = TabControl(&stepButton, "Single Step", actionObject(this, doStep));
  tabControl[resetTab]
    = TabControl(&resetButton, "Reset", actionObject(this, resetParser));
  tabControl[helpTab]
    = TabControl(&helpButton, "Grammar Trace", actionObject(this, showHelp));
/*
  LOGV((int) &comboBox) LCV((int) &containerBar);
  new ControlHelpDemon(&containerBar, "Container Bar");
  new ControlHelpDemon(&toolBar, "Tool Bar");
*/
  //parser.displayControl = parserDc;
  setClient(&canvas);
  addExtension(&toolBar, IFrameWindow::belowClient, IFrameWindow::thickLine);

  IFont buttonFont = stepButton.font();
  int buttonWidth = buttonFont.textWidth(" Synch Parse ");
  LOGV(buttonWidth);
  IColor buttonTextColor = resetButton.foregroundColor();

  ISize minimum = stepButton.minimumSize();
  minimum.setWidth(buttonWidth);
  stepButton.setMinimumSize(minimum);
  statusField.setFont(buttonFont);
  LOGV(statusField.font().name());
  int statusFieldWidth = buttonFont.textWidth("MSelect reduction tokenM");
  statusField.setMinimumSize(ISize(statusFieldWidth,minimum.height()));
  LOGV(statusField.minimumSize().asString());
  statusField.setBackgroundColor(IGUIColor::dialogBgnd);
  statusField.setForegroundColor(buttonTextColor);

  ISize margin = toolBar.margin();
  margin.setHeight(0);
  toolBar.setMargin(margin);
  containerBar.setMargin(ISize());
  margin = buttonGroup.margin();
  margin.setWidth(0);
  margin.setHeight(margin.height()/2);
  buttonGroup.setMargin(margin);
  ISize pad = buttonGroup.pad();
  pad.setWidth(0);
  buttonGroup.setPad(pad);

  proceedButton.enableDefault();

  LOGV(proceedButton.minimumSize().asString());
  LOGV(stepButton.minimumSize().asString());
  LOGV(resetButton.minimumSize().asString());
  LOGV(helpButton.minimumSize().asString());

  int minWidth = toolBar.minimumSize().width();
  LOGV(parser.stateStack.size());
  tokenList = parserDc->tokenMenu(parser.stateStack.size());
  LOGV((int) (dc *)tokenList);
  tokenList->windowConnector = new AgDataViewPlug(tokenList);
  LOGV((int)tokenList->windowConnector);

  canvas.tokenListView
   . init(tokenList->windowConnector->windowData())
   . adjustMaxWidth("Token0\tAction\tToken Name")
   . setColumnTitles("Token\tAction\tToken Name")
   ;
  canvas.stackView
   . adjustMaxWidth(" 1\tState:\tToken0\tToken Name")
   . setColumnTitles("\tState\tToken\tToken Name")
   ;
  int loopControl = 2;
  ISize sizeLeft;
  ISize sizeRight;
  while (loopControl--) {
    int desktopWidth = IWindow::desktopWindow()->size().width();
    sizeLeft = canvas.stackView.suggestSize();
    if (sizeLeft.width() > desktopWidth/6) {
      sizeLeft.setWidth(desktopWidth/3);
    }
    sizeRight = canvas.tokenListView.suggestSize();
    if (sizeRight.width() > desktopWidth/2) {
      sizeRight.setWidth(desktopWidth/2);
    }
    int thickness = 
      2*canvas.tracePanels.splitBarThickness(ISplitCanvas::splitBarEdge);
    LOGV(canvas.tracePanels.splitBarThickness(ISplitCanvas::splitBarEdge));
    LOGV(canvas.tracePanels.splitBarThickness(ISplitCanvas::splitBarMiddle));
    LOGV(borderWidth());
    thickness += 
      canvas.tracePanels.splitBarThickness(ISplitCanvas::splitBarMiddle);
    thickness += 2*borderWidth();
    int width = sizeLeft.width() + sizeRight.width() + thickness;
    int height = max(6*sizeLeft.height(), sizeRight.height());
    LOGV(height);
    height =
      min(height,
	  3*AgDataView::defaultWindowHeight*canvas.stackView.lineHeight()/2);
    height += 2*canvas.stackView.columnHeadTitle.size().height();
    height += canvas.stackTitle.minimumSize().height();
    LOGV(height);
    if (width < minWidth) {
      width = minWidth;
    }
    if (width > (2*desktopWidth)/3) {
      width = (2*desktopWidth)/3;
    }
    int toolbarWidth = toolBar.minimumSize().width();
    if (width < toolbarWidth) {
      width = toolbarWidth;
    }
    ISize clientSize(width, height);
    LOGV(clientSize.asString());
    IRectangle frameRect(frameRectFor(clientSize));
    ISize frameSize = frameRect.size();
    LOGV(frameSize.asString());
    ISize resultant = clientRectFor(frameRect).size();
    LOGV(resultant.asString());
    sizeTo(frameRect.size());
    LOGV(sizeLeft.asString());
    LOGV(sizeRight.asString());
    setStatusField();
  }

  int left = canvas.stackView.tableWidth;
  int right = canvas.tokenListView.tableWidth;
  if (left > sizeLeft.width())  {
    left = sizeLeft.width();
  }
  if (right > sizeRight.width()) {
    right = sizeRight.width();
  }

  LOGV(left);
  LOGV(right);

  canvas.tracePanels.setSplitWindowPercentage(&canvas.leftPanel, left);
  canvas.tracePanels.setSplitWindowPercentage(&canvas.rightPanel, right);
  positionFrame();

  stackViewLine = parser.stateStack.size();
  canvas.stackView
   . setEnterAction(AgAction())
   . setSelectAction(actionObject(this, stackSelect))
   . setCursorLine(stackViewLine)
   ;
  canvas.ruleView
   . setSelectAction(actionObject(this, ruleSelect))
   . setEnterAction(AgAction())
   ;
  canvas.tokenListView
   //. setEnterAction(AgAction())
   . setEnterAction(actionObject(this, tokenEnter))
   . setSelectAction(actionObject(this, tokenSelect))
   ;
  canvas.reductionChoiceView
   . setEnterAction(actionObject(this, reductionChoiceEnter))
   . setSelectAction(actionObject(this, reductionChoiceSelect))
   ;

  listViewLine = canvas.tokenListView.getCursorLine();

  LOGV(listViewLine);
  stackViewLine = parser.stateStack.size();
  ruleViewLine = canvas.ruleView.getCursorLine();
  LOGV(canvas.ruleView.windowId);

  syntaxDependent = 1;
  LOGV(parser.stateStack.size());
  IShowListHandler::handleEventsFor(&comboBox);
  listViewLine = 
    tokenList->positionCursor(parser.state.number,parser.state.token);
  LOGV(canvas.ruleView.windowId);
  canvas.tokenListView.setCursorLine(listViewLine);
  LOGV(canvas.ruleView.windowId);
  synchRules(parser.stateStack.size(), parser.state.token);
  LOGV(canvas.ruleView.windowId);
  frameHandler->setDeactivateAction(actionObject(this, onDeactivate));
  LOGV(canvas.ruleView.windowId);
  AgFocusHandler::handleEventsFor(this);
  for (int i = 0; i < nTabs; i++) {
    IKeyboardHandler::handleEventsFor(tabControl[i].window);
    if (i == comboTab) {
      continue;
    }
    IMouseHandler::handleEventsFor(tabControl[i].window);
  }
  IMouseHandler::handleEventsFor(&statusField);
  IKeyboardHandler::handleEventsFor(&containerBar);
  LOGV(canvas.ruleView.windowId);
  show();
  LOGV(canvas.ruleView.windowId);
  //setFocus();
  //LOGS("Returned from setFocus");
  LOGV(canvas.ruleView.windowId);
  canvas.ruleView.setCursorLine(canvas.itemStack->nt - 1);
  refresh();
  LOGS("refresh call returned");
  canvas.ruleView.setCursorLine(ruleViewLine);
  canvas.tokenListView.dataArea.setFocus();
  LOGS("all done");
}

GTWindow::GTWindow(trace_window_dc* gtControl)
  : AgFrame(  IFrameWindow::dialogBackground
	    | IFrameWindow::systemMenu
	    | IFrameWindow::maximizeButton
	    | IFrameWindow::sizingBorder)
  , parser((gtControl == 0 ? 0 : gtControl->parser_stack))
  , parserDc(new FtParserDc(parser))
  , canvas(this, parserDc)
  , toolBar(this, ISetCanvas::packTight | ISetCanvas::centerVerticalAlign)
  , containerBar(&toolBar, ISetCanvas::packTight)
  , statusField(nextChildId(), &containerBar, &containerBar, IRectangle(),
		  IStaticText::defaultStyle()
		| IStaticText::center
		| IStaticText::vertCenter
		| IStaticText::border3D)
  , statusFieldHelp(&statusField, "Parse Status")
  , comboBox(&containerBar, "Now is the time for ")
  //, comboBoxHelp(&comboBox, "Grammar Trace Text Entry")
  , buttonGroup(&toolBar, ISetCanvas::packExpanded)
  , proceedButton(IDTB_PROCEED, &buttonGroup, PROCEED)
  , stepButton(IDTB_STEP, &buttonGroup, SINGLE_STEP)
  , resetButton(IDTB_RESET, &buttonGroup, RESET)
  , helpButton(IDTB_HELP, &buttonGroup, HELP)
  , comboBoxActive(0)
  , comboBoxListShowing(0)
  , ruleSelectActive(0)
  , selectedToken(0)
{
  LOGSECTION("GTWindow::GTWindow");
  ok_ptr(this);
  AgString title;
  if (gtControl == 0) {
    title = "Grammar Trace";
  }
  else {
    if (gtControl->foot_title.exists()) {
      title = AgString::format("%s (%s)",
			       gtControl->head_title.pointer(),
			       gtControl->foot_title.pointer());
    }
    else {
      title = gtControl->head_title;
    }
  }
  LOGV(title.pointer());
  AgString objectName = AgString::format("AnaGram : %s", 
					 simple_file_name.pointer());
  LOGV(objectName.pointer());
  windowTitle.setObjectText(objectName.pointer());
  windowTitle.setViewText(title.pointer());
  registerTitle(title.pointer());
  init();
}

GTWindow::GTWindow(tsd *initialStates, AgString headTitle, AgString footTitle)
  : AgFrame(  IFrameWindow::dialogBackground
	    | IFrameWindow::systemMenu
	    | IFrameWindow::maximizeButton
	    | IFrameWindow::sizingBorder)
  , parser(initialStates)
  , parserDc(new FtParserDc(parser))
  , canvas(this, parserDc)
  , toolBar(this, ISetCanvas::packTight | ISetCanvas::centerVerticalAlign)
  , containerBar(&toolBar, ISetCanvas::packTight)
  , statusField(nextChildId(), &containerBar, &containerBar, IRectangle(),
		  IStaticText::defaultStyle()
		| IStaticText::center
		| IStaticText::vertCenter
		| IStaticText::border3D)
  , statusFieldHelp(&statusField, "Parse Status")
  , comboBox(&containerBar, "Now is the time for ")
  //, comboBoxHelp(&comboBox, "Grammar Trace Text Entry")
  , buttonGroup(&toolBar, ISetCanvas::packExpanded)
  , proceedButton(IDTB_PROCEED, &buttonGroup, PROCEED)
  , stepButton(IDTB_STEP, &buttonGroup, SINGLE_STEP)
  , resetButton(IDTB_RESET, &buttonGroup, RESET)
  , helpButton(IDTB_HELP, &buttonGroup, HELP)
  , comboBoxActive(0)
  , comboBoxListShowing(0)
  , ruleSelectActive(0)
  , selectedToken(0)
{
  LOGSECTION("GTWindow::GTWindow");
  ok_ptr(this);

  AgString objectName = AgString::format("AnaGram : %s",
					 simple_file_name.pointer());
  LOGV(objectName.pointer());
  AgString title;
  if (footTitle.exists()) {
    title = AgString::format("%s (%s)",
			     headTitle.pointer(),
			     footTitle.pointer());
  }
  else {
    title = headTitle;
  }

  windowTitle.setObjectText(objectName.pointer());
  windowTitle.setViewText(title.pointer());
  registerTitle(title.pointer());
  init();
  comboBox.setEnterAction(actionObject(this, comboBoxEnter));
}

void GTWindow::setRuleViewCursor() {
  LOGSECTION("GTWindow::setRuleViewCursor");
  ok_ptr(this);
  LOGV(canvas.ruleView.cursorLine);
  canvas.ruleView.setCursorLine(canvas.itemStack->nt - 1);
  LOGV(canvas.ruleView.cursorLine);
  canvas.ruleView.setCursorLine(ruleViewLine);
  LOGV(canvas.ruleView.cursorLine);
}

void GTWindow::onDeactivate() {
  LOGSECTION("GTWindow::onDeactivate");
  ok_ptr(this);
  //if (comboBox.hasFocus()) canvas.focusControl = comboTab;
}


GTWindow::~GTWindow() {
  IShowListHandler::stopHandlingEventsFor(&comboBox);
  AgFocusHandler::stopHandlingEventsFor(this);
  for (int i = 0; i < nTabs; i++) {
    IKeyboardHandler::stopHandlingEventsFor(tabControl[i].window);
    if (i == comboTab) {
      continue;
    }
    IMouseHandler::stopHandlingEventsFor(tabControl[i].window);
  }
  IMouseHandler::stopHandlingEventsFor(&statusField);
  IKeyboardHandler::stopHandlingEventsFor(&containerBar);
}

AgString GTWindow::copyTitle() {
  ok_ptr(this);
  switch (canvas.focusControl) {
    case stackTab: return canvas.stackView.copyTitle;;
    case tokenTab: return canvas.tokenListView.copyTitle;;
    case choiceTab: return canvas.reductionChoiceView.copyTitle;;
    case ruleTab: return canvas.ruleView.copyTitle;;
  }
  return AgString();
}

GTWindow  &GTWindow::copyTo(IClipboard &c) {
  ok_ptr(this);
  switch (canvas.focusControl) {
    case stackTab: canvas.stackView.copyTo(c); break;
    case tokenTab: canvas.tokenListView.copyTo(c); break;
    case choiceTab: canvas.reductionChoiceView.copyTo(c); break;
    case ruleTab: canvas.ruleView.copyTo(c); break;
  }
  return *this;
}

/*
GTView &GTView::completeReduction() {
  LOGSECTION("GTView::completeReduction");
  reductionMenu->des->d_size.y = 0;
  bottomPanel.setSplitWindowPercentage(&reductionChoiceView, 0);
  bottomPanel.setSplitWindowPercentage(&ruleView, 100);
  bottomPanel.refresh();
  tabControl[comboTab].enabled = 0;
  frame->reductionChoiceLine = reductionChoiceView.getCursorLine();
  int token = ibnfs[ibnfb[parser.ruleToReduce]+frame->reductionChoiceLine];
  LOGV(token);
  parser.completeReduction(token);
  return *this;
}
*/

void GTWindow::reductionChoiceSelect() {
  LOGSECTION("GTWindow::reductionChoiceSelect");
  ok_ptr(this);
  int sn = parser.reductionState.number;
  reductionChoiceLine = canvas.reductionChoiceView.getCursorLine();
  int tn = canvas.reductionMenu->token(reductionChoiceLine);
  parser.reductionState.token= tn;
  LOGV(sn) LCV(tn);
  listViewLine = tokenList->positionCursor(sn,tn);
  canvas.tokenListView.setCursorLine(listViewLine);
  canvas.stackView.repaintLine(parser.stateStack.size());
}

void GTWindow::ruleSelect() {
  LOGSECTION("GTWindow::ruleSelect");
  ok_ptr(this);
  LOGV(listViewLine);
  LOGV(stackViewLine);
  LOGV(ruleViewLine);
  int tn = 0;
  int k = canvas.ruleView.getCursorLine();
  if (k == ruleViewLine) {
    return;
  }
  ruleViewLine = k;
  LOGV(k);
  k = canvas.itemStack->nt - k - 1;
  LOGV(k);
  int sx, sn, fn, fx;
  xtxf(canvas.itemStack, k, &sx, &sn, &fn, &fx);
  int length = Rule(fn)->length();
  LOGV(fn);
  LOGV(fx);
  LOGV(length);
  if (fx < length) {
    //tn = lstptr(map_form_number[fn],tokens)[fx];
    tn = Rule(fn).token(fx);
  }
  int stackDepth = parser.stateStack.size();
  int stackLevel = sx;
  LOGV(stackLevel) LCV(stackDepth);
  if (stackLevel == stackDepth && 
      parser.processState == FtParser::selectionRequired) {
    if (fx >= length) {
      return;
    }
    //int tn = lstptr(map_form_number[fn],tokens)[fx];
    int tn = Rule(fn).token(fx);
    LOGV(tn);
    int n = ibnfn[parser.ruleToReduce];
    while (n--) {
      LOGV(n);
      LOGV(ibnfs[ibnfb[parser.ruleToReduce]+n]);
      if (ibnfs[ibnfb[parser.ruleToReduce]+n] == tn) {
	break;
      }
    }
    LOGV(fn) LCV(n) LCV(tn);
    if (n < 0) {
      return;
    }
    canvas.reductionChoiceView.setCursorLine(n);
    parser.reductionState.token = tn;
    synchTokenList(sn, tn);
    canvas.stackView.repaintLine(stackDepth);
    return;
  }
  canvas.stackView.setCursorLine(stackLevel);
  stackViewLine = stackLevel;
  synchTokenList(sn, tn);
}

void GTWindow::setLookaheadToken(unsigned token) {
  LOGSECTION("GTWindow::setLookaheadToken");
  ok_ptr(this);
  LOGV(parser.stateStack.size());
  LOGV(canvas.stackView.cursorLine);
  parser.state.token = token;
  LOGV(token);
  int line = parser.stateStack.size();
  canvas.stackView.setCursorLine(line);
}


void GTWindow::synchRules(unsigned stackIndex, unsigned token) {
  LOGSECTION("GTWindow::synchRules");
  ok_ptr(this);
  LOGV(stackIndex) LCV(token);
  unsigned stackLocation = canvas.itemStack->nt;
  LOGV(stackLocation);
  int snx, sn, sx;
  if (stackIndex >= parser.stateStack.size()) {
    sn = parser.state.number;
  }
  else {
    sn = parser.stateStack[stackIndex].number;
  }
  state_number_map *sp = &map_state_number[sn];
  LOGV(stackLocation);
  while (stackLocation--) {
    int fn, fx;
    xtxf(canvas.itemStack,stackLocation, &sx, &snx, &fn, &fx);
    if (sx == stackIndex) {
      break;
    }
  }
  assert(sx == stackIndex);
  int k = stackLocation;
  LOGV(sn);
  LOGV(token);
  if (stackIndex >= parser.stateStack.size()) {
    ruleViewLine = canvas.itemStack->nt - stackLocation - 1;
    do {
      int fn, fx;
      xtxf(canvas.itemStack, k, &sx, &snx, &fn, &fx);
      if (fx < Rule(fn)->length()
          && token == Rule(fn).token(fx))
          //&& token == lstptr(map_form_number[fn],tokens)[fx])
      {
        k = canvas.itemStack->nt - k - 1;
        canvas.ruleView.setCursorLine(k).synchCursor(k);
        ruleViewLine = k;
        return;
      }
      unsigned *p = lstptr(*sp,reductions);
      unsigned n = sp->n_reductions;
      while (n--) {
        unsigned tn = *p++;
        unsigned rule = *p++;
        if (rule == fn && tn == token) {
          k = canvas.itemStack->nt - k - 1;
          canvas.ruleView.setCursorLine(k).synchCursor(k);
          ruleViewLine = k;
          return;
        }
      }
      k--;
    } while (k >= 0 && sx == stackIndex);
    canvas.ruleView.setCursorLine(ruleViewLine).synchCursor(ruleViewLine);
    return;
  }
  int nextState;
  if (stackIndex + 1 >= parser.stateStack.size()) {
    nextState = parser.state.number;
  }
  else {
    nextState = parser.stateStack[stackIndex+1].number;
  }
  int charToken = map_state_number[nextState].char_token;
  LOGV(nextState) LCV(charToken);
  do {
    int fn, fx;
    xtxf(canvas.itemStack, k, &sx, &snx, &fn, &fx);
    int length = Rule(fn)->length();
    LOGV(k) LCV(snx);
    LOGV(fn) LCV(fx) LCV(length);
    //if (sx == stackIndex && fx < length
    //    && token == lstptr(map_form_number[fn],tokens)[fx]) {
    if (fx < length
        && charToken == Rule(fn).token(fx)) {
        //&& charToken == lstptr(map_form_number[fn],tokens)[fx]) {
      k = canvas.itemStack->nt - k - 1;
      canvas.ruleView.setCursorLine(k).synchCursor(k);
      ruleViewLine = k;
      return;
    }
    k--;
    LOGV(k);
    LOGV(sn) LCV(snx);
  } while (k >= 0 && sx == stackIndex);
}

void GTWindow::setUpViews() {
  LOGSECTION("GTWindow::setUpViews");
  ok_ptr(this);
  unsigned stackDepth = parser.stateStack.size();
  int stackCursor = stackDepth;
  LOGV(tokenList->state_number);
  canvas.activePanel = canvas.focusControl;
  if (parser.processState == FtParser::selectionRequired) {
    LOGV(statusField.font().name());
    showReductionSelection();
    LOGV(statusField.font().name());
    stackCursor = parser.reductionIndex;
    canvas.focusControl = canvas.activePanel = choiceTab;
    tabControl[choiceTab].enabled = 1;
  }
  else if (parser.ruleToReduce) {
    parser.ruleToReduce = 0;
    canvas.reductionMenu->des->d_size.y = 0;
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView,0);
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 100);
    canvas.bottomPanel.refresh();
    tabControl[choiceTab].enabled = 0;
  }
  LOGV(tokenList->state_number);
  canvas.refreshRules(parser.ruleToReduce);
  LOGV(tokenList->state_number);
  IString text = comboBox.text();
  LOGV(parser.state.token);
  int sn = parser.state.number;
  int tn = 0;
  if (parser.processState != FtParser::ready) {
    tn = parser.state.token;
  }
  if (parser.processState == FtParser::selectionRequired) {
    LOGS("selection required");
    sn = parser.reductionState.number;
    tn = 
      canvas.reductionMenu->token(canvas.reductionChoiceView.getCursorLine());
    LOGV(sn) LCV(tn);
  }
  else if (parser.processState <= FtParser::running) {
    tn = parser.state.token;
    parser.processState = FtParser::ready;
  }
  else if (canvas.focusControl == comboTab) {
    LOGS("Text in the combobox");
    LOGV(text);
    if (text.length()) {
      parser.prime((const char*) text);
      tn = parser.state.token;
      LOGV(tn);
      state_number_map *sp = &map_state_number[sn];
      unsigned *tokenPointer = lstptr(*sp,t_actions);
      int k;
      for (k = 0; tokenPointer[k]; k++) {
	if (tokenPointer[k] == tn) {
	  break;
	}
      }
      LOGV(tn) LCV(k) LCV(tokenPointer[k]);
      tn = tokenPointer[k];
      int snx;
      k = canvas.ruleView.getCursorLine();
      LOGV(k) LCV(canvas.itemStack->nt);
      k = canvas.itemStack->nt - k - 1;
      LOGV(k) LCV(sn);
      if (tn) {
	do {
	  int sx, fn, fx;
	  xtxf(canvas.itemStack, k, &sx, &snx, &fn, &fx);
	  LOGV(k) LCV(sx) LCV(snx) LCV(fn) LCV(fx);;
	  int length = Rule(fn)->length();
	  LOGV(length);
	  if (sn == snx && fx < length) {
	    if (tn == Rule(fn).token(fx)) {
	      break;
	    }
	    //if (tn == lstptr(map_form_number[fn],tokens)[fx]) break;
	  }
	  if (k == 0) {
	    break;
	  }
	  k--;
	} while (sn == snx);
      }
      LOGV(sn) LCV(snx);
      if (tn && sn == snx) {
        k = canvas.itemStack->nt - k - 1;
        canvas.ruleView.setCursorLine(k).synchCursor(k);
        ruleViewLine = k;
      }
      else {
	tn == 0;
      }
    }
  }
  LOGV(tokenList->state_number);
  LOGV(parser.state.token);
  if (parser.processState == FtParser::running) {
    parser.processState = FtParser::ready;
  }
  LOGV(sn);
  LOGV(tn);

  canvas.stackView
   . reset()
   . setLayoutDistorted(IWindow::layoutChanged | IWindow::immediateUpdate, 0)
   . setCursorLine(stackCursor)
   . refresh()
   ;
  LOGV(tokenList->state_number);
  LOGV(parser.state.token);
  stackViewLine = stackCursor;
  LOGS("stackView set up");

  LOGS("new token menu created");
  LOGV(parser.state.number);
  tokenList->reset(parser.state.number);

  LOGV(parser.state.token);
  LOGV(tokenList->state_number);
  LOGS("new tokenList created");
  char buf[100];
  sprintf(buf, "Allowable input - State %d", parser.state.number);
  //AgString tokenTitle = AgString::format("Allowable input - State %d", 
  //                                     parser.state.number);
  //LOGV(tokenTitle.pointer());
  canvas.tokenListTitle
   //. setText(tokenTitle.pointer())
   . setText(buf)
   . refresh()
   ;
  LOGS("tokenListTitle set up");
  LOGV(tokenList->state_number);
  listViewLine = tokenList->positionCursor(sn, tn);
  canvas.tokenListView
   . reset()
   . setCursorLine(listViewLine)
   . setLayoutDistorted(IWindow::layoutChanged | IWindow::immediateUpdate, 0)
   . refresh()
   ;
  LOGV(parser.state.token);
  LOGS("tokenListView set up");
  LOGV(tokenList->state_number);
  LOGV(listViewLine);
  LOGV(stackViewLine);
  LOGV(ruleViewLine);
  LOGV(parser.location());
  setStatusField();
  LOGV(tokenList->state_number);
  setFocus();
  LOGV(tokenList->state_number);
}

void GTWindow::reductionChoiceEnter() {
  LOGSECTION("GTWindow::reductionChoiceEnter");
  ok_ptr(this);
  tabControl[choiceTab].enabled = 0;
  reductionChoiceLine = canvas.reductionChoiceView.getCursorLine();
  int tn = canvas.reductionMenu->token(reductionChoiceLine);
  parser.reductionState.token = tn;
  unsigned sn = parser.reductionState.number;
  LOGV(sn) LCV(tn);
  listViewLine = tokenList->positionCursor(sn,tn);
  canvas.tokenListView.setCursorLine(listViewLine);
  if (comboBox.text().length()) canvas.focusControl = comboTab;
  else canvas.focusControl = tokenTab;
  acceptToken();
  setFocus();
}

Boolean GTWindow::windowResize(IResizeEvent &event){
  LOGSECTION("GTWindow::windowResize");
  ok_ptr(this);
  if (event.controlWindow() != this) {
    return false;
  }
  LOGV(id());
  canvas.mainSplitter.sizeTo(
    clientRectFor(IRectangle(IPoint(), event.newSize())).size()
  );
  return false;
}

void GTWindow::synchTokenList(unsigned sn, unsigned tn) {
  LOGSECTION("GTWindow::synchTokenList");
  ok_ptr(this);
  AgString tokenTitle = AgString::format("Allowable input - State %d", sn);
  canvas.tokenListTitle.setText(tokenTitle.pointer());

  LOGV(sn);

  tokenList->reset(sn);

  listViewLine = tokenList->positionCursor(sn,tn);
  canvas.tokenListView
   . reset()
   . setCursorLine(listViewLine)
   . setLayoutDistorted(IWindow::layoutChanged | IWindow::immediateUpdate, 0)
   . refresh()
   ;
}

void GTWindow::stackSelect() {
  LOGSECTION("GTWindow::stackSelect");
  ok_ptr(this);
  int ln = canvas.stackView.getCursorLine();
  LOGV(listViewLine);
  LOGV(stackViewLine);
  LOGV(ruleViewLine);
  FtParser::State state =
    (ln >= parser.stateStack.size()) ? parser.state : parser.stateStack[ln];
  int sn = state.number, tn = state.token;
  synchTokenList(sn, tn);
  synchRules(ln, tn);
}

GTWindow &GTWindow::showReductionSelection() {
  LOGSECTION("GTWindow::showReductionSelection");
  ok_ptr(this);

  LOGV(statusField.font().name());

  canvas.reductionMenu = new FtParserReductionDc(parser);
  AgDataViewPlug *connector = new AgDataViewPlug(canvas.reductionMenu);
  canvas.reductionMenu->windowConnector = connector;

  canvas.reductionChoiceView.init(connector);

  LOGV(canvas.reductionMenu->columnHeadTitle.pointer());
  LOGV(connector->columnHeadTitle().pointer());

  canvas.reductionChoiceView.setEnterAction(actionObject(this, 
						       reductionChoiceEnter));

  ISize tableSize = canvas.reductionChoiceView.suggestSize();

  int width = 40*font().avgCharWidth();
  int testWidth = tableSize.width();
  if (testWidth < width/2) {
    width = width/2;
  }
  else if (testWidth > 2*width) {
    width = 2*width;
  }
  else {
    width = testWidth;
  }

  int rightWidth = canvas.bottomPanel.size().width();

  int thickness = 
    2*canvas.tracePanels.splitBarThickness(ISplitCanvas::splitBarEdge);
  thickness += 
    canvas.tracePanels.splitBarThickness(ISplitCanvas::splitBarMiddle);
  canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView, 
					      width);
  canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 
					      rightWidth - width - thickness);

  canvas.bottomPanel.refresh();
  LOGV(canvas.size().asString());
  reductionChoiceLine = parser.reductionSelection;
  canvas.reductionChoiceView
   . setCursorLine(reductionChoiceLine)
   . synchCursor(reductionChoiceLine)
   ;
  canvas.focusControl = choiceTab;
  setFocus();

  int sn = parser.reductionState.number;
  int tn = canvas.reductionMenu->token(0);
  listViewLine = tokenList->positionCursor(sn, tn);
  canvas.tokenListView.setCursorLine(listViewLine);

  return *this;
}

void GTWindow::comboBoxSelect() {
  LOGSECTION("GTWindow::comboBoxSelect");
  ok_ptr(this);
  IString text = comboBox.itemText(comboBox.selection());
  LOGV((char *) text);
  comboBox.setText(text);
}

void GTWindow::comboBoxEnter() {
  LOGSECTION("GTWindow::comboBoxEnter");
  ok_ptr(this);
  canvas.focusControl = comboTab;
  int selection = comboBox.selection();
  LOGV(selection);
  LOGV(IComboBox::notFound);
  LOGV(comboBox.isListShowing());
  LOGV(comboBox.hasChanged());
  if (comboBoxListShowing) {
    comboBoxListShowing = 0;
    LOGV(selection);
    if (selection == IComboBox::notFound) {
      return;
    }
    IString text = comboBox.itemText(selection);
    LOGV((char *) text);
    comboBox.setText(text);
    LOGV(comboBox.selection());
    return;
  }
  comboBoxProceed();
  proceedButton.unhighlight();
}

Boolean GTWindow::listShown(IControlEvent &) {
  LOGSECTION("GTWindow::listShown");
  ok_ptr(this);
  comboBoxListShowing = 1;
  return false;
}

void GTWindow::ComboBox::saveText() {
  LOGSECTION("GTWindow::comboBox::saveText()");
  IString contents = text();
  if (contents.length() == 0) {
    return;
  }
  int n = count();
  while (n--) {
    if (itemText(n) == contents) {
      remove(n);
      break;
    }
  }
  addAsFirst(contents);
  IEntryField::removeAll();
  n = count();
  LOGV(count());
  if (n < 8 && n > minimumRows()) {
    setMinimumRows(8);
  }
  LOGV(minimumRows());
}

void GTWindow::proceed() {
  LOGSECTION("GTWindow::proceed");
  ok_ptr(this);
  IString text = comboBox.text();
  LOGV(text);
  LOGV(canvas.focusControl);
  //if (comboBox.hasFocus()) {
  //  canvas.focusControl = canvas.activePanel = comboTab;
  //}
  LOGV(canvas.focusControl) LCV(canvas.activePanel);
  //switch(canvas.focusControl) {
  switch(canvas.activePanel) {
    case stackTab:
    case ruleTab: {
      messageBeep();
      setFocus();
      return;
    }
    case tokenTab: {
      tokenProceed();
      break;
    }
    case choiceTab: {
      reductionChoiceEnter();
      break;
    }
    case comboTab: {
      comboBoxProceed();
      break;
    }
    case proceedTab: {                   // proceed button has focus
      if (parser.processState == FtParser::selectionRequired) {
	reductionChoiceEnter();
      }
      else if (text.length() > 0) {
	comboBoxProceed();
      }
      else tokenProceed();
      break;
    }
    default:
      assert(0);
  }

  proceedButton.unhighlight();
  proceedButton.enableDefault();
  setFocus();
}

void GTWindow::doStep() {
  LOGSECTION("GTWindow::doStep");
  ok_ptr(this);
  IString text = comboBox.text();
  LOGV(text);

  LOGV(canvas.focusControl);
  //if (comboBox.hasFocus()) {
  //  canvas.focusControl = canvas.activePanel = comboTab;
  //}
  LOGV(canvas.focusControl) LCV(canvas.activePanel);
  //switch(canvas.focusControl) {
  switch(canvas.activePanel) {
    case stackTab:
    case ruleTab: {
      messageBeep();
      setFocus();
      return;
    }
    case tokenTab: {
      canvas.focusControl = tokenTab;
      acceptToken();
      break;
    }
    case choiceTab: {
      reductionChoiceEnter();
      break;
    }
    case comboTab: {
      comboBoxStep();
      break;
    }
    case stepTab: {                   // step button has focus
      if (parser.processState == FtParser::selectionRequired) {
	reductionChoiceEnter();
      }
      else if (text.length() > 0) {
	comboBoxStep();
      }
      else {
	canvas.focusControl = tokenTab;
	acceptToken();
      }
      break;
    }
    default:
      assert(0);
  }

  proceedButton.enableDefault();
  stepButton.unhighlight();
  stepButton.disableDefault();
  setFocus();
}


void GTWindow::comboBoxProceed() {
  LOGSECTION("GTWindow::comboBoxProceed");
  ok_ptr(this);
  canvas.focusControl = comboTab;
  if (parser.processState == FtParser::finished) {
    messageBeep();
    setFocus();
    return;
  }
  LOGV(comboBox.selectedTextLength());
  LOGV(comboBox.hasSelectedText());
  IString text = comboBox.text();
  LOGV((char *) text);
  if (text.length() == 0) {
    messageBeep();
    setFocus();
    return;
  }

  if (parser.processState == FtParser::selectionRequired) {
    parser.ruleToReduce = 0;
    canvas.reductionMenu->des->d_size.y = 0;
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView,0);
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 100);
    tabControl[choiceTab].enabled = 0;
    canvas.bottomPanel.refresh();
    parser.processState = FtParser::running;
  }

  unsigned stackLevel = canvas.stackView.getCursorLine();
  unsigned stackDepth = parser.stateStack.size();
  if (stackLevel != stackDepth) {
    LOGV(stackDepth);
    while (stackDepth > stackLevel) {
      parser.stateStack.pop(parser.state);
      stackDepth--;
    }
    parserDc->des->d_size.y = stackLevel+1;
  }

  parser.parse(text);
  if (parser.processState > FtParser::running) {
    messageBeep();
  }
  LOGV(comboBox.selectedTextLength());
  LOGV(comboBox.hasSelectedText());

  int k = (char *) text + strlen(text) - (char *) parser.state.pointer;
  comboBox.saveText();
  comboBox.setText((char *) parser.state.pointer);
  if (k > 0) {
    comboBox.setText((char *) parser.state.pointer);
  }

  if (parser.processState == FtParser::selectionRequired) {
    int k = parser.stateStack.size() - parser.reductionIndex;
    assert((unsigned) k <= (unsigned) parser.stateStack.size());
    LOGV(k);
    parser.stateStack.discardData(k);
    parser.state = parser.reductionState;
  }
  LOGV(parser.state.number);
  LOGV(parser.state.token);
  tokenList->reset(parser.state.number);
  stackDepth = parser.stateStack.size();
  //parser.displayControl->des->d_size.y = stackDepth+1;
  parserDc->des->d_size.y = stackDepth+1;
  LOGV(stackDepth);
  LOGV(parser.processState);
  LOGV(tokenList->state_number);

  setUpViews();
}

void GTWindow::comboBoxStep() {
  LOGSECTION("GTWindow::comboBoxStep");
  ok_ptr(this);
  LOGV(comboBox.selectedTextLength());
  LOGV(comboBox.hasSelectedText());
  IString text = comboBox.text();
  LOGV((char *) text);
  if (text.length() == 0 || parser.processState == FtParser::finished) {
    messageBeep();
    return;
  }

  if (parser.processState == FtParser::selectionRequired) {
    parser.ruleToReduce = 0;
    tabControl[choiceTab].enabled = 0;
    canvas.reductionMenu->des->d_size.y = 0;
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView,0);
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 100);
    canvas.bottomPanel.refresh();
    parser.processState = FtParser::running;
  }

  canvas.focusControl = comboTab;
  unsigned stackLevel = canvas.stackView.getCursorLine();
  unsigned stackDepth = parser.stateStack.size();
  if (stackLevel != stackDepth) {
    LOGV(stackDepth);
    while (stackDepth > stackLevel) {
      parser.stateStack.pop(parser.state);
      stackDepth--;
    }
    parserDc->des->d_size.y = stackLevel+1;
  }

  LOGV((int)(char *) text);
  LOGV(parser.state.pointer);

  parser.processState = FtParser::running;
  parser.step(text);
  if (parser.processState > FtParser::running) {
    messageBeep();
  }

  LOGV(comboBox.selectedTextLength());
  LOGV(comboBox.hasSelectedText());
  int k = (char *) text + strlen(text) - (char *) parser.state.pointer;
  LOGV(k);
  comboBox.saveText();
  if (k > 0) {
    comboBox.setText((char *) parser.state.pointer);
  }

  if (parser.processState == FtParser::selectionRequired) {
    int k = parser.stateStack.size() - parser.reductionIndex;
    assert((unsigned) k <= (unsigned) parser.stateStack.size());
    LOGV(k);
    parser.stateStack.discardData(k);
    parser.state = parser.reductionState;
  }
  LOGV(parser.state.number);
  LOGV(parser.state.token);
  tokenList->reset(parser.state.number);
  stackDepth = parser.stateStack.size();
  //parser.displayControl->des->d_size.y = stackDepth+1;
  parserDc->des->d_size.y = stackDepth+1;
  LOGV(stackDepth);
  LOGV(parser.processState);
  LOGV(tokenList->state_number);

  setUpViews();
}

void GTWindow::tokenSelect() {
  LOGSECTION("GTWindow::tokenSelect");
  ok_ptr(this);
  LOGV(canvas.tokenListView.getCursorLine());

  int line = canvas.tokenListView.getCursorLine();
  LOGV(line);
  LOGV(listViewLine);
  LOGV(stackViewLine);
  LOGV(ruleViewLine);

  int stackLine = canvas.stackView.getCursorLine();
  LOGV(stackLine);
  if (stackLine == stackViewLine && line == listViewLine) {
    return;
  }
  listViewLine = line;
  int stackDepth = parser.stateStack.size();
  LOGV(stackDepth);
  int sn;
  if (stackLine >= stackDepth) {
    sn = parser.state.number;
  }
  else {
    sn = parser.stateStack[stackLine].number;
  }
  LOGV(sn);
  state_number_map *sp = &map_state_number[sn];

  unsigned token = lstptr(*sp,t_actions)[line];
  LOGV(token);
  if (parser.processState == FtParser::selectionRequired) {
    int n = ibnfn[parser.ruleToReduce];
    while (n--) {
      if (token == ibnfs[ibnfb[parser.ruleToReduce]+n]) {
        canvas.reductionChoiceView.setCursorLine(n);
        reductionChoiceLine = n;
        break;
      }
    }
  }
}

void GTWindow::acceptToken() {
  LOGSECTION("GTWindow::acceptToken");
  ok_ptr(this);
  LOGV(canvas.tokenListView.getCursorLine());
  LOGV(listViewLine);
  LOGV(stackViewLine);
  LOGV(ruleViewLine);

  canvas.focusControl = tokenTab;
  if (parser.processState == FtParser::finished) {
    messageBeep();
    setFocus();
    return;
  }
  int line = canvas.tokenListView.getCursorLine();
  LOGV(tokenList->state_number);


  int stackLevel = canvas.stackView.getCursorLine();
  int stackDepth = parser.stateStack.size();
  if (stackLevel != stackDepth) {
    LOGV(stackDepth);
    while (stackDepth > stackLevel) {
      parser.stateStack.pop(parser.state);
      stackDepth--;
    }
    parserDc->des->d_size.y = stackLevel+1;
  }

  LOGV(parser.state.token);

  if (parser.processState == FtParser::selectionRequired) {
    parser.ruleToReduce = 0;
    tabControl[choiceTab].enabled = 0;
    canvas.reductionMenu->des->d_size.y = 0;
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView,0);
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 100);
    canvas.bottomPanel.refresh();
    parser.processState = FtParser::running;
  }

  state_number_map *sp = &map_state_number[parser.state.number];
  unsigned token = lstptr(*sp, t_actions)[line];

  LOGV(canvas.tokenListView.cursorLine);
  AgString lineText = canvas.tokenListView.windowData->
    getLine(canvas.tokenListView.cursorLine);
  LOGV(lineText.pointer());
  if (lineText == "No Default") {
    messageBeep();
    setFocus();
    return;
  }

  LOGV(parser.state.token);
  parser.stepToken(token);

  if (parser.processState > FtParser::running) {
    messageBeep();
  }

  if (parser.processState == FtParser::selectionRequired) {
    int k = parser.stateStack.size() - parser.reductionIndex;
    assert((unsigned) k <= (unsigned) parser.stateStack.size());
    LOGV(k);
    parser.stateStack.discardData(k);
    parser.state = parser.reductionState;
    unsigned stackDepth = parser.stateStack.size();
    //parser.displayControl->des->d_size.y = stackDepth+1;
    parserDc->des->d_size.y = stackDepth+1;
  }
  LOGV(parser.state.number);
  LOGV(parser.state.token);
  tokenList->reset(parser.state.number);
  stackDepth = parser.stateStack.size();
  //parser.displayControl->des->d_size.y = stackDepth+1;
  parserDc->des->d_size.y = stackDepth+1;

  LOGV(stackDepth);
  LOGV(parser.processState);
  LOGV(tokenList->state_number);
  setUpViews();
  LOGV(tokenList->state_number);
  AgFrame::windowRegistry.refresh("Trace Coverage");
}

void GTWindow::tokenEnter() {
  LOGSECTION("GTWindow::tokenEnter");
  ok_ptr(this);
  //acceptToken();
  //comboBox.saveText();
  tokenProceed();
}


void GTWindow::tokenProceed() {
  LOGSECTION("GTWindow::tokenProceed");
  ok_ptr(this);
  LOGV(canvas.tokenListView.getCursorLine());
  LOGV(listViewLine);
  LOGV(stackViewLine);
  LOGV(ruleViewLine);
  canvas.focusControl = tokenTab;
  if (parser.processState == FtParser::finished) {
    messageBeep();
    setFocus();
    return;
  }
  int line = canvas.tokenListView.getCursorLine();
  LOGV(tokenList->state_number);


  int stackLevel = canvas.stackView.getCursorLine();
  int stackDepth = parser.stateStack.size();
  if (stackLevel != stackDepth) {
    LOGV(stackDepth);
    while (stackDepth > stackLevel) {
      parser.stateStack.pop(parser.state);
      stackDepth--;
    }
    parserDc->des->d_size.y = stackLevel+1;
  }

  LOGV(parser.state.token);

  if (parser.processState == FtParser::selectionRequired) {
    parser.ruleToReduce = 0;
    tabControl[choiceTab].enabled = 0;
    canvas.reductionMenu->des->d_size.y = 0;
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView,0);
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 100);
    canvas.bottomPanel.refresh();
    parser.processState = FtParser::running;
  }

  state_number_map *sp = &map_state_number[parser.state.number];
  unsigned token = lstptr(*sp,t_actions)[line];

  LOGV(canvas.tokenListView.cursorLine);
  AgString lineText = canvas.tokenListView.windowData->
    getLine(canvas.tokenListView.cursorLine);
  LOGV(lineText.pointer());
  if (lineText == "No Default") {
    messageBeep();
    setFocus();
    return;
  }

  LOGV(parser.state.token);
  if (token) {
    parser.parseToken(token);
  }
  else {
    parser.stepToken(token);
  }

  if (parser.processState > FtParser::running) {
    messageBeep();
  }

  if (parser.processState == FtParser::selectionRequired) {
    int k = parser.stateStack.size() - parser.reductionIndex;
    assert((unsigned) k <= (unsigned) parser.stateStack.size());
    LOGV(k);
    parser.stateStack.discardData(k);
    parser.state = parser.reductionState;
    unsigned stackDepth = parser.stateStack.size();
    //parser.displayControl->des->d_size.y = stackDepth+1;
    parserDc->des->d_size.y = stackDepth+1;
  }
  LOGV(parser.state.number);
  LOGV(parser.state.token);
  tokenList->reset(parser.state.number);
  stackDepth = parser.stateStack.size();
  //parser.displayControl->des->d_size.y = stackDepth+1;
  parserDc->des->d_size.y = stackDepth+1;

  //comboBox.saveText();

  LOGV(stackDepth);
  LOGV(parser.processState);
  LOGV(tokenList->state_number);
  setUpViews();
  LOGV(tokenList->state_number);
  AgFrame::windowRegistry.refresh("Trace Coverage");
}

Boolean GTWindow::findNext(AgString s) {
  LOGSECTION("GTWindow::findNext");
  ok_ptr(this);
  LOGV(canvas.focusControl);
  int flag;
  switch (canvas.focusControl) {
    case stackTab: {
      flag = canvas.stackView.findNext(s);
      if (flag) {
	stackSelect();
      }
      return flag;
    }
    case tokenTab: {
      flag = canvas.tokenListView.findNext(s);
      if (flag) {
	tokenSelect();
      }
      return flag;
    }
    case choiceTab: {
      flag = canvas.reductionChoiceView.findNext(s);
      if (flag) {
	reductionChoiceSelect();
      }
      return flag;
    }
    case ruleTab: {
      flag = canvas.ruleView.findNext(s);
      if (flag) {
	ruleSelect();
      }
      return flag;
    }
    default:
      messageBeep();
      return false;
  }
}

Boolean GTWindow::findPrev(AgString s) {
  LOGSECTION("GTWindow::findPrev");
  ok_ptr(this);
  LOGV(canvas.focusControl);
  int flag;
  switch (canvas.focusControl) {
    case stackTab: {
      flag = canvas.stackView.findPrev(s);
      if (flag) {
	stackSelect();
      }
      return flag;
    }
    case tokenTab: {
      flag = canvas.tokenListView.findPrev(s);
      if (flag) {
	tokenSelect();
      }
      return flag;
    }
    case choiceTab: {
      flag = canvas.reductionChoiceView.findPrev(s);
      if (flag) {
	reductionChoiceSelect();
      }
      return flag;
    }
    case ruleTab: {
      flag = canvas.ruleView.findPrev(s);
      if (flag) {
	ruleSelect();
      }
      return flag;
    }
    default:
      messageBeep();
      return false;
  }
}


char *GTWindow::processStateText[] = {
  "Ready",
  "Running",                                   //running,
  "Parse Complete",                          //finished,
  "Syntax Error",           //syntaxError,
  "Unexpected End of File", //unexpectedEndOfFile,
  "Select Reduction Token",  //selectionRequired
  "Selection Error"
};

GTWindow &GTWindow::setStatusField() {
  ok_ptr(this);
  char *msg = processStateText[parser.processState];
  statusField.setText(msg);
  statusField.refresh();
  AgString tokenTitle = AgString::format("Allowable input - State %d", 
					 parser.state.number);
  canvas.tokenListTitle.setText(tokenTitle.pointer());
  return *this;
}

void GTWindow::resetParser() {
  LOGSECTION("GTWindow::resetParser");
  ok_ptr(this);
  if (parser.ruleToReduce) {
    parser.ruleToReduce = 0;
    canvas.reductionMenu->des->d_size.y = 0;
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.reductionChoiceView,0);
    canvas.bottomPanel.setSplitWindowPercentage(&canvas.ruleView, 100);
    canvas.bottomPanel.refresh();
  }
  tabControl[choiceTab].enabled = 0;
  comboBox.saveText();
  parser.reset();
  LOGV(parser.state.token);
  LOGV(parser.processState);
  int stackDepth = parser.stateStack.size();
  parserDc->des->d_size.y = stackDepth + 1;
  canvas.stackView.setCursorLine(stackDepth);
  tokenList->reset(parser.state.number);
  LOGV(parser.state.token);
  canvas.tokenListView.reset();
  LOGV(parser.state.token);
  LOGV(parser.state.token);
  LOGV(parser.processState);
  listViewLine
    = tokenList->positionCursor(parser.state.number, parser.state.token);
  canvas.tokenListView.setCursorLine(listViewLine);
  LOGV(parser.state.token);
  canvas.refreshRules(0);
  synchRules(parser.stateStack.size(), parser.state.token);
  LOGV(parser.processState);

//  if (comboBox.text().length()) canvas.focusControl = comboTab;
//  else canvas.focusControl = tokenTab;
  canvas.focusControl = tokenTab;
  setFocus();
  canvas.stackView.refresh();
  canvas.tokenListView.refresh();
  LOGV(parser.processState);
  setStatusField();
  resetButton.unhighlight();
  resetButton.disableDefault();
  proceedButton.enableDefault();
}

void GTWindow::showHelp() {
  ok_ptr(this);
  AgHelpWindow::showHelp("Grammar Trace");
  helpButton.unhighlight();
  helpButton.disableDefault();
  proceedButton.enableDefault();
  if (comboBox.text().length()) {
    canvas.focusControl = comboTab;
  }
  else {
    canvas.focusControl = tokenTab;
  }
}