
/**

   Node.java
  
   Interne Repraesentation eines Und/Oder-Baumes in Categories
  
   Autor: Matthias Nickles (Matthias_Nickles@msn.com)
  
   Quellcode v0.10.1e
  
   Naehere Erlaeuterungen siehe Dokumentation und AOT.java

*/

// Mit "***" gekennzeichnete Codebereiche umgehen pragmatisch Beschraenkungen 
// oder Fehler der verwendeten Java-Systeme/GUIs und sollten von Zeit zu Zeit 
// ueberprueft werden.


//package AOT;

import java.awt.*; 

import java.io.*;  

import java.util.*;

import java.net.*;

import View;

import AOTParse;

import PropsDialog;

import InplaceLayout;  // siehe class Text_Node und TextLeafArea

//import browser.*;  // Basisklassen fuer den internen HTML-Viewer 


/** ein Und/Oder-Baum als Java-Datenstruktur */
abstract class Node implements Cloneable { 
 
 /** Einrueckung in autom. erzeugten Selektionsdateien (.sel) 
 *** wg. Fehler in Java 1.0-Core von J++ nicht zu gebrauchen! */
 final static String TAB = "  "; 
 
 /** muessen Kanten-Ids. ueberall eindeutig sein oder nur unter ihren Geschwistern?*/
 static boolean unique_ids_all_over = false; 

 /** Konstanten fuer den get_selection()-Modus: */

 final static int GET_IDS = 0;

 final static int GET_LABELS = 1;

 final static int GET_COMMENTARIES = 2;

 final static int GET_HELP = 2;

 /** null fuer Wurzelknoten*/
 Node parent; 
 
 /** vorlaufende Kante (auch fuer Wurzel)*/
 Edge edge; 
 
 /** undef. bei einem Text-Blatt, [0] bei einem AND-Blatt*/
 Node[] children;  

 /** optionaler Knotenname, der beim Speichern in Textform mit einem
  "Include-Knoten" z.B. als Datei-/URL-Name verwendet wird, oder null andernfalls*/
 String name = null; 

 /** true: Unterbaum verstecken ("collapsed")*/
 boolean compressed = false; 

 /** Hilfetext oder null;*/
 String help = null; 


 Node(Node parent, Edge edge, Node[] children, String help, boolean compressed) {

  this.parent = parent;

  this.edge = edge;

  this.children = children;

  this.help = help;

  this.compressed = compressed;

 }

 /** Anwender doppelklickte auf Kante ==> Aktivierung umschalten*/
 void toggle_activation() {  

  if(parent!=null) {

   if(active()) { // Knoten deaktivieren, falls mgl. (impliziert, das parent aktiv ist)

    if(!(parent instanceof AND_Node))
     deactivate();
    else
     parent.toggle_activation();

   } else
    activate(); // immer erlaubt, auch wenn parent noch inaktiv ist! 
    // Aktivierungsast wird dann ggf. bis zur Wurzel vervollstaendigt

  }

 }


 void activate() { 
  
  if(parent!=null) {

   parent.activate();  
   // erlaubt die Aktivierung von Knoten, deren parents noch inaktiv sind

   parent.set_child_activated(this); // (dies reichte, falls parent schon aktiv ist)

  }
 
 }


 boolean match(String pattern) {

  AOT.Aot.document.match_fail = false;

  activate_edges(pattern, null, true);

  return !AOT.Aot.document.match_fail;

 }


 boolean activate_edges(String selection, String previous, boolean match_only) {

  return activate_edges(selection, previous, match_only, null);

 }


 /** liefert zur Selektion selection das Aequivalent mit vertauschten Ids und Labels */
 String id_labels_swap(String selection) {

  String[] swap_string = new String[1];

  swap_string[0] = new String();

  activate_edges(selection, null, false, swap_string);

  return swap_string[0];

 }


 /** selektion aktivieren oder pruefen, ob (Teil-)selection schon aktiv ist (match_only=true).
     Ist id_labels_swap!=null, wird die Selektion neu erzeugt, wobei Labels und Identifizierer vertauscht werden.*/
 boolean activate_edges(String selection, String previous, boolean match_only, String[] id_labels_swap) {

  StringBufferInputStream input; 

  StreamTokenizer selstream = new java.io.StreamTokenizer(input = new StringBufferInputStream(selection));
  // (es gibt im util-Package noch eine Klasse StreamTokenizer, die aber
  // weniger leistungsfaehig ist)

  selstream.slashSlashComments(true); // Kommentare der Form ... //...<eol> erlauben

  selstream.slashStarComments(true); // Kommentare der Form ... /*...*/ ... erlauben

  selstream.whitespaceChars(0, ' ');

  selstream.eolIsSignificant(false);

  selstream.quoteChar('\"'); // \n und \r in Strings >evtl.< erlaubt (haengt von Java-Implementation ab)

  // *** es scheint ein Problem mit dem Zeichen 10 (als Code 10, nicht mit Fluchtzeichen)
  // in Strings zu geben; Zeilenschaltungen in Selektionen
  // daher immer als \n schreiben!

  selstream.wordChars('A', 'z');  // zzgl. Ziffern, die immer dazugenommen werden

  if(!activate_edges(selstream, match_only, id_labels_swap, "\r\n  ")) { // Fehler ist aufgetreten

   if(id_labels_swap!=null)
	;  //...
   else if(match_only)
    AOT.Aot.document.match_fail = true;
   else {

    System.out.println("Error parsing selection "+(match_only?"pattern":""));  

    if(selstream.sval!=null) // Cursor auf Fehlerstelle setzten (nicht bei allen Fehlerarten mgl.)
     AOT.Aot.document.display_selection(selection, selection.length()-input.available()-selstream.sval.length()-1); //(evtl. ungenau)
    else //(^ Klassen-Problem: Node kennt nicht sein Dokument)
     AOT.Aot.document.display_selection(selection, selection.length()-input.available()); //(evtl. ungenau)

    InfoDialog.info(AOT.Aot.document.selection.textarea, AOT.APPLET_NAME+" Error", "Error parsing selection at line "+selstream.lineno(), InfoDialog.OK, "cats_src.html#sel_grammatik");

    if(previous!=null)
     activate_edges(previous, "()", false); // vorherige Selektion restaurieren

    return false;

   }

  }

  return true;

 }


 boolean activate_edges(StreamTokenizer selstream, boolean match_only, String[] id_labels_swap, String indent) {

  Node recent_edge = null;

  try {

parse_loop:
   
   for(int tokenType = selstream.nextToken();
   	   tokenType!=StreamTokenizer.TT_EOF;
	   tokenType = selstream.nextToken()) {

 	switch(tokenType) {

	 case StreamTokenizer.TT_WORD:

	  if(selstream.sval.equals("_")) { // Universalplatzhalter

       if(id_labels_swap!=null) 
		id_labels_swap[0] += "_";

	  } else if(children!=null) { // Kante per Identifizierer aktivieren

       if((recent_edge = activate_edge(selstream.sval, match_only, id_labels_swap))==null) {

	    if(!match_only) // beim Constraint-Check werden fehlerh. Selektionen gezielt eingesetzt
	     System.out.println("Semantical error in selection: Edge-id does not match: "+selstream.sval);  

        break parse_loop;

	   } else if(id_labels_swap!=null) 
		id_labels_swap[0] += indent+"\""+AOTParse.escapeString(recent_edge.edge.label.trim())+"\"";
	
	  } else {

       if(!match_only) // beim Constraint-Check werden fehlerh. Selektionen gezielt eingesetzt
        System.out.println("Semantical error in selection: Edge-id does not match: "+selstream.sval);  

       break parse_loop;

	  }
	
	  break;

   	 case '(':

	  if(id_labels_swap!=null)
	   id_labels_swap[0] += "(";

	  if(recent_edge!=null) 
	   if(!recent_edge.activate_edges(selstream, match_only, id_labels_swap, indent+TAB))
        break parse_loop;

      break;

   	 case ')':

      if(id_labels_swap!=null)
   	   id_labels_swap[0] += ")";

      return true;

	 case ',': 

      if(id_labels_swap!=null)
	   id_labels_swap[0] += ",";

	  break;
	 
	 case '\"':  // Blatt-Text einsetzen oder Kante per "Label" identifizieren
     //(Text-Blatt: man kann sich auf die "reine" Aktivierung beschraenken, indem man textleaf(_) angibt)

	  if(this instanceof Text_Node) {
 
       if(id_labels_swap!=null)
		id_labels_swap[0] += "\""+AOTParse.escapeString(selstream.sval)+"\"";
       else if(!match_only)
        ((Text_Node)this).set_leaf_text(selstream.sval);

	  } else {  // Kind-Kante per "Label" aktivieren
      // (Dazu muessen natuerlich alle Geschwister-Labels eindeutig sein (was sonst nur fuer Ids gefordert wird))

       if(children!=null) { // Kante aktivieren

        if((recent_edge = activate_edge_by_label(selstream.sval, match_only, id_labels_swap))==null) {

	     if(!match_only) // beim Constraint-Check werden fehlerh. Selektionen gezielt eingesetzt
	      System.out.println("Semantical error in selection: Edge label does not match: \""+AOTParse.escapeString(selstream.sval)+"\"");  

         break parse_loop;

	    } else if(id_labels_swap!=null)
		 id_labels_swap[0] += indent+recent_edge.edge.id;

	   } else {

        if(!match_only) // beim Constraint-Check werden fehlerh. Selektionen gezielt eingesetzt
         System.out.println("Semantical error in selection: Edge label does not match: \""+AOTParse.escapeString(selstream.sval)+"\"");  

        break parse_loop;

       }

	  }

	  break;

	 default:

	  System.out.println("Lexical error in selection: Char \""+Integer.toString(tokenType)+"\" not expected");

      break parse_loop;

	}

   }

  } catch (IOException xcpt) { /*(kann nicht vorkommen)*/ }

  return false; // Fehler

 }


 Node activate_edge(String name, boolean match_only, String[] id_labels_swap) {

  Node recent_edge = null; 

  for(int i = 0; i<children.length; i++) 
   if(name.equals(children[i].edge.id)) {
          
    recent_edge = children[i];
 
    if(!recent_edge.active()) {

	 if(match_only)
	  AOT.Aot.document.match_fail = true;
	 else if(id_labels_swap==null)
      recent_edge.activate();

	}

   }

   return recent_edge;

 }


 Node activate_edge_by_label(String name, boolean match_only, String[] id_labels_swap) {

  Node recent_edge = null; 

  for(int i = 0; i<children.length; i++) 
   if(name.equals(AOTParse.escapeString(children[i].edge.label.trim()))) {
          
    recent_edge = children[i];
 
    if(!recent_edge.active()) {

	 if(match_only)
	  AOT.Aot.document.match_fail = true;
	 else if(id_labels_swap==null)
      recent_edge.activate();

	}

   }

   return recent_edge;

 }


 /** Sammelt aktive Kanten und Blatt-Texte; das Resultat ist nur fuer aktives this definiert!
  (Syntax einer Selektion siehe activate_edges()). what=0: Kopf=id, 1:=Label, 2:Kommentar/Knoten-Hilfe */
 abstract String get_selection(String indent/*Praefix zur Einrueckung*/, int what);


 abstract void deactivate();
 

 /** (wird in class (i)OR_Node ueberschrieben)*/
 void set_child_activated(Node child) { }
 

 /** (wird in class (i)OR_Node ueberschrieben) */
 void set_child_deactivated(Node dummy) { }
 

 abstract boolean child_active(Node child);


 /** Bottom-Up Test, ob Knoten aktiv */
 boolean active() {  
  
  if(parent==null)
   return true;  // (die Wurzel gilt immer als aktiv)
 
  return parent.child_active(this); // true impliziert auch, dass parent selbst aktiv ist!

 }


 /** Include-Knoten werden auf includes gepusht.*/
 abstract String toString(Stack includes, String indent/*Praefix zur Einrueckung*/, boolean top);
 
 String attributes_toString() { 

  return new String((compressed?", collapsed=true":"")
	                +(help!=null?", help=\""+AOTParse.escapeString(help)+"\"":""));

 }


 String childrenToString(Stack includes, String indent/*Praefix zur Einrueckung*/) {

  String result = "[";
  
  for(int i = 0; i<children.length; i++) {

   result += indent+children[i].toString(includes, indent+TAB, false);
   
   if(i<children.length-1)
    result += ", ";
 
  }

  return result += "]";
 
 }
 

 public String toString() {

  String result = toString(null, "\r\n  ", false);

  return result;

 }


 void set_edge_label(String label) {

  this.edge.label = label;

 }


 void set_edge(Edge edge) {

  this.edge = edge;

 }


 String edge_label() {

  return edge.label;

 }


/** stellt Knoten graphisch in view dar
 Der Aktivierungszustand des Knotens wird top-down waehrend des Zeichnens
 festgestellt und ueber den Parameter active angegeben.*/
/*synchronized !*/abstract Area display(Point position, boolean active, View view); 
 
 void set_children(Node[] children) {  // waehrend der Konstruktion
  
  this.children = children;

 }


 void add_child(Node child) { 
 
  add_child(child, false, null);

 }
 

 /**  vgl. Methode Document.paste();
 Erlaeuterung von as_first und pivot siehe bei Methode Document.paste()*/
 void add_child(Node child, boolean as_first, Node pivot) { 
  
  child.parent = null;

  child.deactivate();

  if(children==null)
   return;

  Node[] more_children = new Node[children.length+1];

  int i = 0;

  if(pivot==null) {

   if(!as_first)
    for(i = 0; i<children.length; i++)
     more_children[i] = children[i];
   else
    for(i = children.length; i>0; i--)
     more_children[i] = children[i-1];

   more_children[i] = child;
  
  } else {

   for(int j = 0; j<children.length; j++, i++) {

	if(as_first&&children[j]==pivot)
	 more_children[i++] = child;

    more_children[i] = children[j];

	if(!as_first&&children[j]==pivot)
	 more_children[++i] = child;
   }

  }

  child.parent = this;

  children = more_children;

 }


 /**(wird in class OR_Node erweitert) */
 void remove_child(Node child) { 

  child.remove_inplace_areas();

  Node[] less_children = new Node[children.length-1];
  
  int j = 0;

  for(int i = 0; i<children.length; i++)
   if(children[i]!=child)
	less_children[j++] = children[i];

  children = less_children;

 }


 /** (wird in class (i)OR_Node erweitert) */
 void replace_child(Node child, Node node) { 

  child.remove_inplace_areas();
  
  for(int i = 0; i<children.length; i++)
   if(children[i]==child)
	children[i] = node;

  node.parent = this;

 }


 /**(nur fuer And/Or-Nodes)*/
 Node get_neighbour(Node node, boolean upper) { 

  for(int i = 0; i<children.length; i++)
   if(children[i]==node) {

	if(upper&&i>0)
     return children[i-1];
	else if(i<children.length-1)
	 return children[i+1];

  }

  return null;

 }


 /**(nur fuer And/Or-Nodes)*/
 void swap_children(Node pivot, boolean upper) { 

  for(int i = 0; i<children.length; i++)
   if(children[i]==pivot) {

	if(upper&&i>0) {

     Node temp = children[i-1];

	 children[i-1] = children[i];

     children[i] = temp;

	} else if(!upper&&i<children.length-1) {

     Node temp = children[i+1];

	 children[i+1] = children[i];

     children[i] = temp;

	}

	break;

   }

 }


 /** primitives Austauschverfahren (tut's hier aber)*/
 void sort_children() {
 
  if(children!=null) {

   for(int k = 0; k<children.length-1; k++)
	for(int j = k+1; j<children.length; j++) {
	
	 String a = children[j].edge.label, b = children[k].edge.label;

	 if(a==null)
	  a = "";

	 if(b==null)
	  b = "";

	 if(a.compareTo(b)<0) {

	   Node temp = children[j];

	   children[j] = children[k];

	   children[k] = temp;

     }

    }

  }

 }


 /** Name eines include-"Knotens" festlegen */
 void set_name(String name) { 

  this.name = name;

 }


 void toggle_compression() {

  set_collapsed(!compressed);

 }


 void set_collapsed(boolean collapsed) { //(sollte besser ..compressed.. heissen?!!!)

  compressed = collapsed;

 }


 void set_collapsed_for_all(boolean collapsed) {

  compressed = collapsed;

  if(children!=null)
   for(int k = 0; k<children.length; k++)
    children[k].set_collapsed_for_all(collapsed);

 }


 /** entfernt top-down alle Controls zu inplace-Bereichen */
 void remove_inplace_areas() { 
 
  if(children!=null)
   for(int k = 0; k<children.length; k++)
    children[k].remove_inplace_areas();

 }


 Node check_for_empty_OR_nodes() {

  if(children==null)
   return null;

  if(children.length==0&&((this instanceof OR_Node)||(this instanceof iOR_Node)))
   return this;

  for(int i = 0; i<children.length; i++) {

   Node node = children[i].check_for_empty_OR_nodes();

   if(node!=null)
    return node;

  }

  return null;

 }


 void set_help(String help) {

  this.help = help;

 }


 /** vgl. find_node_by_edge_among_sisters()*/
 Node find_node_by_edge(String identifier, boolean include_labels/* Labels bei Suche einschliessen*/, boolean ignore_case, Node ignore/*diesen Knoten bei der Suche auslassen (oder null), nicht aber seine Kinder*/, boolean prefix/* reicht es, wenn der Anfang uebereinstimmt?*/, boolean part/* reicht irgendein Teilstueck? */) {
 
  if(this!=ignore && edge!=null/*(edge==null ist hier ausnahmsweise moeglich!)*/) {

   if(ignore_case) {

    if(prefix) {

     if(include_labels&&edge.label!=null)
      if(edge.label.toUpperCase().startsWith(identifier.toUpperCase()))
       return this;

     if(edge.id.toUpperCase().startsWith(identifier.toUpperCase()))
      return this;

    } else if(part) {

     if(include_labels&&edge.label!=null)
      if(edge.label.toUpperCase().indexOf(identifier.toUpperCase())>=0)
       return this;

     if(edge.id.toUpperCase().indexOf(identifier.toUpperCase())>=0)
      return this;

    } else {

     if(include_labels&&edge.label!=null)
      if(edge.label.equalsIgnoreCase(identifier))
       return this;

     if(edge.id.equalsIgnoreCase(identifier))
      return this;

    }

   } else { 

    if(prefix) {

     if(include_labels&&edge.label!=null)
      if(edge.label.startsWith(identifier))
       return this;

     if(edge.id.startsWith(identifier))
      return this;

    } else if(part) {

     if(include_labels&&edge.label!=null)
      if(edge.label.indexOf(identifier)>=0)
       return this;

     if(edge.id.indexOf(identifier)>=0)
      return this;

    } else {

     if(include_labels&&edge.label!=null)
      if(edge.label.equals(identifier))
       return this;

     if(edge.id.equals(identifier))
      return this;

    }

   }

  }

  if(children==null)
   return null;

  for(int i = 0; i<children.length; i++) {

   Node node = children[i].find_node_by_edge(identifier, include_labels, ignore_case, ignore, prefix, part);

   if(node!=null)
    return node;

  }

  return null;

 }


 /** vgl. find_node_by_edge()*/
 Node find_node_by_edge_among_sisters(String identifier, boolean include_labels/* Labels bei Suche einschliessen*/, boolean ignore_case) {

  if(parent!=null) {

   for(int i = 0; i<parent.children.length; i++) {

    Node node = parent.children[i];

    if(node!=this&&node.edge!=null) {

     if(ignore_case) {

      if(node.edge.id.equalsIgnoreCase(identifier))
       return node;

      if(include_labels&&node.edge.label!=null)
       if(node.edge.label.equalsIgnoreCase(identifier))
        return node;

     } else { 
 
      if(node.edge.id.equals(identifier))
       return node;

      if(include_labels&&node.edge.label!=null)
       if(node.edge.label.equals(identifier))
        return node;

     }

    }

   }

  }

  return null;
 
 }


 /** sucht den hoechsten vorkommenden Index eines autom. erzeugten Identifizierers
    (wird nur einmal beim Oeffnen einer .cat-Datei aufgerufen) */
 void update_edge_id_no() {

  try {

   if(edge.id.startsWith(Edge.auto_prefix)) {
  
    long no = Long.parseLong(edge.id.substring(edge.id.lastIndexOf("_")+1));

    if(no>Edge.max_id_no)
	 Edge.max_id_no = no;

   }

   if(children!=null)
    for(int i = 0; i<children.length; i++) 
     children[i].update_edge_id_no();

  } catch(Exception e) {}  

 }


 /** liefert den absoluten (Datei-)Namen der den Knoten enthaltenden Include-Datei*/
 String get_path() {

  if(name!=null) {

   if(name.startsWith("file:")||name.startsWith("http:"))
	return name;

   File file = new File(name);

   if(!file.isAbsolute()&&parent!=null) {

    String parent_path = parent.get_path();

    if(parent_path!=null)
     file = new File((new File(parent_path)).getParent() ,name);

   }

   return file.getAbsolutePath();

  } else if(parent!=null)
   return parent.get_path();

  return null;  // (kann nie vorkommen)

 }


 /** sucht den include-Knoten, dessen (absoluter) Name path ist.
   Leider fkt. der Vergleich nicht, wenn (nur) der Vergleichs-Pfad 
   mit /../ zum darueberliegenden Verzeichnis verzweigt.
 */
 Node find_name(String path) {

  if(name!=null)
   if(get_path().equals(path))
	return this;

  if(children!=null)
   for(int k = 0; k<children.length; k++) {

	Node found = children[k].find_name(path);

    if(found!=null)
	 return found;

   }

  return null;

 }

 abstract Node copy(boolean copy_source) throws CloneNotSupportedException;


 abstract Node create(boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException;


 Node copy() throws CloneNotSupportedException {

  return copy(true); 

 }


 protected Object clone() throws CloneNotSupportedException {

  return copy();

 }



 /** Prueft, ob dieser Teilbaum aktivierte Kommentare zu Knoten enthaelt */
 boolean has_comments() {  

  if(!active())
   return false;

  if(help!=null)
   if(help.trim().length()>0)
    return true;

  if(children==null)
   return false;

  if(children.length==0)
   return false;

  for(int i = 0; i<children.length; i++)
   if(children[i].has_comments())
    return true;

  return false;
  
 }



}


/** Hilfsstruktur: hinfuehrende Kante zu einem Knoten ("leading edge")*/
class Edge implements Cloneable { 

 /** Praefix fuer autom. generierte Identifikatoren*/
 static String auto_prefix = "id_";  

 /** wird zur autom. Erstellung eindeutiger Kantenidentifizierer verwendet*/
 static long max_id_no = 1l;  

 /** Identifikator (muss vorhanden+eindeutig sein)*/
 String id; 

 /** Beschriftung (oder null)*/
 String label; 

 /** Hilfetext (oder null)*/
 String help; 



 /** id=null: Bezeichner automatisch generieren*/
 Edge(String label, String help, String id) {

  set_id(id!=null?id:auto_generate_id(label, null, AOT.Aot.document.root, null));

  set_label(label);

  set_help(help);

 }

 
 protected Object clone() throws CloneNotSupportedException {

  return copy(null);

 }


 /** additional_tree ist ein (Unter-)Baum, der zusaetzlich zum Dokumentenbaum 
  und dem Clipboard durchsucht werden muss, um die Eindeutigkeit von
  neu generierten Kanten-Identifikatoren zu gewaehrleisten
  (nur falls Node.unique_ids_all_over=true) */
 Edge copy(Node additional_tree) throws CloneNotSupportedException {
 
  return new Edge(label!=null?new String(label):null, help!=null?new String(help):null, 
	              new String(Edge.auto_generate_id(id.startsWith(auto_prefix)?label:id, null, AOT.Aot.document.root, additional_tree)));

 }


 void set_id(String id) {
 
  this.id = id;

 }


 /** prueft Kanten-Identifizierer auf Korrektheit (ohne Dubletten-Check) */
 static boolean check(String edge_id, Frame win) {

  if(edge_id.length()==0) {
  
   InfoDialog.info(AOT.APPLET_NAME+" Error", "Every edge must have an identifier", InfoDialog.OK, win, null);

   return false;

  }

  int i;

  for(i = 1; i<edge_id.length(); i++)
   if(!(edge_id.charAt(i)>='A'&&edge_id.charAt(i)<='Z'||edge_id.charAt(i)>='a'&&edge_id.charAt(i)<='z'||edge_id.charAt(i)>='0'&&edge_id.charAt(i)<='9'||edge_id.charAt(i)=='_'))
    break;

  if(i<edge_id.length()||((edge_id.charAt(0)<'a'||edge_id.charAt(0)>'z'))) {
  
   InfoDialog.info(AOT.APPLET_NAME+" Error", "An identifier must consist of letters and digits only and must start with a lower-case letter", InfoDialog.OK, win, "cats_fit.html#edge");

   return false;

  }

  return true;

 }

 void set_label(String label) {
 
  if(label!=null)
   if(label.length()==0)
    label = null;

  this.label = label;

 }


 void set_help(String help) {
 
  this.help = help;

 }

 
 public String toString() {

  String edge_str = new String(id);

  if(help!=null||label!=null)
   edge_str += "("+(label!=null?"label=\""+AOTParse.escapeString(label)+"\""+(help!=null?", ":""):"")
                  +(help!=null?"help=\""+AOTParse.escapeString(help)+"\"":"")
	          +")";

  return edge_str;

 }

 
 /** Erzeugt einen eindeutigen Bezeichner, basierend auf dem beliebigen String label (sollte, muss aber kein Kantenlabel sein)
     node: Zielknoten, universe: Start fuer die Suche, additional_tree: siehe Edge.copy()
  */
 static String auto_generate_id(String label, Node node/* Zielknoten*/, Node universe/*Start fuer die Suche*/, Node additional_tree/*siehe Edge.copy()*/) {
 
  String auto_generated_id = new String(auto_prefix);

  if(label!=null) {

   for(int i = 0; i<label.length(); i++)
    if((label.charAt(i)>='A'&&label.charAt(i)<='Z'||label.charAt(i)>='a'&&label.charAt(i)<='z'||label.charAt(i)>='0'&&label.charAt(i)<='9'))
     auto_generated_id += label.charAt(i);
  
   auto_generated_id = auto_generated_id.toLowerCase()+"_";

  } 

  /* langsam, aber schoenere Resultate:

  // Folgender Algorithmus numeriert neue Kanten fortlaufend beginnend mit 1 
  // (z.B. ... id_KanteX_1, id_KanteX_2, ..., id_KanteY_1, id_KanteY_2, ...).
  // Fuer sehr grosse Baeume ist er zu langsam. Als Ersatz koennte
  // man z.B. an auto_generated_id auch einfach den Wert eine globalen 
  // Zaehlervariablen anhaengen (s.u.).

  auto_generated_id += "1";

  int no = 1;
   
  for(;;) {
	  
   if(Node.unique_ids_all_over) {

    if(universe.find_node_by_edge(auto_generated_id, false, false, node)==null)
     if(AOT.Aot.clipboard.root.find_node_by_edge(auto_generated_id, false, false, node)==null)
 	 if(additional_tree!=null) {
      if(additional_tree.find_node_by_edge(auto_generated_id, false, false, node)==null)
       break;
     } else
      break;
   } else
    if(universe.find_node_by_edge_among_sisters(auto_generated_id, false, false)==null)
	 break;

   auto_generated_id = auto_generated_id.substring(0, auto_generated_id.lastIndexOf("_")+1)+(++no);

  }

  schnell: */
 
  auto_generated_id += max_id_no++;

  return auto_generated_id;

 }

}


class AND_Node extends Node { 
 

 AND_Node(Node parent, Edge edge, Node[] children, String help, boolean compressed) {
  
  super(parent, edge, children, help, compressed);

 }

 
 void deactivate() { 
  
  if(parent!=null)
   parent.set_child_deactivated(this);

  for(int i = 0; i<children.length; i++)
   children[i].deactivate();
 
 }


 boolean child_active(Node child) {
  
  return active();  // setzt sich u.U. bis zur Wurzel fort (vgl. aber OR-Knoten)

 }

 
 /** (siehe Bemerkung in class Node() zu dieser Methode) */
 String get_selection(String indent, int what) {

  if(what==Node.GET_COMMENTARIES&&!has_comments())
   return("(_)");

  if(children.length==0)
   return new String(""); // weil's sonst kein Prolog-Term wird

  String selection = new String("(");

  for(int i = 0; i<children.length; i++) {

   String child_sel = children[i].get_selection(indent+TAB, what);

   if(true||   (what!=Node.GET_COMMENTARIES||!child_sel.equals("_")))
    selection += indent+(what==GET_LABELS?("\""+AOTParse.escapeString(children[i].edge.label)+"\""):(what==GET_IDS?children[i].edge.id:("\""+AOTParse.escapeString(children[i].help)+"\"")))
                 +child_sel;
   else
    selection += indent+"_";

   if(i+1<children.length)
    selection += ",";

  }

  return selection+")";
  
 }

 
 String toString(Stack includes, String indent/*Praefix zur Einrueckung*/, boolean top) {

  if(!top&&includes!=null&&name!=null) {  // Include-Knoten sammeln
 
   includes.push(this);

   return edge.toString()+":include(\""+AOTParse.escapeString(name)+"\")";

  } else
   return (top?"":edge.toString()+":")+"and("+childrenToString(includes, indent)+attributes_toString()+")";
 
 }


/* synchronized !*/Area display(Point position, boolean active, View view) {

  if(!compressed)
   return new Area(view.draw_AND_Node(position, active, children.length==0, name, false),
                   view.draw_children(children, position, new Node[0], active));
  else {

   // Controls der inplace-Bereiche von Kindern loeschen:
   remove_inplace_areas();

   return new Area(view.draw_AND_Node(position, active, children.length==0, name, true),
                   view.edge_dimension());

  }

 }


 Node copy(boolean copy_source) throws CloneNotSupportedException {
  return create(this, copy_source, null, null); 

 }


 Node create(boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {

  return create(this, copy_source, subroot, parent); 

 }


 /** Diese Methode kopiert nicht nur, sondern fuehrt ggf. einen Cast durch (source darf
  also z.B. auch ein Oder-Knoten sein)
  Es wird dabei sichergestellt, dass jeder neue Kanten-Bezeichner ein Unikat ist
  Die Methode ist statisch, weil sie einen beliebigen Knoten source
  kopiert und nicht sich selbst. Daher kommt sie ohne "this" aus und kann
  ggf. vom Java-Compiler optimiert werden.*/
 static/*(!)*/ Node create(Node source, boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {
 
  AND_Node copy = new AND_Node(null, null, new Node[0], source.help!=null?new String(source.help):null, source.compressed);

  if(parent!=null)
   parent.add_child(copy);

  if(subroot==null) // subroot ist der gerade in Konstruktion befindliche Unterbaum;
   subroot = copy; // dieser muss bei der Generierung neuer Kanten-Ids beruecksichtigt werden

  copy.set_edge(copy_source?source.edge.copy(subroot):source.edge);

  if(source.children!=null)
   for(int i = 0; i<source.children.length; i++)
    source.children[i].create(copy_source, subroot, copy);

  if(source.name!=null)
   copy.set_name(new String(source.name));

  return copy;

 }

}


class OR_Node extends Node { // (kann man von class iOR ableiten, wenn diese mal stabil ist)
 
 /** =null: keine Auswahl */
 Node active_child; 


 OR_Node(Node parent, Edge edge, Node[] children, String help, boolean compressed) {
 
  super(parent, edge, children, help, compressed);

  active_child = null;

 }


 void deactivate() { 
  
  if(parent!=null)
   parent.set_child_deactivated(this); // Rueckmeldung an OR-parent

  if(active_child!=null)
   active_child.deactivate();
	 
 }


 void set_child_activated(Node child) { 

  if(active_child!=null&&active_child!=child)
   active_child.deactivate();

  active_child = child;

 }


 void set_child_deactivated(Node child) {  // *nach* Deaktivierung von child aufzurufen
  
  if(active_child==child)
   active_child = null;

 }


 boolean child_active(Node child) {
  
  return child==active_child;  // vgl. AND_Node.child_active()

 }


 /** (siehe Bemerkung in class Node() zu dieser Methode)*/
 String get_selection(String indent, int what) {

  if(what==Node.GET_COMMENTARIES&&!has_comments())
   return("(_)");

  if(active_child!=null) {

   return new String("("+indent+(what==GET_LABELS?("\""+AOTParse.escapeString(active_child.edge.label)+"\""):(what==GET_IDS?active_child.edge.id:("\""+AOTParse.escapeString(active_child.help)+"\"")))
                     +active_child.get_selection(indent+TAB, what)+")");
  } else
   return new String("(_)");

 }


 String toString(Stack includes, String indent, boolean top) {

  if(!top&&includes!=null&&name!=null) {  // Include-Knoten sammeln
 
   includes.push(this);

   return edge.toString()+":include(\""+AOTParse.escapeString(name)+"\")";

  } else
   return (top?"":edge.toString()+":")+"or("+childrenToString(includes, indent)+attributes_toString()+")";

 }


/* synchronized !*/Area display(Point position, boolean active, View view) {

  if(!compressed)
   return new Area(view.draw_OR_Node(position, active, active_child, name, false),
                   view.draw_children(children, position, active_child, false));
  else {

   // Controls der inplace-Bereiche von Kindern loeschen:
   remove_inplace_areas();

   return new Area(view.draw_OR_Node(position, active, active_child, name, true),
                   view.edge_dimension());

  }

 }


 void remove_child(Node child) { 
  
  if(child==active_child)
   child.deactivate();

  super.remove_child(child);

 }


 void replace_child(Node child, Node node) { 
  
  if(child==active_child)
   child.deactivate();

  super.replace_child(child, node);

 }


 /** copy(...) befindet sich nicht in class Node, weil create() statisch sein muss (siehe class Properties) */
 Node copy(boolean copy_source) throws CloneNotSupportedException {
  return create(this, copy_source, null, null); 

 }


 Node create(boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {

  return create(this, copy_source, subroot, parent); 

 }


 /** vgl. AND_Node.create() */
 static Node create(Node source, boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {
 
  OR_Node copy = new OR_Node(null, null, new Node[0], source.help!=null?new String(source.help):null, source.compressed);

  if(parent!=null)
   parent.add_child(copy);

  if(subroot==null)
   subroot = copy;

  copy.set_edge(copy_source?source.edge.copy(subroot):source.edge);

  if(source.children!=null)
   for(int i = 0; i<source.children.length; i++)
    source.children[i].create(copy_source, subroot, copy);

  if(source.name!=null)
   copy.set_name(new String(source.name));

  copy.active_child = null; // (Mitkopieren im Fall source=Oder-Knoten waere nicht sinnvoll)

  return copy;

 }

}


/** inklusives-Oder -Knoten (multi-decision nodes) */
class iOR_Node extends Node {  
  
 /** =[0]: keinerlei Auswahl  (=null: nicht definiert!) */
 Node[] active_childs; 


 iOR_Node(Node parent, Edge edge, Node[] children, String help, boolean compressed) {
 
  super(parent, edge, children, help, compressed);

  active_childs = new Node[0];

 }


 void deactivate() { 
  
  if(parent!=null) {

   parent.set_child_deactivated(this); // Rueckmeldung an iOR-parent

   Node[] old_active_childs = new Node[active_childs.length];

   for(int i = 0; i<active_childs.length; i++)
    old_active_childs[i] = active_childs[i];

   for(int i = 0; i<old_active_childs.length; i++)
    old_active_childs[i].deactivate();

  }
	 
 }


 void set_child_activated(Node child) { 

  int i;

  Node[] xtd_active_childs = new Node[active_childs.length+1];

  for(i = 0; i<active_childs.length; i++)
   if(active_childs[i]==child)
	return;
   else
    xtd_active_childs[i] = active_childs[i];

  xtd_active_childs[i] = child;

  active_childs = xtd_active_childs;

 }


 void set_child_deactivated(Node child) { 
  
  Node[] less_children = new Node[active_childs.length-1];
  
  int j = 0;

  for(int i = 0; i<active_childs.length; i++)
   if(active_childs[i]!=child)
    less_children[j++] = active_childs[i];

  active_childs = less_children;
	 
 }


 boolean child_active(Node child) {

  for(int i = 0; i<active_childs.length; i++)
   if(active_childs[i]==child)
    return true;

  return false;
 
 }


 /** (siehe Bemerkung in class Node() zu dieser Methode)*/
 String get_selection(String indent, int what) {

  if(what==Node.GET_COMMENTARIES&&!has_comments())
   return("(_)");

  if(active_childs.length==0)
   return new String("(_)");

  String selection = new String("(");

/* alt(-ernativ):

  for(int i = 0; i<active_childs.length; i++) {
   
   selection += indent+active_childs[i].edge.id
                +active_childs[i].get_selection(indent+TAB, labels);

   if(i+1<active_childs.length)
    selection += ",";

  }
*/
  // bewahrt die Reihenfolge im gesamten Kindervektors:

  for(int i = 0; i<children.length; i++)
   for(int j = 0; j<active_childs.length; j++)
	if(children[i]==active_childs[j]) {

	 selection += indent+(what==GET_LABELS?("\""+AOTParse.escapeString(active_childs[j].edge.label)+"\""):(what==GET_IDS?active_childs[j].edge.id:("\""+AOTParse.escapeString(active_childs[j].help)+"\"")))
                  +active_childs[j].get_selection(indent+TAB, what);

     selection += ",";

	 break;

    }

  if(selection.endsWith(","))
   selection = selection.substring(0, selection.length()-1);

  return selection+")";
  
 }


 String toString(Stack includes, String indent, boolean top) {

  if(!top&&includes!=null&&name!=null) {  // Include-Knoten sammeln
 
   includes.push(this);

   return edge.toString()+":include(\""+AOTParse.escapeString(name)+"\")";

  } else
   return (top?"":edge.toString()+":")+"ior("+childrenToString(includes, indent)+attributes_toString()+")";

 }
 

/* synchronized !*/Area display(Point position, boolean active, View view) {

  if(!compressed)
   return new Area(view.draw_iOR_Node(position, active, active_childs, name, false),
                   view.draw_children(children, position, active_childs, false));
  else {

   // Controls der inplace-Bereiche von Kindern loeschen:
   remove_inplace_areas();

   return new Area(view.draw_iOR_Node(position, active, active_childs, name, true),
                   view.edge_dimension());

  }

 }


 void remove_child(Node child) { 
  
  super.remove_child(child);

  child.deactivate();  // weil das Kind evtl. von woanders noch referenziert wird!

 }


 void replace_child(Node child, Node node) { 
  
  super.replace_child(child, node);

  child.deactivate(); 

 }


 /** copy(...) befindet sich nicht in class Node, weil create() statisch sein muss (siehe class Properties) */
 Node copy(boolean copy_source) throws CloneNotSupportedException {

  return create(this, copy_source, null, null); 

 }


 Node create(boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {

  return create(this, copy_source, subroot, parent); 

 }


 /** vgl. AND_Node.create() */
 static Node create(Node source, boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {
 
  iOR_Node copy = new iOR_Node(null, null, new Node[0], source.help!=null?new String(source.help):null, source.compressed);

  if(parent!=null)
   parent.add_child(copy);

  if(subroot==null)
   subroot = copy;

  copy.set_edge(copy_source?source.edge.copy(subroot):source.edge);

  if(source.children!=null)
   for(int i = 0; i<source.children.length; i++)
    source.children[i].create(copy_source, subroot, copy);

  if(source.name!=null)
   copy.set_name(new String(source.name));

  copy.active_childs = new Node[0]; // (Mitkopieren im Fall source=Oder-Knoten waere nicht sinnvoll)

  return copy;

 }

}


class Text_Node extends Node { 
 
 /** darf nicht null sein */
 String text;

 /** vorgegebene Groesse des Editorfensters fuer text */
 private int rows, columns;

 /** optionales Editierfeld fuer Inplace-Darstellung */
 TextLeafArea input_area = null;

 /** optionale(-r) Button (im inplace-Bereich) zum Oeffnen eines Dokuments text */
 OpenButton open_button = null;

 /** optionale(-r) Buttons (im inplace-Bereich) zum Navigieren */
 OpenButton back_button = null, forward_button = null;


 Text_Node(Node parent, Edge edge, String help, boolean compressed, String text, int rows, int columns) {

  super(parent, edge, null, help, compressed);

  set_leaf_text(text);
	  
  set_rows(rows);
  
  set_columns(columns);
  
 }


 void set_leaf_text(String text) { // auch HTML-Text ist hier erlaubt

  View view = null;

  if(input_area!=null) {

   view = ((View)(input_area.getParent().getParent()));

   if(this.text!=null)
    if(!this.text.equals(text)) {

     view.document.set_selection_modified(true);

 	 view.document.set_modified(true);

    }

   this.text = text.trim();

   if(input_area.text_comp!=null&&input_area.html_panel==null&&!is_HTML()) 
   // einfache Lsng. um unnoetige Neudarstellung zu vermeiden
    input_area.text_comp.setText(text);
   else
    remove_inplace_areas();

  } else
   this.text = text.trim();

  if(view!=null&&input_area==null) {
 
   open_inplace_area(view.inplace_panel);
 
   input_area.move_inplace(1, 1); // Dummy-Positionierung (es muss vor ..layout().. immer eine Pos. angegeben sein)

   input_area.add_buttons(false); // zum Anzeige-/Eingabe-Bereich passende Buttons (z.B. Open, <<<, >>>) hinzufuegen
 
   view.inplace_panel.layout(); 

  }

 }


 void set_rows(int rows) {

  if(rows<1)  // Check, da hier evtl. der Anwender seine Finger dran hatte
   rows = 1;

  this.rows = rows;

 }


 void set_columns(int columns) {

  if(columns<1)  // Check, da hier evtl. der Anwender seine Finger dran hatte
   columns = 1;

  this.columns = columns;

 }

 int get_rows() {

  return rows;

 }


 int get_columns() {

  return columns;

 }


 boolean single_line() {

  return rows<=1&&text.indexOf("\n")<0;

 } 


 void deactivate() { 
  
  if(parent!=null)
   parent.set_child_deactivated(this);

 }


 boolean child_active(Node child) { return false; }


 String toString(Stack includes, String indent, boolean top) {

  if(!top&&includes!=null&&name!=null) {  // Include-Knoten sammeln
 
   includes.push(this);

   return edge.toString()+":include(\""+AOTParse.escapeString(name)+"\")";

  } else
   return (top?"":edge.toString()+":")+"text(\""+AOTParse.escapeString(text)+"\""+attributes_toString()+")";

 }


 String attributes_toString() { 

  String attributes = super.attributes_toString();

  if(rows!=AOTreeString.DEFAULT_TEXT_ROWS||columns!=AOTreeString.DEFAULT_TEXT_COLUMNS) {
  // (wenn beide Werte den Defaults entsprechen, kann man davon ausgehen,
  // dass man sie nicht explizit aufgefuehrt sehen moechte, oder?)

   attributes += ", rows="+rows+", columns="+columns;

  }

  return attributes;

 }


 /** entfernt inplace-Bereich */
 void remove_inplace_areas() { 

  if(input_area!=null) {

   ((View)(input_area.getParent().getParent())).remove_atom(input_area);

   input_area = null;

  }

  if(open_button!=null) {

   ((View)(open_button.getParent().getParent())).remove_atom(open_button);

   open_button = null;

  }

  if(back_button!=null) {

   ((View)(back_button.getParent().getParent())).remove_atom(back_button);

   back_button = null;

   ((View)(forward_button.getParent().getParent())).remove_atom(forward_button);

   forward_button = null;

  }

 }
	 
/* synchronized !*/Area display(Point position, boolean active, View view) {

  return view.draw_Text_Node(position, active, text, name, this);

 }


 /** (siehe Bemerkung in class Node() zu dieser Methode)*/
 String get_selection(String indent, int what) {
 
  if(what==Node.GET_COMMENTARIES&&!has_comments())
   return("(_)");

  if(text.length()<1)
   return new String("(_)");
  else
   return new String("(\""+AOTParse.escapeString(text)+"\")");

  // beim Text "" wird _ geliefert, da sowas mit 99%tiger Sicherheit in einer
  // Anfrage Verwendung findet.

 }


 /** copy(...) befindet sich nicht in class Node, weil create() statisch sein muss (siehe class Properties) */
 Node copy(boolean copy_source) throws CloneNotSupportedException {

  return create(this, copy_source, null, null); 

 }


 Node create(boolean copy_source, Node subroot, Node parent) throws CloneNotSupportedException {

  return create(this, copy_source, subroot, parent); 

 }


 /** (siehe AND_Node.create())*/
 static Node create(Node source, boolean copy_source, Node subr, Node parent) throws CloneNotSupportedException {
 
  Node subroot = subr;

  Text_Node copy = source instanceof Text_Node?
   new Text_Node(null, null, source.help!=null?new String(source.help):null, source.compressed, new String(((Text_Node)source).text), ((Text_Node)source).rows, ((Text_Node)source).columns)
  :
   new Text_Node(null, null, source.help!=null?new String(source.help):null, source.compressed, new String(" "), AOTreeString.DEFAULT_TEXT_ROWS, AOTreeString.DEFAULT_TEXT_COLUMNS);

  if(parent!=null)
   parent.add_child(copy);

  if(subroot==null)
   subroot = copy;

  copy.set_edge(copy_source?source.edge.copy(subroot):source.edge);

  if(source.name!=null)
   copy.set_name(new String(source.name));

  return copy;

 }


 /** Blatt-Text als eigenes Fenster oeffnen */
 void edit() { 

   // Plain oder HTML text ?

   String content = /*new String(*/text;

   if(is_HTML()) { 

    AOT.online_help("internal_.html", content);

   } else // Plain text
    new TextLeafDialog(this);

 }


 void open_inplace_area(Panel inplace_panel) {

  input_area = new TextLeafArea(this, inplace_panel);

 }


 boolean is_HTML() {  //(kein exakter Test)

  return text.startsWith("<!")||text.toUpperCase().startsWith("<HTML")||text.toUpperCase().startsWith("<HEAD");

 }

}



/** inplace-Bereich zur Anzeige und Eingabe eines Blatt-Texts direkt im Baumdiagramm.
    Z.Zt. unterstuetzte Inhalte: Plain- und HTML-Text */
class TextLeafArea extends Panel {

 Text_Node node; 

 /** Plain-Text: je nach Anzahl der Zeilen TextArea oder TextField */
 TextComponent text_comp; 

 /** eigentl. kein Panel; html_panel.panel1 wird im Konstruktor BrowserInterface hinzugefuegt */
 BrowserInterface html_panel;  

 Frame frame;


 TextLeafArea(Text_Node node, Panel inplace_panel) {

  super(); //(?)

  this.node = node;

  if(node.is_HTML()) {

   inplace_panel.add(this);

   html_panel = new BrowserInterface(this, node.get_columns()*12, 12/*<20!!!*/*Math.max(/*4*/1, node.get_rows())+10, node.text, "Center", true, "internal_.html");
                                                   // ^praktisch beliebig, da ueber die Schriftgroesse im Panel nichts bekannt ist ^...

   text_comp = null;

  } else {

   if(node.get_rows()>1)
    text_comp = new TextArea(node.text, node.get_rows(), node.get_columns());
   else
    text_comp = new TextField(node.text, node.get_columns());

   add("Center", text_comp);
 
   text_comp.setFont(new Font("Courier", Font.PLAIN, 12));

   inplace_panel.add(this);
 
  }
 
 }


 public void requestFocus() {

  if(text_comp!=null)
   text_comp.requestFocus();
  else if(html_panel!=null)
   html_panel.panel1.requestFocus();
  else
   super.requestFocus();
  
 }


 void move_inplace(int pos_x, int pos_y) {

  ((View)(getParent().getParent())).move_inplace(this, pos_x, pos_y, 
    text_comp!=null?preferredSize().width:node.get_columns()*12+20,  
    text_comp!=null?text_comp.preferredSize().height+4:12*Math.max(/*4*/1, node.get_rows())+30);

  validate();

 }

 /** diese Funktion ist kritisch, weil es Ueberschneidungen mit dem Message-
 Handling des Views gibt! */
 public boolean handleEvent(Event e) { 

  if(getParent()==null)
   return false;

  View view = ((View)(getParent().getParent()));

  if(view==null) //(?)
   return false;

  Document doc = view.document;

  if(e.id==Event.KEY_PRESS) {
	  
   if(e.key==27) { // <Esc>

    if(text_comp!=null&&node.text!=null)
     text_comp.setText(node.text); // Abbruch; alten Inhalt wiederherstellen

    view.canvas.requestFocus();

    return true;

   } else if(e.key==10) {  // Enter (unter jview kommt hier das "einfache Enter" allerdings nicht an, was aber nix ausmacht)

    if(view.recent_neuro!=null&&node==view.recent_neuro.node) { // das Textfeld ist gemeint

     if(node.get_rows()<=1||node.get_rows()>1&&e.controlDown()) { // Eingabe bestaetigen

      if(text_comp!=null) {

       node.set_leaf_text(text_comp.getText());

       System.out.println("<Enter> pressed, changes confirmed");

      }
 
      if(doc!=null)
       doc.display_different();

      view.canvas.requestFocus();
	
 	  return true;
 
     } else {

	  if(AOT.java_102)
       // *** nur unter Java 1.0 als Code(!) mgl.: 
 	   text_comp.getPeer().handleEvent(e);
      // Alternative unter Java 1.1 fuer ^ ?
      else if(text_comp!=null) {

       System.out.println("Internal warning: getPeer() not invoked because of incompatible Java release.");

       return text_comp.handleEvent(e); //(nutzt normalerweise nix)

      }

 	  return true;
     
     }
     
    } else
     return view.handleEvent(e);

   }

  } else if(e.id==Event.KEY_ACTION) {  // nicht-ASCII Tasten:

	if(text_comp!=null)
     switch(e.key) {

	 case Event.LEFT: case Event.RIGHT: case Event.HOME: case Event.END: 

	  if(AOT.java_102)
       // *** nur unter Java 1.0 als Code(!) mgl.: 
 	   text_comp.getPeer().handleEvent(e);
      // Alternative unter Java 1.1 fuer ^ ?
      else {

       System.out.println("Internal warning: getPeer() not invoked because of incompatible Java release.");

       return text_comp.handleEvent(e); //(nutzt normalerweise nix)

      }

 	  return true;

     // fuehrt zu Problemem:  
     /*case Event.UP: case Event.DOWN:

      if(node.get_rows()>1) {

 	   if(AOT.java_102)
	   // *** nur unter Java 1.0 als Code(!) mgl.: 
 	   text_comp.getPeer().handleEvent(e);
       // Alternative unter Java 1.1 fuer ^ ?

 	   return true;

      }*/

	}

  } else if(e.id==Event.LOST_FOCUS) {  // *** wird von Win95 nicht erzeugt!

   /*...*/

  } else if(e.id==Event.GOT_FOCUS) {  // *** wird von Win95 nicht erzeugt!

   /*...*/

  }

  boolean retval = false;

  if(text_comp!=null) {

   retval = text_comp.handleEvent(e);

   if(e.id==Event.KEY_PRESS) {
    ;//node.set_leaf_text(getText()); 
    // ^ aktivieren fuer staendige Aktualisierung auch ohne <Enter>

   }

  }

  add_buttons(true);

  return retval;

 }


 public boolean mouseDown(Event evt, int x, int y) {  
 /* *** Ereignis kommt nie an (jedenfalls unter Win95)! */

  View view = ((View)(getParent().getParent()));

  /* fuehrt evtl. zu unerwuenschter Markierung, wenn zwischenzeitlich das Dokument wechselt
  if(node!=view.recent_neuro.node)
   view.mark_edge(view.find_neuro(node), false);
  */
  System.out.println("Internal warning: hit TextLeafArea.mouseDown()");

  return false;

 }


 boolean add_buttons(boolean show) { // Ggf. Knopf "Open..." hinzufuegen

  View view = ((View)(getParent().getParent()));

  if(view==null)  //(?)
   return false;

  Document doc = view.document;

  boolean show_buttons = false;

  int cursor_pos = text_comp!=null?text_comp.getSelectionStart():0;

  if(node.open_button==null) {

   node.open_button = new OpenButton(node, "Open ...");

   view.add_inplace(node.open_button);

   node.open_button.move_inplace(0, 0); // Dummy-Positionierung

   show_buttons = true;

  }

  if(node.back_button==null&&node.is_HTML()) { 

   node.back_button = new OpenButton(node, "<<");

   view.add_inplace(node.back_button);

   node.back_button.move_inplace(0, 0); 

   node.forward_button = new OpenButton(node, ">>");

   view.add_inplace(node.forward_button);

   node.forward_button.move_inplace(0, 0);

   show_buttons = true;

  }

  if(show_buttons) {

   if(show) {

    doc.display_different();

    requestFocus();

    if(text_comp!=null)
     text_comp.select(cursor_pos, cursor_pos);

   }

   return true;

  }

  return false;

 }


 public boolean mouseMove(Event evt, int x, int y) {  
 //***fkt. unter Win95 (u.a.?) nicht, da das Event nur GUI-intern verwaltet wird

  if(getParent()==null)
   return false;

  String help = node.help!=null?node.help:new String();

  View view = ((View)(getParent().getParent()));

  if(view==null)
   return false;

  Document doc = view.document;

  if(doc==null)
   return false;

  doc.display_decision_help(help);

  if(help.indexOf("\n")>=0)
   help = help.substring(0, help.indexOf("\n"))+"..."; 
  
  view.decision_help_label.setText(help);

  return false;

 }

}


/** Knopf zum Oeffnen des Inhaltes eines Textknotens als Fenster, wird optional an inplace-Bereich(-e) angehaengt */
class OpenButton extends Button {

 Text_Node node; 
 
 String label;


 OpenButton(Text_Node node, String label) {

  super(label); // (fuer label ist alles erlaubt ausser "Open" !!)

  this.label = label;

  resize(preferredSize().width, node.input_area.preferredSize().height);

  this.node = node;

 }


 void move_inplace(int pos_x, int pos_y) {

  ((View)(getParent().getParent())).move_inplace(this, pos_x, pos_y, preferredSize().width, preferredSize().height);

 }


 public boolean handleEvent(Event e) {

  if(getParent()==null)
   return false;

  View view = ((View)(getParent().getParent()));

  if(view==null)
   return false;

  if(e.id==Event.ACTION_EVENT) {

   if(label.startsWith("<")&&/*?:*/node.input_area!=null&&node.input_area.html_panel!=null)
    node.input_area.html_panel.goBack();
   else if(label.startsWith(">")&&/*?:*/node.input_area!=null&&node.input_area.html_panel!=null)
    node.input_area.html_panel.goForward();
   else
    node.edit();
 
    return true;

   }

  return false;

 }

}


/** Fenster zur Eingabe von Blatttext*/
class TextLeafDialog extends Dialog {

 /** anfaengliche Position*/
 final static int POS_X = 410, POS_Y = 210;

 boolean in_reshape = false;

 TextComponent textarea;

 Text_Node node;

 Button ok_button, cancel_button, help_button;


 TextLeafDialog(Text_Node node) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Please enter text", false);

  this.node = node;

  textarea = new TextArea(node.text, node.get_rows(), node.get_columns());

  textarea.setFont(new Font("Courier", Font.PLAIN, 12));

  add("Center", textarea);

  Panel p = new Panel();

  p.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));

  ok_button = new Button("     Ok     ");

  p.add(ok_button);

  cancel_button = new Button(" Cancel ");

  p.add(cancel_button);

  help_button = new Button(" Help ");

  p.add(help_button);

  p.setBackground(Color.white);

  add("South", p);

  pack(); 

  move(POS_X,POS_Y);

  show();

  move(POS_X,POS_Y); // *** sonst klappt Positionierung evtl. nicht (!)

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.KEY_PRESS&&e.key==27||e.id==Event.WINDOW_DESTROY) {

   hide();

   dispose();

   AOT.Aot.document.view.canvas.requestFocus();

   return true;

  }

  if(e.id==Event.ACTION_EVENT) {

   if(e.target==ok_button) {

    /*if(AOT.Aot.access_mode>=AOT.SUPERUSER) // *** fkt. nicht:
     node.set_leaf_text(textarea.getText(), textarea.getRows(), textarea.getColumns());
     // *** getRows()... wird nie aktualisiert!! (jedenfalls unter Windows 95)
    else */
     node.set_leaf_text(textarea.getText());

	AOT.Aot.document.set_selection_modified(true);
	
    AOT.Aot.document.set_modified(true);
 
	AOT.Aot.document.display_different();
	  
    hide();

    dispose();

    AOT.Aot.document.view.canvas.requestFocus();

    return true;

   } else if(e.target==cancel_button) {

    hide();

    dispose();

    AOT.Aot.document.view.canvas.requestFocus();

    return true;
  
   } else if(e.target==help_button) {

    AOT.online_help("cats_sel.html");
      
    return true;

   }

  } else if(e.id==Event.KEY_ACTION) { 

   //...
  
  }

  return super.handleEvent(e);

 }

}


/** Und/Oder-Baum in Textform (Dateityp .cat)
   
    Grammatik siehe Datei AOTParse.jj (fuer Parser-Generator JavaCC) */

class AOTreeString { // 


 /** Default-Ausmass eines Blatttextes (in Zeichen) */
 static final int DEFAULT_TEXT_ROWS = 1;  

 static final int DEFAULT_TEXT_COLUMNS = 60;

 boolean error;

 int error_line, error_column;

 int error_pos;

 Document parent_doc;

 ParseError err_state;

 String text;

 /** optionales Editor-Fenster zur Darstellung von text*/
 AOTreeStringDialog dialog = null; 
 
 
 AOTreeString(Document parent_doc, String text) {

  this.parent_doc = parent_doc;

  set_text(text);

 }


 /** Baum-Konstruktion*/
 Node parse(Node parent, Edge edge, StringBuffer message, Constraint[][] consp, Bookmark[][] bookmp) {  

  StringBufferInputStream stringstream = new StringBufferInputStream(text);

  error = false;

  AOTParse parse = new AOTParse(stringstream);

  parse.token_source.error_after = null;
 
  try {

   return parse.document(parent, edge, consp, bookmp);

  } catch (ParseError xcpt) {

   error = true;

   error_pos = parse.token_source.input_stream.tokenBegin;
   // ^dazu muss "private" vor dem Member input_stream in AOTParseTokenManager entfernt werden!

   err_state = xcpt;

   if(parse.token_source.error_after==null) { // syntaktischer Fehler:
    
	if(parse.error_string!=null) {

     String expct = new String();

     for(int i = 0; i<parse.expected_tokens.length; i++)
 	  expct += parse.expected_tokens[i]+(i<parse.expected_tokens.length-1?" or ":"");

     message.append("found \""+parse.error_string+"\" instead of "+expct+"\nat line "+(error_line = parse.error_line)+", column "+(error_column = parse.error_column));

	} else
	 message.append("(unknown reason)");

   } else {  // lexikalischer Fehler

    message.append("invalid character " /*+"after \""+parse.token_source.error_after+"\"" << *** wg. Fehler in JavaCC leer*/
		/* stattdessen:*/ +"\""+parse.token_source.input_stream.GetImage().substring(0,1)+"\""
		+"\nat line "+(error_line = parse.token_source.error_line)+", column "+(error_column = parse.token_source.error_column));

   }

   return null; 

  }

 }


 /** erweiterte Fehlerinformationen liefern*/
 ParseError get_err_state() {
  
  return err_state; // definiert, falls parse() fehlgeschlagen ist

 }


 /** Zeigt Quelltext in einem modeless-Client-Fenster an (editierfaehig, siehe class AOTreeStringDialog)*/
 void display() {

  if(dialog==null)
   dialog = new AOTreeStringDialog(this);
    
  dialog.show();

  dialog.requestFocus();

 }

 
 public String toString() { 

  return new String(text);

 }


 void set_text(String text) {

  this.text = text;

  if(dialog!=null)
   dialog.refresh();

 }


 void parse(String text) {

  String current_directory_before = AOT.current_directory!=null?new String(AOT.current_directory):null;

  AOT.set_dir(parent_doc.name()); // damit nachfolgende Include-Dateien mit
  			                      // relativem Pfad bzgl. dem Verzeichnis von .name geoeffnet werden

  set_text(text); //(?)

  parent_doc.parse(null);

  AOT.current_directory = current_directory_before;

 }

}


/** Categories text view */
class AOTreeStringDialog extends Dialog {

 AOTreeString parent;

 TextArea textarea;

 Button refresh_button, parse_button, cancel_button;
 
 Checkbox include_switch;


 AOTreeStringDialog(AOTreeString parent) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Categories text view  ["+parent.parent_doc.name()+"]", false);

  this.parent = parent;

  textarea = new TextArea(parent.text, 30, 60);

  textarea.setFont(new Font("Courier", Font.PLAIN, 14));

  add("Center", textarea);

  Panel p = new Panel();

  p.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));

  refresh_button = new Button(" Refresh ");

  p.add(refresh_button);

  parse_button = new Button(" Parse ");

  p.add(parse_button);

  cancel_button = new Button(" Close ");

  p.add(cancel_button);

  include_switch = new Checkbox("Expand includes");

  p.add(include_switch);

  p.setBackground(Color.white);

  add("South", p);

  pack();

 }


 void refresh() {

  textarea.setText(parent.text);

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY) {

   hide();

   dispose();

   parent.dialog = null;

   return true;

  } else if(e.id==Event.ACTION_EVENT) {

   if(e.target==refresh_button) {

    parent.parent_doc.display_source(include_switch.getState());

    return true;

   } else if(e.target==parse_button) {

    parse(textarea.getText()); 

	return true;

   } else if(e.target==cancel_button) {

    hide();

    dispose();

    parent.dialog = null;

    return true;

   } else if(e.target==include_switch)
    parent.parent_doc.display_source(include_switch.getState());

  }

  return super.handleEvent(e);

 }


 void parse(String text) {

  parent.parse(text);

 }


 void put_cursor_on_error_pos() {

  textarea.select(parent.error_pos, parent.error_pos);

 }

}


/** Festlegen bzw. Aendern der Eigenschaften eines Node-Objekts
   (modeless, d.h. die Box kann offen bleiben, waehrend man einen anderen Knoten selektiert) */
class PropertiesDialog extends Frame {
 
 Node node, previous_node; 

 Document doc;

 /** tatsaechliche Darstellung (getrennt, weil das Layout automatisch
  vom MS J++ Resource-Wizard kreiert wurde)*/
 PropsDialog dialog; 

 /** um ggf. eine Warnung anzuzeigen, bevor der Dialog verlassen wird*/
 boolean modified; 

 RefreshPropsDialog alert = null;


 PropertiesDialog(Node node, Document doc) {
  
  //super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Properties", false);
  super("Properties");

//AOT.mouse_ban = 3;

  this.node = node;

  this.doc = doc;

  modified = false;

  setFont(new Font("Dialog", Font.PLAIN, 12)); // (Dummy; wenn fehlt => Absturz)

  dialog = new PropsDialog(this);

  dialog.CreateControls();

  show();

  refresh();

  dialog.edge_label.requestFocus();

  previous_node = null;

 }


 /** fuellt die Controls im Dialog mit den Attributen von node*/
 void refresh() {
 
  if(modified) {

   if(alert==null) {

    alert = new RefreshPropsDialog(AOT.APPLET_NAME+" Warning", "Settings in Properties-dialogue could have been modified.\nActivate them for the previous editable-object?", InfoDialog.YES|InfoDialog.NO, this);

    alert.show();

   }

  } else
   read_properties();

 }


 /** zum Merken des editable-objects und dessen Vorgaenger*/
 void set_node(Node node) { 

  if(this.node!=node) {

   previous_node = this.node;

   this.node = node;

  }

 }


 /** siehe RefreshPropsDialog*/
 void read_properties() { 

  dialog.edge_id.setText(node.edge.id);

  if(node.edge.label!=null)
   dialog.edge_label.setText(node.edge.label);
  else
   dialog.edge_label.setText("");

  if(node.edge.help!=null)
   dialog.edge_help.setText(node.edge.help);
  else
   dialog.edge_help.setText("");

  if(node.help!=null)
   dialog.node_help.setText(node.help);
  else
   dialog.node_help.setText("");

  if(node.name!=null)
   dialog.node_name.setText(node.name);
  else
   dialog.node_name.setText("");

  if(node instanceof Text_Node) {

   dialog.text.setText(((Text_Node)node).text);

   dialog.text.enable();

   dialog.rows.setText(String.valueOf(((Text_Node)node).get_rows()));

   dialog.rows.enable();

   dialog.Cols/*J++ ResEditor akzeptierte keine Kleinschreibung cols (!)*/
	          .setText(String.valueOf(((Text_Node)node).get_columns()));

   dialog.Cols.enable();

  } else {

   dialog.text.setText("");

   dialog.text.disable();

   dialog.rows.disable();

   dialog.Cols.disable();

  }

  dialog.type_and.setState(node instanceof AND_Node && node.children!=null && node.children.length>0);

  dialog.type_or.setState(node instanceof OR_Node);

  dialog.type_ior.setState(node instanceof iOR_Node);

  dialog.type_text.setState(node instanceof Text_Node);

  dialog.type_and_leaf.setState(node instanceof AND_Node&&(node.children==null||node.children.length==0));

  dialog.edge_id_auto.setState(false);

  dialog.edge_id.enable();

 }


 /** Rueckgabe false: Ungueltige Eingabe (Dialog offen lassen)*/
 boolean activate_changes() { 

  if(node instanceof Text_Node) 
   ((Text_Node)node).remove_inplace_areas(); // sonst Exception!

  String edge_id = dialog.edge_id.getText().trim();

  String edge_label = dialog.edge_label.getText().trim();

  int i;
  
  if(dialog.edge_id_auto.getState()) // Kanten-Identifizierer automatisch generieren
   dialog.edge_id.setText(edge_id = Edge.auto_generate_id(edge_label, node, doc.root, null));

  if(!Edge.check(edge_id, this)) {

   dialog.edge_id.show();

   dialog.edge_id.requestFocus();

   return false;

  }


  Node dublette;

  if(Node.unique_ids_all_over) { // andernfalls wird nur geprueft, ob der Id. unter seinen Geschwistern eindeutig ist
   
   // Ist der Identifizierer im aktuellen Dokument eindeutig?

   dublette = doc.root.find_node_by_edge(edge_id, false, false, node, false, false); 
   // (^ dieser Test fkt. nur unter der Voraussetzung, dass nicht bereits vorher eine Dublette
   // vorhanden war)
	  
   if(dublette==null)
    dublette = AOT.Aot.clipboard.root.find_node_by_edge(edge_id, false, false, node, false, false); 

  } else
   dublette = node.find_node_by_edge_among_sisters(edge_id, false, false);

  if(dublette!=null) {

   InfoDialog.info(AOT.APPLET_NAME+" Error (Properties dialog)", "An edge-identifier must be unique"+(Node.unique_ids_all_over?"":" among his neighbours"), InfoDialog.OK, this, "cats_fit.html#edge");

   dialog.edge_id.show();

   dialog.edge_id.requestFocus();

   return false;

  }

  node.edge.set_id(edge_id); 

  if(edge_label.length()>0)
   node.edge.set_label(edge_label); 
  else
   node.edge.set_label(null);

  if(dialog.edge_help.getText().trim().length()>0)
   node.edge.set_help(dialog.edge_help.getText().trim()); 
  else
   node.edge.set_help(null);

  String name = dialog.node_name.getText().trim();

  if(name.length()>0&&!name.equals(node.name)) {

   node.set_name(null);

   String path;

   if(name.startsWith("file:")||name.startsWith("http:")) {

	path = name;

    OpenIncludeDialog include_question = new OpenIncludeDialog(AOT.APPLET_NAME+" (Properties dialog)", 
 		                                  "Replace the marked subtree with the URL-contents?", InfoDialog.YES|InfoDialog.NO, path, name, doc);

	include_question.show(); 

   } else {

    File file = new File(name);

    if(!file.isAbsolute()) {

	 String parent_path = node.get_path();

     System.out.println("Include-name "+name+" refers to directory "+new File(parent_path).getParent());
 
	 file = new File(new File(parent_path).getParent(), name);

    }

    if(file.exists()) { // *** evtl. problematisch, falls Ablauf in Web-Browser
     // Ist eine Datei dieses Namens bereits vorhanden, fragen, ob ihr Inhalt 
     // in das Dokument expandiert werden soll:
 
     OpenIncludeDialog include_question = new OpenIncludeDialog(AOT.APPLET_NAME+" (Properties dialog)", 
 		                                  "The file "+file.getAbsolutePath()+" exists. Replace the marked subtree with its contents?", InfoDialog.YES|InfoDialog.NO, file.getAbsolutePath(), name, doc);

	 include_question.show(); 

    } else
	 System.out.println("(does not exist yet)");

   }

   node.set_name(name);

   doc.view.canvas.update();

  } else if(node.parent!=null&&name.length()==0)
   node.set_name(null);

  if(dialog.node_help.getText().trim().length()>0)
   node.set_help(dialog.node_help.getText().trim()); 
  else
   node.set_help(null);

  boolean replace_later = false;  // folgender Knotentyp-Cast erfordert document.replace(...)

  Node replacement_target = node; // merken, da das Ziel des Replacements evtl. nicht das editable_object ist (gdw. actviate_changes() ueber RefreshPropsDialog aufgerufen wurde)

  try {

   if(dialog.type_and.getState()&&!(node instanceof AND_Node)) { // Knoten in Und-Knoten umwandeln: 

    node = AND_Node.create(node, false, null, null); 

	replace_later = true; // Knoten erst spaeter im Baum austauschen, da dabei der Properties-Status (noch unvollstaendig) aufgefrischt wird
  
   } else if(dialog.type_or.getState()&&!(node instanceof OR_Node)) { // Knoten in Oder-Knoten umwandeln:

    node = OR_Node.create(node, false, null, null); 
 
	replace_later = true;
  
   } else if(dialog.type_ior.getState()&&!(node instanceof iOR_Node)) { // Knoten in inklusive-Oder-Knoten umwandeln:

    node = iOR_Node.create(node, false, null, null); 
 
	replace_later = true;
  
   } else if(dialog.type_text.getState()&&!(node instanceof Text_Node)) { // Knoten in Text-Knoten umwandeln:

    node = Text_Node.create(node, false, null, null); 

	replace_later = true;
  
   } else if(dialog.type_and_leaf.getState()&&(!(node instanceof AND_Node)||node.children.length>0)) { // Knoten in Und-Blatt umwandeln:

    node = AND_Node.create(node, false, null, null); 

    node.children = new Node[0];

	replace_later = true;
  
   }

  } catch(Exception xcpt) { 
   
   System.out.println(xcpt);  
   
  }

  if(node instanceof Text_Node) {

   ((Text_Node)node).set_leaf_text(dialog.text.getText().trim()); 

   try {

    if(dialog.rows.getText().trim().length()>0)
     ((Text_Node)node).set_rows(Integer.parseInt(dialog.rows.getText().trim())); 

   } catch(NumberFormatException xcpt) {

    InfoDialog.info(AOT.APPLET_NAME+" Error (Properties dialog)", "You must give a number in field <Rows> ", InfoDialog.OK, this, "cats_fit.html#leaf");

    dialog.rows.show();

    dialog.rows.requestFocus();

    return false;
 
   }

   try {

    if(dialog.Cols.getText().trim().length()>0)
     ((Text_Node)node).set_columns(Integer.parseInt(dialog.Cols.getText().trim())); 

   } catch(NumberFormatException xcpt) {

    InfoDialog.info(AOT.APPLET_NAME+" Error (Properties dialog)", "You must give a number in field <Cols> ", InfoDialog.OK, this, "cats_fit.html#leaf");

    dialog.Cols.show();

    dialog.Cols.requestFocus();

    return false;
 
   }

  }

  modified = false;

  if(replace_later)
   doc.replace(null, node, replacement_target, false);
  else
   doc.display_different();

  doc.set_modified(true);

  return true;

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY||e.id==Event.KEY_PRESS&&e.key==27) {

   hide();

   dispose();

   doc.props = null;

   doc.view.canvas.requestFocus();

   return true;

  } else if(e.id==Event.KEY_ACTION||e.id==Event.KEY_PRESS) {

   modified = true;

  } else if(e.id==Event.ACTION_EVENT) {

   if(e.target==dialog.activate_button) {

    activate_changes();

    return true;

   } else if(e.target==dialog.browse_button) { // (wird wahrscheinlich hauptsaechlich zum Laden verwendet:)

	// leider nicht fuer Applets mgl.:

    String filename = AOT.inquire_filename("Categories-tree", "*."+AOT.CATEGORIES_FILE_TYPE, FileDialog.LOAD, this, new File(node.get_path()).getParent());

	if(filename!=null)
     dialog.node_name.setText(filename);
	   
	return true;

   } else if(e.target==dialog.edge_id_auto) {
   
    if(dialog.edge_id_auto.getState())
	 dialog.edge_id.disable();
	else
	 dialog.edge_id.enable();

   } else if(e.target==dialog.ok_button) {

	if(activate_changes()) {

     hide();
 
     dispose();

     doc.props = null;

     doc.view.canvas.requestFocus();

    } 

    return true;

   } else if(e.target==dialog.cancel_button) {

    hide();

    dispose();

    doc.props = null;

    doc.view.canvas.requestFocus();

    return true;
  
   } else if(e.target==dialog.help_button) {

    AOT.online_help("cats_pro.html");
  
    return true;

   } else {

    modified = true;

    if(e.target==dialog.type_text) {

     dialog.text.enable();

 	 dialog.Cols.enable();

	 dialog.rows.enable();

    } else if(e.target==dialog.type_and||e.target==dialog.type_or||e.target==dialog.type_and_leaf) {

     dialog.text.disable();

 	 dialog.Cols.disable();

	 dialog.rows.disable();

    }

   }

  }

  return super.handleEvent(e);

 }

}


class Constraint { 

 static int noname_count = 0;

 String id;

 String help;

 /** enthaelt immer mind. zwei Elemente */
 String[] patterns;  // als Implikation: patterns[0] => OR(patterns[1],patterns[2]...)
 
 boolean suspended = false; // zeitweilig deaktivert?


 Constraint() {

  id = new String("noname"+ ++noname_count);

  help = new String("(unknown purpose)");

  patterns = new String[2];
  
  patterns[0] = new String("(_)");

  patterns[1] = new String("(_)");

 }


 Constraint copy() {  // vereinfachtes clone()
 
  Constraint cons = new Constraint();

  cons.id = new String(id);

  cons.help = new String(help);

  cons.patterns = new String[patterns.length];

  for(int i = 0; i<patterns.length; i++)
   cons.patterns[i] = new String(patterns[i]);

  return cons;

 }


 public String toString() {

  String cons_text = new String("\r\nconstraint(\r\n"+patterns[0]+", [");

  for(int i = 1; i<patterns.length; i++)
   cons_text += "\r\n"+patterns[i]+(i<patterns.length-1?",":"");

  return cons_text+"],\r\nlabel=\""+AOTParse.escapeString(id)+"\", help=\""+AOTParse.escapeString(help)+"\", suspended="+(suspended?"true":"false")+").";

 }


 String injured(Node root) {

  if(suspended)
   return null;

  String injured_constraint_description = null;

  if(root.match(patterns[0])) {

   int j; 

   for(j = 1; j<patterns.length; j++)
    if(root.match(patterns[j]))
     break;

   if(j>=patterns.length)
    return new String("Name: "+id+"\nDescription: "+help);

  }

  return null;

 }

}


/** Verwaltungs-Dialog fuer alle Constraints eines Dokumentes */
class ConstraintsDialog extends Frame /*Dialog*/ {
 
 Document doc;

 /** tatsaechliche Darstellung (getrennt, weil das Layout automatisch
  vom MS J++ Resource-Wizard kreiert wurde)*/
 ConsDialog dialog; 

 /** Constraints-Eigenschaften-Dialog (modal) */
 ConstraintsPropertiesDialog cons_props = null;  

 
 ConstraintsDialog(Document doc) {

  super("Constraints");

  this.doc = doc;

  setFont(new Font("Dialog", Font.PLAIN, 12)); // (Dummy; wenn fehlt => Absturz)

  dialog = new ConsDialog(this);

  dialog.CreateControls();

  show();

  refresh();

 }


 void refresh() {

  dialog.cons_list.clear();

  for(int i = 0; i<doc.constraints.length; i++) {

   String cons = new String((doc.constraints[i].suspended?"inactive: ":"")+doc.constraints[i].id+": "+doc.constraints[i].help);

   dialog.cons_list.addItem(cons);

  }

  doc.set_selection_modified(true);  // zwecks Aktualisierung der Liste der verletzten Constraints
	  
 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY||e.id==Event.KEY_PRESS&&e.key==27||e.id==Event.ACTION_EVENT&&e.target==dialog.close_button) {

   hide();

   dispose();

   doc.cons = null;

   doc.view.canvas.requestFocus();

   return true;

  } else if(e.id==Event.ACTION_EVENT) {

   if(e.target==dialog.New_button) { // (J++ besteht auf Grossschreibung...)

	doc.set_modified(true);

	Constraint[] constraints = new Constraint[doc.constraints.length+1];

	try {

	 System.arraycopy(doc.constraints, 0, constraints, 0, doc.constraints.length);

	} catch(Exception xcpt) {}

	constraints[doc.constraints.length] = new Constraint();

	doc.constraints = constraints;

	refresh();

    dialog.cons_list.select(doc.constraints.length-1);

	properties();

    return true;
  
   } else if(e.target==dialog.remove_button) {

	int no = dialog.cons_list.getSelectedIndex();

	if(no>=0) {

     doc.set_modified(true);

	 Constraint[] constraints = new Constraint[doc.constraints.length-1];

	 int j = 0;

	 for(int i = 0; i<doc.constraints.length; i++)
      if(i!=no)
	   constraints[j++] = doc.constraints[i];

	 doc.constraints = constraints;

	 refresh();

    }

    return true;
  
   } else if(e.target==dialog.edit_button||e.target==dialog.cons_list) {

	properties();

    return true;
  
   } else if(e.target==dialog.suspend_button) {

	int no = dialog.cons_list.getSelectedIndex();

	if(no>=0) {

     doc.set_modified(true);

	 doc.constraints[no].suspended = !doc.constraints[no].suspended;

     refresh();

	}

   } else if(e.target==dialog.helpc_button) {

    AOT.online_help("cats_sel.html");  //*** ...
  
    return true;

   }

  }

  return false;

 }


 void properties() {

  int con_no = dialog.cons_list.getSelectedIndex();

  if(con_no>=0) {

   doc.set_modified(true);
	 
   cons_props = new ConstraintsPropertiesDialog(this, con_no);

   refresh();
 
  }

 }

}


/** Eigenschaften-Dialog fuer ein Constraint */
class ConstraintsPropertiesDialog extends Dialog {

 ConstraintsDialog parent;

 /** tatsaechliche Darstellung (getrennt, weil das Layout automatisch
  vom MS J++ Resource-Wizard kreiert wurde)*/
 ConsPropsDialog dialog; 

 int con_no; // Index des Constraints in parent (speichern, da Windows
 // die Listenauswahl leider loescht)

 Constraint prev_cons; // Zwecks Undo bei <Cancel>

  
 ConstraintsPropertiesDialog(ConstraintsDialog parent, int con_no) {
  
  super(parent, "Constraint properties", false/* *** sollte eigentlich modal sein, fkt. aber nicht richtig*/);

  prev_cons = parent.doc.constraints[con_no].copy();

  this.parent = parent;

  this.con_no = con_no;

  setFont(new Font("Dialog", Font.PLAIN, 12)); // (Dummy; wenn fehlt => Absturz)

  dialog = new ConsPropsDialog(this);

  dialog.CreateControls();

  show();

  dialog.id_edit.setText(new String(parent.doc.constraints[con_no].id));
  //(new String, weil ich mir nicht ganz sicher ueber die interne
  //Referenzierung von Elementen in Windows-Listen bin)

  dialog.help_edit.setText(new String(parent.doc.constraints[con_no].help));

  // ^ ..setText() nicht nach refresh()!

  refresh();

 }


 void refresh() {

  dialog.selpat_list.clear();

  dialog.selpat_list.addItem(new String(parent.doc.constraints[con_no].patterns[0]));

  for(int i = 1; i<parent.doc.constraints[con_no].patterns.length; i++)
   dialog.selpat_list.addItem((i==1?"=>  ":"OR  ")+new String(parent.doc.constraints[con_no].patterns[i]));

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY||e.id==Event.KEY_PRESS&&e.key==27||e.id==Event.ACTION_EVENT&&e.target==dialog.cancel_button) {

   parent.doc.constraints[con_no] = prev_cons;

   hide();

   dispose();

   parent.cons_props = null;

   return true;

  } else if(e.id==Event.ACTION_EVENT) {

   if(e.target==dialog.New_button) { /* (J++ besteht auf Grossschreibung...)
     Fuegt die aktuelle Selektion (im Baum oder Selection text view)
	 selpat vor dem markierten Element no im Constraint con_no ein */

	int no = dialog.selpat_list.getSelectedIndex();

	if(no<0)
	 no = 0;

    String selpat;
	 
	if(parent.doc.selection!=null) // Selection text view existiert:
     selpat = parent.doc.selection.textarea.getText();
	else // ...nicht:
	 selpat = parent.doc.root.get_selection("\r\n  ", Node.GET_IDS);

    String[] patterns = new String[parent.doc.constraints[con_no].patterns.length+1];

	int j = 0;

	for(int i = 0; i<patterns.length; i++)
	 if(i==no)
	  patterns[i] = new String(selpat);
	 else
	  patterns[i] = parent.doc.constraints[con_no].patterns[j++];

	parent.doc.constraints[con_no].patterns = patterns;

	refresh();

    dialog.selpat_list.select(dialog.selpat_list.countItems());

    return true;
  
   } else if(e.target==dialog.replace_button) {

	int no = dialog.selpat_list.getSelectedIndex();

	if(no>=0) {

     String selpat;
	 
	 if(parent.doc.selection!=null) // Selection text view existiert:
      selpat = parent.doc.selection.textarea.getText();
	 else // ...nicht:
	  selpat = parent.doc.root.get_selection("\r\n  ", Node.GET_IDS);

	 parent.doc.constraints[con_no].patterns[no] = new String(selpat);

	 refresh();
   
    }

   } else if(e.target==dialog.remove_button) {

	int no = dialog.selpat_list.getSelectedIndex();

	if(no>=0&&parent.doc.constraints[con_no].patterns.length>2) {

     String[] patterns = new String[parent.doc.constraints[con_no].patterns.length-1];

	 int j = 0;

	 for(int i = 0; i<patterns.length+1; i++)
	  if(i!=no)
	   patterns[j++] = parent.doc.constraints[con_no].patterns[i];

 	 parent.doc.constraints[con_no].patterns = patterns;

 	 refresh();

	}

    return true;
  
   } else if(e.target==dialog.activate_button||e.target==dialog.selpat_list) {
	//Activate/Edit (d.h. indirekte Uebertragung in das Selection text Fenster)

	int no = dialog.selpat_list.getSelectedIndex();

	if(no>=0) { //immer

	 if(parent.doc.selection!=null) { // Selection text view existiert

	  parent.doc.selection.set(parent.doc.constraints[con_no].patterns[no]);

      parent.doc.selection.labels_box.setState(parent.doc.constraints[con_no].patterns[no].indexOf("\"(")>=0||parent.doc.constraints[con_no].patterns[no].indexOf("\" (")>=0);
	  // (^ ein zugegeben lausiger Check)

	 }

	 parent.doc.activate_selection(parent.doc.constraints[con_no].patterns[no], true, true);

	}

    return true;
  
   } else if(e.target==dialog.ok_button) { 

    parent.doc.constraints[con_no].id = new String(dialog.id_edit.getText());

    parent.doc.constraints[con_no].help = new String(dialog.help_edit.getText());
 
    hide();

    dispose();

    parent.cons_props = null;

    parent.refresh();

    return true;

   } else if(e.target==dialog.hlp_button) { 

	//...

   }

  }

  return false;

 }

}


/** Liste aller z.Zt. verletzten Constraints  */
class InjuredConstraintsDialog extends Dialog {
 
 Document doc;

 List constraints_list;

 TextArea purpose;

 static int pos_x = 600 /*-1 = keine Aenderung*/, pos_y = 20, size_x = -1, size_y;

 
 InjuredConstraintsDialog(Document doc) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Constraints injured!", false);
  
  this.doc = doc;

  purpose = new TextArea(3, 35);

  add("Center", purpose);

  constraints_list = new List(3, false);

  add("North", constraints_list);

  pack();
  
  if(pos_x!=-1) { //*** fkt. nicht unter Linux:

   move(pos_x, pos_y);

/*   pack();

   move(pos_x, pos_y);

   show();

   move(pos_x, pos_y);

   pack();
 
   move(pos_x, pos_y);*/
   show();

   validate();

  }

  if(size_x!=-1)
   resize(size_x, size_y);

  refresh();

 }


 void refresh() {

  if(constraints_list.countItems()>0) //***
   constraints_list.clear();

  for(int i = 0; i<doc.injured_constraints.length; i++) {

   String cons = new String(doc.injured_constraints[i].id);

   constraints_list.addItem(cons);

  }

  if(doc.injured_constraints.length>0) {

   constraints_list.select(0);
  
   purpose.setText(doc.injured_constraints[0].help);

  } else
   purpose.setText("");

  //getParent().show(); // damit der Anwender mit der Tastatur ungestoert weiterarbeiten kann
  doc.view.canvas.requestFocus(); // damit der Anwender mit der Tastatur ungestoert weiterarbeiten kann

 }


 void close() {

  pos_x = location().x;

  pos_y = location().y;

  size_x = bounds().width;

  size_y = bounds().height;

  hide();

  dispose();

  doc.injured_cons = null;

  doc.view.canvas.requestFocus();

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY||e.id==Event.KEY_PRESS&&e.key==27) {

   close();

   return true;

  } else if(e.id==Event.LIST_SELECT) {

   purpose.setText(doc.injured_constraints[constraints_list.getSelectedIndex()].help);
	  
  }

  return false;

 }

}
