Blob Blame History Raw
/* Copyright (C) 2002 Bjoern Beutel. */

/* Description. =============================================================*/

/* Read in and display Malaga Variables. */

/* Includes. ================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>
#include <gtk/gtk.h>
#include "basic.h"
#include "scanner.h"
#include "input.h"
#include "canvas.h"
#include "tree.h"

/* Constants. ===============================================================*/

enum {FIRST_STATE, NEXT_STATE, PREV_STATE, LAST_STATE};

enum {PATH_BEGIN, PATH_END, FROM_ROOT}; /* Items of the tree's pop-up menu. */

/* Types. ===================================================================*/

typedef enum {INTER_NODE, BREAK_NODE, FINAL_NODE, UNFINAL_NODE,
	      PRUNED_NODE} tree_node_type_t;

typedef enum {ELEMENT_NODE, ELEMENT_LINK, ELEMENT_RULE} element_t;
/* Elements of a node that may be hit by the mouse pointer. */

typedef struct tree_node 
{ 
  struct tree_node *parent; /* Parent of this tree node. */
  struct tree_node *sibling; /* Next sibling of this tree node. */
  struct tree_node *child; /* First successor of this tree node. */
  int_t state_index; /* State index of this tree node. */
  tree_node_type_t type; /* Type of this node. */
  string_t rule_name; /* Name of rule that created this node. */
  string_t link_surf;
  string_t link_feat;
  string_t result_surf;
  string_t result_feat;
  string_t rule_set;
  bool_t result_path;

  /* The following items are used to display the tree node. */
  bool_t in_path; /* TRUE iff this node is in displayed path. */
  int_t x, y; /* Node position. */
  pos_string_t *pos_rule_name; /* Positioned rule name. */
  pos_string_t *pos_link_surf; /* Positioned link surface. */
  pos_string_t *pos_state_index; /* Positioned state index. */
} tree_node_t;

typedef struct
{
  list_node_t *next; /* Next path node in this list. */
  pos_string_t *state_index;
  pos_string_t *link_surf;
  pos_value_t *link_feat;
  pos_string_t *rule_name;
  pos_string_t *result_surf;
  pos_value_t *result_feat;
  pos_string_t *rule_set;
} path_node_t;

/* Global variables. ========================================================*/

rectangle_t tree_geometry, path_geometry;
string_t tree_font_family, path_font_family;
int_t tree_font_size, path_font_size;
tree_mode_t tree_mode;
bool_t show_state_indexes;
bool_t inline_path;

/* Variables. ===============================================================*/

/* Information in Tree window. */
static pos_string_t *result_surf;
static tree_node_t *tree_nodes;
static tree_node_t **states; /* Dynamic array of nodes representing states. */
static int_t state_count; /* Size of the array STATES. */
static int_t tree_width, tree_height; /* Width and height of tree canvas. */
static canvas_t *tree_canvas;
static tree_node_t *popup_menu_node;

static bool_t tree_show_state_indexes; 
/* Controls whether state indexes are shown in tree. */

/* Information in Path window. */
static list_t path_nodes;
static canvas_t *path_canvas;
static pos_string_t *plus;
static pos_string_t *comma;
static tree_node_t *path_begin, *path_end;

static bool_t path_show_state_indexes; 
/* Controls whether state indexes are shown in path. */

/* Forward declarations. ====================================================*/

static void goto_state( canvas_t *canvas, guint action );

/* Functions. ===============================================================*/

static string_t
parse_optional_value( string_t *input )
/* Parse "{}" or "{VALUE}" in *INPUT and return NULL or VALUE, resp.
 * Update *INPUT. The result must bee freed after use. */
{
  string_t s, value;
  
  if (**input != '{') 
    complain( "Missing \"{\"." );
  for (s = *input + 1; *s != '}'; s++)
  {
    if (*s == EOS) 
      complain( "Missing \"}\"." );
    if (*s == '\"') 
    {
      for (s++; *s != '\"'; s++)
      {
	if (*s == EOS) 
	  complain( "Missing '\"'." );
	if (s[0] == '\\' && s[1] == '\"') 
	  s++;
      }
    }
  }
  if (s > *input + 1) 
    value = new_string( *input + 1, s );
  else 
    value = NULL;
  *input = s + 1;
  parse_whitespace( input );
  return value;
}

/*---------------------------------------------------------------------------*/

static void
configure_path( canvas_t *canvas, int_t *width_p, int_t *height_p )
/* Do the layout of the path CANVAS. 
 * Return the canvas' total size in *WIDTH_P and *HEIGHT_P. */
{
  int_t path_width, path_height, x, x_dist;
  path_node_t *node;
  bool_t is_first;
  int_t space_width = get_space_width( canvas );
  int_t font_height = get_font_height( canvas );
  int_t font_ascent = get_font_ascent( canvas );
  int_t border_width = get_border_width( canvas );
  
  config_pos_string( plus, canvas );
  config_pos_string( comma, canvas );
  x_dist = space_width + comma->width;

  path_width = path_height = border_width;
  is_first = TRUE;

  FOREACH( node, path_nodes )
  {
    if (node->link_feat != NULL)
    {
      if (is_first) 
	is_first = FALSE; 
      else 
	path_height += font_height;
      
      config_pos_string( node->link_surf, canvas );
      node->link_surf->x = border_width + plus->width + space_width;
      path_width = MAX( path_width, 
			node->link_surf->x + node->link_surf->width );
      
      config_pos_value( node->link_feat, canvas );
      node->link_feat->x = node->link_surf->x;
      if (inline_path)
      {
	node->link_surf->y = (path_height 
			      + node->link_feat->ascent - font_ascent);
	node->link_feat->x += node->link_surf->width + x_dist;
      }
      else
      {
	node->link_surf->y = path_height;
	path_height += font_height;
      }
      node->link_feat->y = path_height;
      path_width = MAX( path_width, 
			node->link_feat->x + node->link_feat->width );
      path_height += node->link_feat->height;
    }
    if (node->result_feat != NULL)
    {
      if (is_first)
	is_first = FALSE; 
      else
	path_height += font_height;

      x = border_width;

      /* Configure rule name. */
      if (node != (path_node_t *) path_nodes.first)
      {
	config_pos_string( node->rule_name, canvas );
	node->rule_name->x = x;
	x += node->rule_name->width + 2 * space_width;
	if (! inline_path)
	  node->rule_name->y = path_height;
      }

      /* Configure state index. */
      if (path_show_state_indexes)
      {
	config_pos_string( node->state_index, canvas );
	node->state_index->x = x;
	if (inline_path)
	  x += node->state_index->width + x_dist;
	else
	{
	  node->state_index->y = path_height;
	  path_width = MAX( path_width, x + node->state_index->width );
	  path_height += font_height;
	}
      }

      /* Configure result surface. */
      config_pos_string( node->result_surf, canvas );
      node->result_surf->x = x;
      if (inline_path) 
	x += node->result_surf->width + x_dist;
      else
      {
	node->result_surf->y = path_height;
	path_width = MAX( path_width, x + node->result_surf->width );
	path_height += font_height;
      }

      /* Configure result feature structure. */
      config_pos_value( node->result_feat, canvas );
      node->result_feat->x = x;
      node->result_feat->y = path_height;
      if (inline_path)
	x += node->result_feat->width + x_dist;
      else
      {
	path_width = MAX( path_width, x + node->result_feat->width );
	path_height += node->result_feat->height;
      }

      /* Configure rule set. */
      config_pos_string( node->rule_set, canvas );
      node->rule_set->x = x;
      if (inline_path)
      {
	node->rule_name->y 
	  = node->state_index->y 
	  = node->result_surf->y 
	  = node->rule_set->y
	  = path_height + node->result_feat->ascent - font_ascent;
	path_height += node->result_feat->height;
      }
      else
      {
	node->rule_set->y = path_height;
	path_height += font_height;
      }
      path_width = MAX( path_width, x + node->rule_set->width );
    }
  }

  *width_p = path_width + border_width;
  *height_p = path_height + border_width;
}

/*---------------------------------------------------------------------------*/

static void
expose_path( canvas_t *canvas, rectangle_t *area )
{
  path_node_t *node;
  int_t x1, x2, y;
  int_t space_width = get_space_width( canvas );
  int_t font_height = get_font_height( canvas );
  int_t border_width = get_border_width( canvas );
  int_t line_width = get_line_width( canvas );

  set_color( BLACK );

  FOREACH( node, path_nodes )
  {
    if (node->link_feat != NULL)
    {
      plus->x = border_width;
      plus->y = node->link_surf->y;
      draw_pos_string( plus, canvas );
      draw_pos_string( node->link_surf, canvas );
      draw_pos_value( node->link_feat, canvas );
      if (inline_path)
      {
	comma->x = node->link_surf->x + node->link_surf->width;
	comma->y = node->link_surf->y;
	draw_pos_string( comma, canvas );
      }
    }
    if (node->result_feat != NULL)
    {
      if (node != (path_node_t *) path_nodes.first)
      {
	x1 = node->rule_name->x;
	x2 = x1 + node->rule_name->width + space_width;
	y = node->rule_name->y + font_height + line_width;
	draw_pos_string( node->rule_name, canvas );

	/* Draw arrow. */
	draw_lines( 2, x1, y, x2, y );
	draw_lines( 3, 
		    x2 - space_width, y - space_width / 2,
		    x2, y, 
		    x2 - space_width, y + space_width / 2 );
      }
      if (path_show_state_indexes)
	draw_pos_string( node->state_index, canvas );
      draw_pos_string( node->result_surf, canvas );
      draw_pos_value( node->result_feat, canvas );
      draw_pos_string( node->rule_set, canvas );
      if (inline_path)
      {
	comma->x = node->result_surf->x + node->result_surf->width;
	comma->y = node->result_surf->y;
	draw_pos_string( comma, canvas );
	comma->x = node->result_feat->x + node->result_feat->width;
	draw_pos_string( comma, canvas );
      }
    }
  }
}

/*---------------------------------------------------------------------------*/

static void
unmark_tree_nodes( tree_node_t *node )
/* Mark NODE and all its children and siblings *not* to be part of the path. */
{
  for (; node != NULL; node = node->sibling)
  {
    node->in_path = FALSE;
    unmark_tree_nodes( node->child );
  }
}

/*---------------------------------------------------------------------------*/

static void
free_path( void )
{
  path_node_t *path_node;

  /* Clear old path nodes. */
  FOREACH_FREE( path_node, path_nodes )
  {
    free_pos_string( &path_node->state_index );
    free_pos_value( &path_node->link_feat );
    free_pos_value( &path_node->result_feat );
  }

  unmark_tree_nodes( tree_nodes );
}

/*---------------------------------------------------------------------------*/

static void
close_path( canvas_t *canvas )
{
  free_path();
  path_begin = path_end = NULL;
  redraw_canvas( tree_canvas );
}

/*---------------------------------------------------------------------------*/

static void 
set_inline_path( canvas_t *canvas, guint action, GtkWidget *item )
{
  inline_path = GTK_CHECK_MENU_ITEM( item )->active;
  configure_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

static void 
path_index_option( canvas_t *canvas, guint action, GtkWidget *item )
{
  path_show_state_indexes = GTK_CHECK_MENU_ITEM( item )->active;
  configure_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

static GtkItemFactoryEntry path_items[] = 
{
  { "/Style/Inline", NULL, set_inline_path, 0, "<ToggleItem>" },
  { "/Path", NULL, NULL, 0, "<Branch>" },
  { "/Path/Show State Indexes", NULL, path_index_option, 0, "<ToggleItem>" },
  { "/End States", NULL, NULL, 0, "<Branch>" },
  { "/End States/Show First End State", "<Control>F", goto_state, FIRST_STATE, 
    NULL },
  { "/End States/Show Previous End State", "<Control>P", goto_state, PREV_STATE,
    NULL },
  { "/End States/Show Next End State", "<Control>N", goto_state, NEXT_STATE, 
    NULL },
  { "/End States/Show Last End State", "<Control>L", goto_state, LAST_STATE, 
    NULL }
};

/*---------------------------------------------------------------------------*/

static void 
display_path( void )
{
  tree_node_t *tree_node;
  path_node_t *path_node;
  string_t state_index, string;

  free_path();
  
  /* Create the path nodes. We go backwards from the path end to the path 
   * beginning, since this direction is easier to traverse. */
  for (tree_node = path_end; ; tree_node = tree_node->parent)
  {
    tree_node->in_path = TRUE;
    path_node = new_node( &path_nodes, sizeof( path_node_t ), LIST_START );
    if (path_begin != NULL && tree_node->result_feat != NULL)
    {
      /* Create a new state in the path. */
      state_index = int_to_string( tree_node->state_index );
      string = concat_strings( "State ", state_index, ":", NULL );
      path_node->state_index = new_pos_string( string );
      free_mem( &string );
      free_mem( &state_index );
      path_node->rule_name = new_pos_string( tree_node->rule_name );
      path_node->result_surf = new_pos_string( tree_node->result_surf );
      set_scanner_input( tree_node->result_feat );
      path_node->result_feat = parse_pos_value();
      parse_token( EOF );
      set_scanner_input( NULL );
      path_node->rule_set = new_pos_string( tree_node->rule_set );
    }
    if (tree_node == path_begin) 
      break;
    if (tree_node->link_feat != NULL)
    {
      /* Create a new link in the path. */
      path_node->link_surf = new_pos_string( tree_node->link_surf );
      set_scanner_input( tree_node->link_feat );
      path_node->link_feat = parse_pos_value();
      parse_token( EOF );
      set_scanner_input( NULL );
    }
    if (path_begin == NULL) 
      break;
  }

  if (path_canvas == NULL) 
  {
    path_canvas = create_canvas(
      "Malaga Path", "path.eps", &path_geometry, configure_path, expose_path,
      close_path, NULL, TRUE, path_items, ARRAY_LENGTH( path_items ) );
    path_show_state_indexes = show_state_indexes;
    if (path_show_state_indexes)
      activate_menu_item( path_canvas, "/Path/Show State Indexes" );
    if (inline_path)
      activate_menu_item( path_canvas, "/Style/Inline" );
  }
  else
  {
    configure_canvas( path_canvas );
    show_canvas( path_canvas );
  }
  redraw_canvas( tree_canvas );
}

/*---------------------------------------------------------------------------*/

static void
configure_node( canvas_t *canvas, tree_node_t *node, int_t x, int_t y )
{
  int_t w, node_width;
  tree_node_t *subnode;
  bool_t is_first;
  int_t font_height = get_font_height( canvas ); 
  int_t space_width = get_space_width( canvas );
  int_t line_width = get_line_width( canvas );

  if (tree_show_state_indexes && node->state_index != -1)
  {
    config_pos_string( node->pos_state_index, canvas );
    node_width = MAX( node->pos_state_index->width + space_width, 
		      font_height );
    node->pos_state_index->x 
      = x + (node_width - node->pos_state_index->width) / 2;
    node->pos_state_index->y = tree_height - font_height * 3 / 2 + 1;
  }
  else
    node_width = font_height;

  node->x = x + node_width / 2; 
  node->y = y;
  x += node_width;
  tree_width = MAX( tree_width, x );

  is_first = TRUE;
  for (subnode = node->child; subnode != NULL; subnode = subnode->sibling)
  {
    if (tree_mode == NO_DEAD_ENDS && subnode->type == BREAK_NODE) 
      continue;
    if (tree_mode == RESULT_PATHS && ! subnode->result_path) 
      continue;

    if (is_first) 
      is_first = FALSE;
    else 
      tree_height += (5 * font_height) / 2;

    config_pos_string( subnode->pos_link_surf, canvas );
    config_pos_string( subnode->pos_rule_name, canvas );

    w = MAX( subnode->pos_link_surf->width, subnode->pos_rule_name->width );
    w = MAX( w, space_width );
    subnode->pos_link_surf->x = x + (w - subnode->pos_link_surf->width) / 2;
    subnode->pos_link_surf->y = tree_height - font_height - line_width;
    subnode->pos_rule_name->x = x + (w - subnode->pos_rule_name->width) / 2;
    subnode->pos_rule_name->y = tree_height + line_width;
    configure_node( canvas, subnode, x + w, tree_height );
  }
}

/*---------------------------------------------------------------------------*/

static void
configure_tree( canvas_t *canvas, int_t *width_p, int_t *height_p )
{
  int_t font_height = get_font_height( canvas );
  int_t border_width = get_border_width( canvas );

  tree_width = tree_height = border_width;

  /* Configure result surface. */
  config_pos_string( result_surf, canvas );
  result_surf->x = border_width;
  result_surf->y = border_width;
  tree_width = MAX( tree_width, border_width + result_surf->width );
  tree_height += font_height;
  if (tree_mode != RESULT_PATHS || tree_nodes->result_path)
  {
    /* Configure tree. */
    tree_height += 2 * font_height;
    configure_node( canvas, tree_nodes, border_width, tree_height );
  }

  /* Return width and height. */
  *width_p = tree_width + border_width;
  *height_p = tree_height + font_height + border_width;
}

/*---------------------------------------------------------------------------*/

static void
expose_node( canvas_t *canvas, tree_node_t *node, int_t font_height )
{
  tree_node_t *subnode;
  const int_t x = node->x;
  const int_t y = node->y;
  const int_t radius1 = font_height / 2;
  const int_t radius2 = (radius1 * 3) / 5;
  int_t middle = y;
  int_t bottom = y;

  /* Draw all subnodes. */
  for (subnode = node->child; subnode != NULL; subnode = subnode->sibling)
  {
    if (tree_mode == NO_DEAD_ENDS && subnode->type == BREAK_NODE) 
      continue;
    if (tree_mode == RESULT_PATHS && ! subnode->result_path) 
      continue;
    
    /* Draw horizontal line. */
    bottom = subnode->y;
    if (node->in_path && subnode->in_path)
    {
      middle = subnode->y;
      set_color( RED );
    }
    else 
      set_color( BLACK );
    draw_lines( 2, x, subnode->y, subnode->x, subnode->y );

    if (path_begin == NULL && subnode->in_path) 
      set_color( RED );
    draw_pos_string( subnode->pos_link_surf, canvas );
    set_color( BLACK );
    draw_pos_string( subnode->pos_rule_name, canvas );
    expose_node( canvas, subnode, font_height );
  }

  /* Draw vertical line. */
  set_color( BLACK );
  draw_lines( 2, x, middle, x, bottom ); 
  if (middle != y)
  {
    set_color( RED );
    draw_lines( 2, x, y, x, middle );
  }

  /* Draw the node itself. */
  if (node->type == BREAK_NODE)
  {
    set_color( (node->in_path && path_begin != NULL ? RED : BLACK) );
    draw_rectangle( x - radius1 , y - radius1, 2 * radius1, 2 * radius1 );
  }
  else
  {
    set_color( WHITE );
    draw_circle( TRUE, x, y, radius1 );

    set_color( (node->in_path && path_begin != NULL ? RED : BLACK) );
    draw_circle( FALSE, x, y, radius1 );
    if (node->type == FINAL_NODE || node->type == UNFINAL_NODE)
      draw_circle( FALSE, x, y, radius2 );
    if (node->type == PRUNED_NODE || node->type == UNFINAL_NODE)
    {
      draw_lines( 2, x - radius1, y - radius1, x + radius1, y + radius1 );
      draw_lines( 2, x + radius1, y - radius1, x - radius1, y + radius1 );
    }
  }
  
  /* Draw the state index. */
  if (tree_show_state_indexes && node->state_index != -1)
  {
    set_color( BLUE );
    draw_pos_string( node->pos_state_index, canvas );
  }
}

/*---------------------------------------------------------------------------*/

static void
expose_tree( canvas_t *canvas, rectangle_t *area )
/* Expose AREA on tree CANVAS. */
{
  int_t font_height = get_font_height( canvas );

  if (result_surf != NULL) 
  {
    set_color( BLACK );
    draw_pos_string( result_surf, canvas );
  }
  if (tree_nodes == NULL) 
    return;
  if (tree_mode != RESULT_PATHS || tree_nodes->result_path)
    expose_node( canvas, tree_nodes, font_height );
}

/*---------------------------------------------------------------------------*/

static bool_t
in_circle( int_t x, int_t y, int_t circle_x, int_t circle_y, int_t radius )
/* Return TRUE if position (X,Y) lies in disc with center (CIRCLE_X, CIRCLE_Y)
 * and RADIUS. */ 
{
  int_t diff_x = x - circle_x;
  int_t diff_y = y - circle_y;
  
  /* Taking DIFF_X or DIFF_Y to the 2nd power may result in an overflow,
   * so we will check first whether DIFF_X and DIFF_Y are small enough. */
  return (ABS( diff_x ) < radius && ABS( diff_y ) < radius
	  && diff_x * diff_x + diff_y * diff_y <= radius * radius);
}

/*---------------------------------------------------------------------------*/

static bool_t
in_pos_string( int_t x, int_t y, pos_string_t *pos_string, int_t height )
/* Return TRUE if position (X,Y) lies in bounding box of POS_STRING, using font
 * height HEIGHT. */
{
  return (x >= pos_string->x 
	  && x < pos_string->x + pos_string->width
	  && y >= pos_string->y 
	  && y < pos_string->y + height);
}

/*---------------------------------------------------------------------------*/

static bool_t
in_rectangle( int_t x, int_t y, 
	      int_t rect_x, int_t rect_y, int_t width, int_t height )
/* Return TRUE if position (X,Y) lies in rectangle with upper left corner 
 * (RECT_X, RECT_Y), width WIDTH and height HEIGHT. */
{
  return (x >= rect_x 
	  && x <= rect_x + width 
	  && y >= rect_y
	  && y <= rect_y + height);
}

/*---------------------------------------------------------------------------*/

static void 
set_tree( canvas_t *canvas, guint mode )
/* Set the tree's display mode to MODE. */
{
  tree_mode = mode;
  if (path_end != NULL
      && ((tree_mode == NO_DEAD_ENDS && path_end->type == BREAK_NODE)
	  || (tree_mode == RESULT_PATHS && ! path_end->result_path)))
  {
    hide_canvas( path_canvas );
  }
  configure_canvas( tree_canvas );
}

/*---------------------------------------------------------------------------*/

static void 
goto_state( canvas_t *canvas, guint action )
/* Display a certain state, according to ACTION, in the path window. */
{
  int_t i = 0;
  bool_t inverse = FALSE; /* TRUE if we search in inverse direction. */

  if (action == FIRST_STATE || action == LAST_STATE
      || (path_begin != NULL && path_begin->type == FINAL_NODE))
  {
    switch (action)
    {
    case FIRST_STATE: 
      i = 0; 
      break;
    case PREV_STATE:
      i = path_begin->state_index - 1;
      inverse = TRUE;
      break;
    case NEXT_STATE:
      i = path_begin->state_index + 1;
      break;
    case LAST_STATE: 
      i = state_count - 1; 
      inverse = TRUE; 
      break;
    } 
    while (i >= 0 && i < state_count)
    {
      if (states[i] != NULL && states[i]->type == FINAL_NODE)
      {
	path_begin = path_end = states[i];
	make_visible( tree_canvas, path_begin->x, path_begin->y );
	display_path();
	break;
      }
      if (inverse) 
	i--; 
      else 
	i++;
    }
  }
}

/*---------------------------------------------------------------------------*/

static tree_node_t *
tree_node_at_position( tree_node_t *node, int_t x, int_t y, int_t font_height, 
		       element_t *element_p)
{
  tree_node_t *subnode;

  if (node == NULL) 
    return NULL;
  for (; node != NULL; node = node->sibling)
  {
    if (tree_mode == NO_DEAD_ENDS && node->type == BREAK_NODE) 
      continue;
    if (tree_mode == RESULT_PATHS && ! node->result_path) 
      continue;
    if ((node->type == BREAK_NODE 
	 && in_rectangle( x, y, 
			  node->x - font_height / 2, node->y - font_height / 2,
			  font_height, font_height ))
	|| (node->type != BREAK_NODE 
	    && in_circle( x, y, node->x, node->y, font_height / 2 ) ))
    {
      *element_p = ELEMENT_NODE;
      return node;
    }
    if (in_pos_string( x, y, node->pos_link_surf, font_height ))
    {
      *element_p = ELEMENT_LINK;
      return node;
    }
    if (in_pos_string( x, y, node->pos_rule_name, font_height ))
    { 
      *element_p = ELEMENT_RULE;
      return node;
    }
    subnode = tree_node_at_position( node->child, x, y, font_height, 
				     element_p );
    if (subnode != NULL)
      return subnode;
  }
  return NULL;
}

/*---------------------------------------------------------------------------*/

static void
display_node( canvas_t *canvas, guint action )
{
  tree_node_t *node;

  if (action == PATH_BEGIN)
    path_begin = popup_menu_node;
  else if (action == FROM_ROOT)
    path_begin = tree_nodes;
  if (action == PATH_END || action == FROM_ROOT)
    path_end = popup_menu_node;

  /* Check whether there is a part from PATH_BEGIN to PATH_END. */ 
  node = path_end; 
  while (node != NULL && node != path_begin) 
    node = node->parent;
  if (node == NULL) 
    path_begin = path_end = popup_menu_node;
  
  display_path();
}

/*---------------------------------------------------------------------------*/

static bool_t
mouse_event(  canvas_t *canvas, int_t x, int_t y, int_t button )
/* Called if mouse has moved to position (X,Y). */
{
  tree_node_t *node;
  element_t element;

  node = tree_node_at_position( tree_nodes, x, y, get_font_height( canvas ),
				&element );
  if (node == NULL)
  {
    set_cursor( canvas, FALSE );
    return FALSE;
  }

  /* No button pressed: we should just update the cursor. */
  if (button == 0)
  {
    set_cursor( canvas, TRUE );
    return TRUE;
  }

  switch (element)
  {
  case ELEMENT_NODE:
    if (button == 1) 
    {
      path_begin = path_end = node;
      display_path();
    }
    else if (button == 3)
    {
      popup_menu_node = node;
      popup_menu( canvas );
    }
    break;

  case ELEMENT_LINK:
    path_begin = NULL;
    path_end = node;
    display_path();
    break;

  case ELEMENT_RULE:
    path_begin = node->parent;
    path_end = node;
    display_path();
    break;
  }
  return TRUE;
}

/*---------------------------------------------------------------------------*/

static void 
tree_index_option( canvas_t *canvas, guint action, GtkWidget *item )
{
  tree_show_state_indexes = GTK_CHECK_MENU_ITEM( item )->active;
  configure_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

static GtkItemFactoryEntry tree_items[] = 
{
  { "/Tree", NULL, NULL, 0, "<Branch>" },
  { "/Tree/Full Tree", NULL, set_tree, FULL_TREE, "<RadioItem>" },
  { "/Tree/No Dead Ends", NULL, set_tree, NO_DEAD_ENDS, "/Tree/Full Tree" },
  { "/Tree/Complete Paths", NULL, set_tree, RESULT_PATHS, "/Tree/Full Tree" },
  { "/Tree/sep1", NULL, NULL, 0, "<Separator>" },
  { "/Tree/Show State Indexes", NULL, tree_index_option, 0, "<ToggleItem>" },
  { "/End States", NULL, NULL, 0, "<Branch>" },
  { "/End States/Show First End State", "<Control>F", goto_state, FIRST_STATE, 
    NULL },
  { "/End States/Show Previous End State", "<Control>P", goto_state, PREV_STATE,
    NULL },
  { "/End States/Show Next End State", "<Control>N", goto_state, NEXT_STATE, 
    NULL },
  { "/End States/Show Last End State", "<Control>L", goto_state, LAST_STATE, 
    NULL }
};

/*---------------------------------------------------------------------------*/

static GtkItemFactoryEntry popup_items[] = 
{
  { "/Set Path Begin", NULL, display_node, PATH_BEGIN, "<Item>" },
  { "/Set Path End", NULL, display_node, PATH_END, "<Item>" },
  { "/Show Path From Root", NULL, display_node, FROM_ROOT, "<Item>" }
};

/*---------------------------------------------------------------------------*/

static void
free_tree_nodes( tree_node_t *node )
/* Free *NODE_P and all its siblings and children. Set *NODE_P to NULL. */
{
  tree_node_t *next;

  for (; node != NULL; node = next)
  {
    /* Remove node from state index. */
    if (node->state_index != -1)
      states[ node->state_index ] = NULL;

    free_tree_nodes( node->child );
    free_mem( &node->rule_name );
    free_mem( &node->link_surf );
    free_mem( &node->link_feat );
    free_mem( &node->result_surf );
    free_mem( &node->result_feat );
    free_mem( &node->rule_set );
    free_pos_string( &node->pos_link_surf );
    free_pos_string( &node->pos_rule_name );
    free_pos_string( &node->pos_state_index );
    next = node->sibling;
    free( node );
  }
}

/*---------------------------------------------------------------------------*/

static void
free_tree( void )
{
  /* Clear old variables. */
  free_pos_string( &result_surf );
  free_pos_string( &plus );
  free_pos_string( &comma );
  free_tree_nodes( tree_nodes );
  tree_nodes = NULL;
}

/*---------------------------------------------------------------------------*/

static void
close_tree( canvas_t *canvas )
/* Called by "canvas.c" when CANVAS gets hidden. */
{
  if (path_canvas != NULL) 
    hide_canvas( path_canvas );
  free_tree();
}

/*---------------------------------------------------------------------------*/

void 
read_tree( void )
/* Read new tree from STDIN. */
{
  string_t line; /* A line of input from STDIN. */
  string_t line_p; /* Part of LINE which is yet to parse. */
  string_t type; /* Node type. */
  string_t state_index;
  tree_node_t *node;
  int_t parent_state_index, i;
  tree_node_t **node_p;

  if (path_canvas != NULL) 
    hide_canvas( path_canvas );
  free_tree();

  /* Allocate new nodes array. */
  if (state_count == 0)
  {
    state_count = 100;
    states = new_vector( sizeof( tree_node_t * ), state_count );
  }

  /* Read new input surface. */
  line = read_line( stdin );
  result_surf = new_pos_string( line );
  free_mem( &line );

  /* Read new tree. */
  while (TRUE) 
  { 
    line = read_line( stdin );
    if (line == NULL) 
      complain( "Premature EOF." );
    if (strcmp_no_case( line, "end" ) == 0) 
      break;

    node = new_mem( sizeof( tree_node_t ) );
    line_p = line;
    node->state_index = parse_int( &line_p );
    type = parse_word( &line_p );
    if (strcmp_no_case( type, "inter" ) == 0) 
      node->type = INTER_NODE;
    else if (strcmp_no_case( type, "break" ) == 0) 
      node->type = BREAK_NODE;
    else if (strcmp_no_case( type, "final" ) == 0) 
      node->type = FINAL_NODE;
    else if (strcmp_no_case( type, "unfinal" ) == 0) 
      node->type = UNFINAL_NODE;
    else if (strcmp_no_case( type, "pruned" ) == 0) 
      node->type = PRUNED_NODE;
    else 
      complain( "Unknown node type \"%s\".", type );
    free_mem( &type );
    parent_state_index = parse_int( &line_p );
    node->rule_name = parse_word( &line_p );
    node->link_surf = parse_optional_value( &line_p );
    node->link_feat = parse_optional_value( &line_p );
    node->result_surf = parse_optional_value( &line_p );
    node->result_feat = parse_optional_value( &line_p );
    node->rule_set = parse_word( &line_p );
    parse_end( &line_p );
    free_mem( &line );

    node->pos_link_surf = new_pos_string( node->link_surf );
    node->pos_rule_name = new_pos_string( node->rule_name );
    if (node->state_index != -1)
    {
      state_index = int_to_string( node->state_index );
      node->pos_state_index = new_pos_string( state_index );
      free_mem( &state_index );
    }

    /* Find parent node. */
    if (parent_state_index == -1) 
      tree_nodes = node; /* This is the root node. */
    else if (parent_state_index < 0 || parent_state_index >= state_count 
	     || states[ parent_state_index ] == NULL)
    {
      complain( "Display data corrupted." );
    }
    else 
    { 
      node->parent = states[ parent_state_index ];

      /* Add NODE to be a child of NODE->PARENT. */
      node_p = &node->parent->child;
      while (*node_p != NULL) 
	node_p = &(*node_p)->sibling;
      *node_p = node;
    }

    /* Add node to the STATES array. */
    if (node->state_index >= state_count)
    {
      renew_vector( &states, sizeof( tree_node_t * ), 2 * node->state_index );
      for (i = state_count; i < 2 * node->state_index; i++) 
	states[i] = NULL;
      state_count = 2 * node->state_index;
    }
    if (node->state_index != -1)
      states[ node->state_index ] = node;

    /* If this is a final node, mark it and all its predecessors. */
    if (node->type == FINAL_NODE) 
    {
      for (; node != NULL; node = node->parent) 
	node->result_path = TRUE;
    }
  }
  free_mem( &line );
  if (tree_nodes == NULL) 
    complain( "Missing root node in tree." );

  plus = new_pos_string( "+" );
  comma = new_pos_string( "," );

  if (tree_canvas == NULL) 
  {
    tree_canvas = create_canvas( 
      "Malaga Tree", "tree.eps", &tree_geometry, configure_tree, expose_tree, 
      close_tree, mouse_event, FALSE, tree_items, ARRAY_LENGTH( tree_items ) );
    set_popup_menu( tree_canvas, popup_items, ARRAY_LENGTH( popup_items ) );
    tree_show_state_indexes = show_state_indexes;
    if (tree_show_state_indexes )
      activate_menu_item( tree_canvas, "/Tree/Show State Indexes" );
  }
  else
  {
    configure_canvas( tree_canvas );
    show_canvas( tree_canvas );
  }
}

/* End of file. =============================================================*/