
/**

   Document.java
   
   Dokumentenklasse in Categories
  
   Kapselt Modell (Baum 'root') und View (Baumansicht 'view');
   nach aussen (d.h. fuer das Applet und den Anwender) stellen sich Modell 
   und View als eine Einheit (AOT.Aot.document) dar.
  
   Autor: Matthias Nickles (Matthias_Nickles@msn.com)
  
   Quellcode v0.10.1b
  
   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.util.*;

import java.awt.*;

import Node; 

import View; 


class Document {

 /* *** der nuetzliche Zugriffsmodifizierer "private protected" ist unter J++ 1.0
  leider nicht verfuegbar. */
  
 Node root = null;

 View view = null;

 /** Baum in Textform (wird allerdings nicht automatisch aktualisiert) */
 AOTreeString source = null; 

 Constraint[] constraints; // minimal ...[0]

 Constraint[] injured_constraints; // minimal ...[0]

 Bookmark[] bookmarks; // minimal ...[0]

 /** optionales Fenster zur Anzeige des Aktivierungszustandes in Textform */
 SelectionTextDialog selection = null; 
 
 /** Node+Edge=Objekt-Eigenschaften-Dialog (modeless, siehe class PropertiesDialog) */
 PropertiesDialog props = null;  

 /** Dialog: Constraints-Liste */
 ConstraintsDialog cons = null;  

 /** Dialog: verletzte Constraints */
 InjuredConstraintsDialog injured_cons = null;  

 /** optionales Fenster zur Anzeige des Help-Attributs von Knoten und Kanten */
 DecisionHelpDialog decision_help = null; 

 /** Antwort-Fenster fuer <Request for Completion> */
 HostDialog host_dialog = null;

 /** ein Editiervorgang setzt diese Variable auf true */
 private boolean modified = false; 

 /** Im Access-Mode 0 (Endanwender) wurde
  ein Blatt-Text veraendert oder die Auswahl bei einem Oder-Knoten
  geaendert.*/
 private boolean selection_modified = false; 

 /** Sicherheitskopie vom Baum vor diversen destruktiven Operationen */
 private Node undo_root = null;

  /** internes Hilfsflag */
 boolean match_fail;



 Document(String filename) {

  open(filename, true);

 }
 

 /** Muss das Dokument gesichert werden? */
 boolean closeable() {  // nicht ueber finalize(), da Zeitpunkt einer Garbage-Collection undefiniert ist!
 // Rueckgabe von true, falls Freigabe des Dokuments moeglich
 
  return(!modified&&!selection_modified);

 }


 String name() {
  
  return root.name;

 }


 /** Namen des Wurzelknotens als Frame-name festlegen*/
 void set_application_name() { 

  try { 

   if(true||view!=null)
    if(view.getParent() instanceof Frame) {

     ((Frame)view.getParent()).setTitle(new String(AOT.APPLET_NAME+" ["+root.name+"]"));

     if(!view.activated_only&&view.getParent() instanceof ApplicationFrame&&!AOT.BROWSER_Navigator)
        ((ApplicationFrame)(view.getParent())).activated_only.setLabel(((ApplicationFrame)(view.getParent())).activated_only_label);

    }

  } catch(Exception e) {}

 }


 void set_name(String name) {

  root.name = name;

  if(view!=null)
   display_different();

 }


 /** root zeichnen, ohne Hintergrund zu loeschen oder Neuralgics neu zu berechnen*/
 /*synchronized !*/ void display() {  

  if(view.inplace_enabled()) // weil sich dann (z.B. durch Aktivierung) die Groessenverhaeltnisse aendern koennen
   display_different();
  else
   view.draw();

 }


/* synchronized !*/void display_different() {  

  display_different(false);

 }

 
 /** volles Update (root zeichnen und Neuralgics berechnen)*/
 /*synchronized !*/void display_different(boolean keep_selection) {  
 // Achtung: reagiert empfindlich auf Unterbrechung im Ablauf!

  Node marked = marked_node();

  view.recent_neuro = null;

  view.renew_neuralgics_next();

  view.canvas.update(); //(ruft draw() auf, loescht vorher das ViewCanvas)

  set_application_name();

  Neuralgic neuro = view.find_neuro(marked);

  view.mark_edge(neuro, true);

  if(!keep_selection) 
   refresh_selection_text();

  refresh_properties(); 

 }


 /** markierter Knoten bzw. dessen zulaufende Kante*/
 Node marked_node() {  

  if(view.get_recent_neuro()==null)
   return root;

  return view.recent_neuro.node;

 }

 
 void paste(Document source) {

  paste(source, false, null);

 }


 /** fuegt eine Kopie vom markierten Knoten in source als Kind zum markierten Knoten hinzu*/
 void paste_as_first(Document source) {

  paste(source, true, null);

 }


/** fuegt eine Kopie vom markierten Knoten in source als Geschwister zum markierten Knoten hinzu */
 void paste_before(Document source) {

  Node pivot = marked_node();

  if(pivot.parent!=null)
   view.mark_edge(view.find_neuro(pivot.parent));

  paste(source, true, pivot);

 }
 

 /** fuegt eine Kopie vom markierten Knoten in source als Geschwister zum markierten Knoten hinzu*/
 void paste_behind(Document source) {

  Node pivot = marked_node();

  if(pivot.parent!=null)
   view.mark_edge(view.find_neuro(pivot.parent));

  paste(source, false, pivot);

 }


 /** fuegt zum markierten Knoten einen neuen Knoten als erstes "Kind" hinzu*/
 void new_as_first() {

  new_branch(true, null);

 }


 /** fuegt zu einem Knoten mit Id. "other" ein "Kind" hinzu (im Endanwender-Modus!).
  Hoechst problematischer Befehl, sollte man ueberdenken!!! */
 void new_alternative() {

  if(!(marked_node() instanceof AND_Node)&&(marked_node().edge.id.trim().equalsIgnoreCase("other")||marked_node().edge.label.trim().equalsIgnoreCase("other"))) {

   new SetIdDialog(this);

  } else
   InfoDialog.info(AOT.APPLET_NAME+" Error", "Invalid operation: No \"other\"-node marked.", InfoDialog.OK, null);

 }


 /** erlaubt dem Endanwender, das help-Attribut eines Knotens zu editieren ("Kommentar") */
 void commentary() {

  new CommentaryDialog(this);

 }


 /** fuegt zum markierten Knoten einen neuen Knoten als Geschwister hinzu*/
 void new_before() {

  Node pivot = marked_node();

  if(pivot.parent!=null)
   view.mark_edge(view.find_neuro(pivot.parent));

  new_branch(true, pivot);

 }
 

 /** fuegt zum markierten Knoten als Geschwister zum markierten Knoten hinzu*/
 void new_behind() {

  Node pivot = marked_node();

  if(pivot.parent!=null)
   view.mark_edge(view.find_neuro(pivot.parent));

  new_branch(false, pivot);

 }


 /** Vertauschen eines Knotens mit seinem Nachbarn*/
 void move(Document source, boolean direction /*true=nach oben*/) {

  Node pivot = marked_node();

  if(pivot.parent!=null) {

   pivot.parent.swap_children(pivot, direction);

   display_different();

   set_modified(true);

  }
  
 }


 /** Loeschen eines Knotens */
 void delete() {

  Node node = marked_node();

  if(node!=root) {

   set_modified(true);

   Node parent = node.parent;

   view.mark_edge(view.find_neuro(parent));

   parent.remove_child(node);

   display_different();

  } else
   InfoDialog.info(AOT.APPLET_NAME+" Error", "You can't remove the root-node", InfoDialog.OK, null);

 }


 /** Entfernt alle Kinder der Wurzel. Es handelt sich aber um eine Modifikation
  ,nicht um die Erzeugung eines neuen Dokuments! */
 void clear() { 

  reset_attributes();

  set_modified(true);

  for(int i = root.children.length; i>=1; i--)
   root.remove_child(root.children[0]);  
  // anders als in open_empty() bleibt hier der Wurzelname erhalten

  display_different();

  view.mark_root();

 }


 Node replace(Document source) {

  return replace(source, null, null, true);

 }


 /** Knoten target durch node (in Dokument source) ersetzen. 
  (fkt. auch auf Wurzel)
  copy=false: Quellknoten muss nicht kopiert werden
  node=null:markierten Knoten in source verwenden
  target=null:markierten Knoten in this verwenden
  Resultat: der Ersatz-Knoten */
 Node replace(Document source, Node node, Node target, boolean copy) {
 
  if(node==null)
   node = source.marked_node(); // der "neue" Knoten

  if(target==null)
   target = marked_node(); // der "alte" Knoten
  
  Node node_copy = null;

  set_modified(true);

  if(target!=root) {

   Node parent = target.parent;

   if(source!=null) 
    view.mark_edge(view.find_neuro(parent));

   try {

	parent.replace_child(target, node_copy = !copy?node:(Node)node.clone());

   } catch(Exception xcpt) { 
   
    System.out.println(xcpt);  
   
   }

  } else {

   try {

	root.remove_inplace_areas();

	root = node_copy = !copy?node:(Node)node.clone();

   } catch(Exception xcpt) { 
   
    System.out.println(xcpt); 
   
   }

   if(name()==null)
    root.name = "noname."+AOT.CATEGORIES_FILE_TYPE;
 
  }

  display_different();

  view.mark_edge(view.find_neuro(node_copy), true);

  return node_copy;

 }
	 

 /** fuegt eine Kopie vom markierten Knoten in source hinzu.
  pivot==null:
    as_first==true: als ersten Sohn des markierten Knotens einfuegen
    sonst: als letzten Sohn des markierten Knotens einfuegen
  sonst:
    as_first==true: als Schwester vor dem Knoten pivot einfuegen
    sonst: als Schwester nach dem Knoten pivot einfuegen */
 void paste(Document source, boolean as_first, Node pivot) {
 	 
  Node node = source.marked_node(), target = marked_node(), node_copy = null;

  if(pivot!=null)
   if(pivot.parent==null) {

    InfoDialog.info(AOT.APPLET_NAME+" Error", "Invalid operation: The root-node doesn't have neighbours", InfoDialog.OK, null);

    return;

   }

  if((target instanceof Text_Node)&&this==AOT.Aot.clipboard) // bei einem Textknoten im Clipboard Blatt zum Vater hinzufuegen um Fehlermeldung zu vermeiden
   target = target.parent; 

  if(!(target instanceof Text_Node)) {

   if(node!=null&&target!=null) {

    set_modified(true);

    try {

     target.add_child(node_copy = (Node)node.clone(), as_first, pivot);

    } catch(Exception xcpt) { 
   
     System.out.println(xcpt);  
   
    }

    display_different();

	if(!view.painted_yet()) // kommt nur vor, falls Clipboard noch nie dargestellt worden ist
	 view.recent_neuro = new Neuralgic(new Rectangle(), new Rectangle(), node_copy, 1);
    else
     view.mark_edge(view.find_neuro(node_copy), true);

   }

  } else 
   InfoDialog.info(AOT.APPLET_NAME+" Error", "A text-leaf cannot have children", InfoDialog.OK, null);

 }


 /** Parameter vgl. paste()*/
 void new_branch(boolean as_first, Node pivot) { 

  set_modified(true);

  new_branch(as_first, pivot, false);  // false=mit Aufruf des Props-Dialogs!

 }


 /** Parameter vgl. paste()*/
 void new_branch(boolean as_first, Node pivot, boolean quiet) { 

  Node target = marked_node();

  /*if(target instanceof Text_Node) // zu einem Textknoten kann man kein Blatt hinzufuegen
   target = target.parent;*/

  if(!(target instanceof Text_Node)) {

   Node new_node;

   new_node = new AND_Node(null, new Edge(null, null, null), new Node[0], null, false);
   
   target.add_child(new_node, as_first, pivot);

   display_different();

   view.mark_edge(view.find_neuro(new_node), true);

   if(AOT.Aot.access_mode==AOT.SUPERUSER&&!quiet)
    properties();

  } else 
   InfoDialog.info(AOT.APPLET_NAME+" Error", "A text-leaf cannot have children", InfoDialog.OK, null);

 }


 /** Eigenschaften einer Kante bzw. des nachfolgenden Knotens node aendern*/
 void properties() {

  if(props==null) 
   props = new PropertiesDialog(marked_node(), this);

  props.requestFocus();

 }

 
 /** Eigenschaften-Dialog aktualisieren*/
 void refresh_properties() { 

  if(props!=null) {

   props.set_node(marked_node());

   props.refresh();

  }

 }

 
 /** Sicherheitskopie vom Baum anlegen*/
 void prepare_undo() {

  try {

   undo_root = root.copy(false); // nicht per clone(), da sonst Kanten-Id.s veraendert wuerden

  } catch(Exception xcpt) { 
   
   System.out.println(xcpt); 
   
  }

 }


 void undo() {

  root.remove_inplace_areas();

  root = undo_root;

  undo_root = null;

  display_different();

 }


 void print() {

  view.print();

 }
 

 void collapse() {

  marked_node().compressed = true;

  display_different();

 }


 void expand() {

  marked_node().compressed = false;

  display_different();

 }


 void collapse_all() {

  root.set_collapsed_for_all(true);
 
  display_different();

 }

 
 void expand_all() {

  root.set_collapsed_for_all(false);
 
  display_different();

 }


 /** Versteckt/zeigt inaktivierte Baumteile */
 boolean toggle_activated_only() {

  boolean hidden = (view.activated_only = !view.activated_only);

  if(hidden)
   view.mark_root();

  display_different();

  return hidden;

 }
 
 /** Quelle eines Baums in Node-Datenstruktur uebersetzen 
  Dateiname filename hier nur kommentierend, vgl. open()*/
 void parse(String filename) {
 // diese Fkt. ist so gestaltet, dass sie bis zur tatsaechlichen Darstellung eines
 // neuen Baums reversibel ist.

  boolean dispose_wait = false;

  if(filename==null)
   filename = name();

  if(AOT.Aot.wait==null&&this!=AOT.Aot.clipboard/*sonst wird von InfoDialog der Fokus auf application gesetzt*/) {

   AOT.Aot.wait = new InfoDialog(AOT.APPLET_NAME+" Information", 
 	   "\nParsing categories source, please wait...\n"
       , 0, null);

   AOT.Aot.wait.center();

   dispose_wait = true;
   
  }

  System.out.println("Parsing categories source, please wait...");  

  Node prev_root = root; // um den alten Baum notfalls wiederherstellen zu koennen

  StringBuffer parse_message = new StringBuffer();
 
  if(cons!=null) {  // Constraints-Dialog entfernen

   cons.hide();

   cons.dispose();

   cons = null;

  }

  Constraint[][] consp = new Constraint[1][];

  Bookmark[][] bookmks = new Bookmark[1][];

  if(AOT.Aot.application!=null)
   AOT.Aot.application.clear_bookmarks();

  root = source.parse(null, new Edge(null, null, "edgeToTheRoot"), parse_message, consp, bookmks);

  if(root==null) { // ungueltiger Quelltext

   if(prev_root!=null)
    root = prev_root;
   else
	open_empty(filename);

   System.out.println("Error: Parser error in "+filename);
 	      
   if(source.dialog==null)
	source.display();

   source.dialog.put_cursor_on_error_pos();

   InfoDialog.info(source.dialog.textarea, AOT.APPLET_NAME+" Error", "Parser error in file "+filename+" :\n"+parse_message, InfoDialog.OK, null);

   if(dispose_wait) {

    AOT.Aot.wait.dispose();  
 
    AOT.Aot.wait = null;
 
   }

   return; // (kein Programmabbruch; Anwender kann es z.B. nochmal versuchen)

  }

  if(prev_root!=null)
   prev_root.remove_inplace_areas(); //(wozu?)

  if(consp[0]!=null) // null, wenn Parse aus Categories text view heraus aufgerufen wurde (dort werden keine Constraints angezeigt)
   constraints = consp[0];
  else
   constraints = new Constraint[0];

  injured_constraints = new Constraint[0];

  if(bookmks[0]!=null) {

   bookmarks = bookmks[0];

   add_bookmarks(AOT.Aot.application);

  } else   
   bookmarks = new Bookmark[0];

  System.out.println("Parse successful");  

  root.update_edge_id_no();  // aktualisiert den hoechsten vorkommenden Index eines autom. erzeugten Identifizierers

  // nein!: set_name(filename);
  root.name = filename;

  if(view!=null)
   if(view.isVisible()&&view.inplace_enabled())
	view.remove_atoms();  // Textkomponenten aus inplace_panel entfernen

  if(view==null) {

   view = new View(this);  // neue Baum-Anzeigeflaeche

   set_application_name();

  } else { // ...andernfalls nur den Inhalt des alten Views austauschen 
	       // (geht etwas schneller):

   view.offset_x = view.offset_y = 0;

   display_different();  

  }

  view.mark_root();

  set_modified(true); // falls source direkt einer Datei entstammt (open()), wird
				      // modified in open() wieder auf false gesetzt.
  
  if(dispose_wait) {

   AOT.Aot.wait.dispose();  
 
   AOT.Aot.wait = null;
 
  }

 }


 /** Oeffnen und Parsen eines Dokumentes (als .cat-Datei mit der textuellen 
  Repraesentation des AND/OR-Trees root).
  Die tatsaechliche visuelle Darstellung des zugehoerigen Views findet hier aber
  nicht statt, sondern bei der Integration in die HTML-Seite bzw. ApplicationFrame.
  open() darf nur vom Konstruktor aufgerufen werden.
  open() sollte nur indirekt ueber AOT.open() aufgerufen werden. */
 void open(String filename, boolean add_to_history/*< keine Verwendung z.Zt.*/) {

  String current_directory_before = AOT.Aot.current_directory!=null?new String(AOT.Aot.current_directory):null;

  String tag = null;

  if(filename.indexOf("#")>=0) {  // "Kantenmarke" anspringen?
   
   tag = filename.substring(filename.indexOf("#")+1);

   if(filename.charAt(0)=='#')
    filename = "";
   else {

    filename = filename.substring(0, filename.indexOf("#"));

    if(current_URL(filename))
     filename = "";  // nur im Zusammenhang mit #Edgemarken, weil sonst Undo all nicht mehr fkt.

   }

  } 

  if(filename.length()>0) {
 
   String file_content = read_tree(filename);

   if(file_content==null) {

    if(root==null)
     open_empty(filename);  // Leeres Dokument kreiern
  
    return;

   }

   if(source==null)
    source = new AOTreeString(this, file_content); // "Quellcode" des Baums
   else
    source.set_text(file_content);

   parse(filename);

  }

  if(tag!=null) {  // Sprung zu Kante mit Identifizierer tag (analog HTML-#Textmarken)

   System.out.println("Jumping to edge "+tag);

   // (zu verhindern, dass "zwischen" zwei Dokumenten (z.B. bevor alle neuen Inplace-
   // Bereiche dargestellt sind) "Geister-"Mausklicks registriert werden, ist in 
   // BrowserCanvas.mouseDown() eine Verzoegerung direkt nach dem ersten Mausereignishandling
   // eingebaut)

   view.mark_edge(view.find_neuro(root.find_node_by_edge(tag, false, false, null, false, false)), true);

  }

  AOT.Aot.current_directory = current_directory_before;

  if(filename.length()>0) {

   set_modified(false);
  
   set_selection_modified(false);

  }

 }


 boolean current_URL(String filename) {

  return filename!=null&&AOT.get_URL(filename, false, false).toString().equalsIgnoreCase(AOT.get_URL(root.name, false, false).toString());
  
 }


 /** Erzeugt ein neues Dokument name, das nur den Wurzelknoten enthaelt*/
 void open_empty(String name) {
 
  System.out.println("Creating empty "+name); 

  reset_attributes();

  root = new AND_Node(null, new Edge(null, null, "edgeToTheRoot"), new Node[0], null, false);
   // anders als bei .clear() bleibt hier der Wurzel-Name nicht erhalten!

  root.name = name;

  view = new View(this);  //ist das notwendig?

  set_application_name();

 }


 /** setzt Dokumenten-Attribute zurueck */
 void reset_attributes() {

  if(cons!=null) {  // Constraints-Dialog entfernen

   cons.hide();

   cons.dispose();

   cons = null;

  }

  if(injured_cons!=null) { // Liste verletzter Contraints schliessen

   injured_cons.hide();

   injured_cons.dispose();

   injured_cons = null;

  }

  if(props!=null) { // Properties-Dialog schliessen

   props.hide();

   props.dispose();

   props = null;

  }

  constraints = new Constraint[0];

  injured_constraints = new Constraint[0];

  bookmarks = new Bookmark[0];

  AOT.Aot.application.clear_bookmarks();

 }


 /** Baum-Quelltext aus Datei lesen */
 static String read_tree(String filename) {

  String content = AOT.read(filename, true);

  AOT.set_dir(filename); // damit nachfolgende Include-Dateien mit
				         // relativem Pfad bzgl. dem Verzeichnis von filename geoeffnet werden

  return content;

 }

 
 /** aehnlich save(), aber unter Aufloesung aller Include-"Knoten"
  (wird z.T. nur zum Sichern des Clipboards (clipboard.cat) verwendet)*/
 void save_flat() {
 
  AOT.save(name(), root.toString(null, "\r\n  ", true), true);

  set_modified(false);
 
 }


 void save(String filename) {

  root.name = filename;

  save();

  display_different();

 }

 
 /** Speichern einer .cat-Datei mit der textuellen Repraesentation des AND/OR-Trees root*/
 void save() {  //(nicht fuer Applets)

  if(name().equalsIgnoreCase("noname."+AOT.CATEGORIES_FILE_TYPE)) {

   String filename = AOT.inquire_filename("Save Categories-tree as...", "*."+AOT.CATEGORIES_FILE_TYPE, FileDialog.SAVE);

   if(filename!=null) {

	root.name = filename;

	display_different();

   }

  }

  System.out.println("Saving document, please wait...");

  Stack includes = new Stack();
 
  includes.push(root);

  pop_includes(includes, 1);

  /* Alternativer Algo. >ohne< Uebernahme des Pfades der einschliessenden Datei durch Include-Dateien
     (statt pop_include())

  do {
     
   Node subroot = (Node)includes.pop();

   AOT.save(subroot.name, subroot.toString(includes, "\r\n  ", true), false);

  } while(!includes.empty());

  */

  set_modified(false);

 }


 void set_modified(boolean state) {

  if(AOT.Aot.access_mode==AOT.SUPERUSER) {

   modified = state;

   //...

  }

  //...

 }

  
 void set_selection_modified(boolean state) {

  if(AOT.Aot.access_mode==AOT.USER)
   selection_modified = state;

  if(this!=AOT.Aot.clipboard) {

   this.injured_constraints = new Constraint[0];  //*** ohne this schlaegt Compilierung mit J++ manchmal fehl!

   if(state) {

    if(this.constraints!=null) { // checken, ob alle Constraints erfuellt sind...

     String injured_constraint_description = null;

     for(int i = 0; i<this.constraints.length; i++) {

      injured_constraint_description = this.constraints[i].injured(root); 

      if(injured_constraint_description!=null) {

	   Constraint[] injured_constraints_tmp = new Constraint[this.injured_constraints.length+1];

	   int j;

	   for(j = 0; j<this.injured_constraints.length; j++)
        injured_constraints_tmp[j] = this.injured_constraints[j]; 

	   this.injured_constraints = injured_constraints_tmp;

       this.injured_constraints[j] = this.constraints[i];

      }

     } 

    }

   }

   if(this.injured_constraints!=null) {
   if(this.injured_constraints.length>0)
    injured_constraints();
   else if(injured_cons!=null)
    injured_cons.close();
   }

  }

 }


 boolean has_been_modified() {

  return modified;

 }
 

 boolean selection_has_been_modified() {

  return selection_modified;

 }


 /** Speichert die in derselben Verschachtelungstiefe liegenden Include-Dateien*/
 void pop_includes(Stack includes, int n) {

  while(n-->0) {
     
   String current_directory_before = AOT.Aot.current_directory!=null?new String(AOT.Aot.current_directory):null;
   // AOT.set_dir() wechselt temporaer in das Directory von .name, um alle Include-Knoten
   // von subroot mit relativem Pfad dort abzuspeichern

   Node subroot = (Node)includes.pop();

   int prev_size = includes.size();

   if(subroot==root) {

	String document_text = "categories("+subroot.toString(includes, "\r\n  ", true)+").";

    for(int i = 0; i<this.constraints.length; i++) // *** this notw. wg. Fehler in J++
     document_text += "\r\n"+constraints[i].toString();

    for(int i = 0; i<this.bookmarks.length; i++) 
     document_text += "\r\n"+bookmarks[i].toString();
	
    AOT.save(subroot.name, document_text, true);

   } else
	AOT.save(subroot.name, "categories("+subroot.toString(includes, "\r\n  ", true)+").", true);

   AOT.set_dir(subroot.name); // damit nachfolgende Include-Dateien mit
				              // relativem Pfad bzgl. dem Verzeichnis von .name geschrieben werden

   pop_includes(includes, includes.size()-prev_size);

   AOT.Aot.current_directory = current_directory_before; // altes Verzeichnis wiederherstellen

  }

 }


 /** button=true: Ok-Button anzeigen, andernfalls nicht. Dies ist sinnvoll
  in AOT.on_quit(), da das Button-Event dort nicht abgefangen werden kann*/
 void warn_about_empty_or_node(boolean button) {

  Node or_node;

  if((or_node = root.check_for_empty_OR_nodes())!=null) {
  // Warnung, da ein Oder-Knoten ohne Kinder wenig Sinn macht

   view.mark_edge(view.find_neuro(or_node), true);

   InfoDialog.info(AOT.APPLET_NAME+" Warning", "Decision-Node without children detected!                         ", button?InfoDialog.OK:0, "cats_fit.html#or");

   //*** weil es in Java 1.0.2 fuer Win32 (u.a.?) keine Thread-synchronen modalen Dialoge 
   // gibt, laeuft das Programm hier leider einfach weiter...

  }

 }
 

 void display_source() {

  display_source(false);

 }


 /** Baum-Quelltext in Fenster anzeigen (categories text view) */
 void display_source(boolean expand_includes) {

  if(!expand_includes) {

   Stack includes = new Stack();

   source.set_text(root.toString(includes, "\r\n  ", true));

  } else
   source.set_text(root.toString(null, "\r\n  ", true)); 
  // (man koennte source auch automatisch aktualisieren, dies wuerde
  // aber das Programm stark verlangsamen)

  source.display();

 }


 boolean activate_selection(String selection, boolean reset, boolean draw) {

  if(AOT.Aot.application!=null)
   AOT.Aot.application.setCursor(Frame.WAIT_CURSOR);

  if(view.activated_only)
   toggle_activated_only();

  String previous = root.get_selection("\r\n  ", Node.GET_IDS); // alte Selektion sichern

  if(reset)
   root.deactivate(); // minimalen Aktivierungszustand herstellen (wie nach Programmstart)

  boolean retval = root.activate_edges(selection, previous, false);

  if(draw)
   display_different(true/* um eine fehlerhafte Selektion als Text angezeigt zu lassen*/);

  set_modified(true);  // wg. Blatt-Texten (koennte man etwas differenzierter sehen...)

  set_selection_modified(true);

  if(AOT.Aot.application!=null)
   AOT.Aot.application.setCursor(Frame.DEFAULT_CURSOR);

  return retval;

 }


 /** Laden und Aktivieren einer in einer .sel-Datei gespeicherten Selektion 
  reset: vor der Aktivierung der neuen Selektion minimalen 
  Aktivierungszustand herstellen*/
 public void load_selection(String filename, boolean reset, boolean draw) {
  
  String selection = AOT.read(filename, true);

  if(selection==null)
   return;

  boolean prev_modified = modified;

  activate_selection(selection, reset, draw);

  set_modified(prev_modified);

 }

 
 void save_selection(String filename) {

  String sel = //root.get_selection("\r\n  ", Node.GET_IDS);
    get_incomplete_selection(false);

  AOT.save(filename, sel, true);

  set_selection_modified(false);

 }


 /** schickt eine vollstaendige Selektion + Kommentare zum Hostrechner */
 void send_selection(/*String selection*/) {

  System.out.println("Preparing transmission, please wait...");

  String[] process = { 
   // der eigentliche Vorgang wird erst in communicate_with_host generiert.
   // #/* dient nur der Client-internen Unterscheidung Token/Befehl/Daten

   "#PUT_SELECTION",
   "",  // Selektionsterm.
   "",  // Kommentarterm. (selbe Syntax, aber "Kommentar"-e statt Ids als Heads)

  };

  /*if(selection==null)
   process[1] = root.get_selection("\r\n  ", Node.GET_IDS)+".";
  else
   process[1] = selection;*/

  process[1] = get_incomplete_selection(true);
  // ... hier fehlt noch der Test auf Vollstaendigkeit (oder besser so?)

  if(activate_selection(process[1], true, true)) { // <- problematisch!
   // ^ notw., um Kommentare der im Selection text view angez. Selektion zu erhalten

   process[2] = root.get_selection("\r\n  ", Node.GET_COMMENTARIES)+".";

// ein paar Hacks:
           /*if(root.help!=null)
            if(root.help.trim().length()>0)*/
             process[2] = "\""+(root.help!=null?AOTParse.escapeString(root.help):"")+"\""+process[2];

			int pos;

            do {

             pos = process[2].indexOf("\"\"(_)");

             if(pos>=0)
              process[2] = process[2].substring(0, pos)+"_"+process[2].substring(pos+5);

            } while(pos>=0);

//System.out.println("KOMMENTAR-TERM:\n"+process[2]+"\n");

   AOT.Aot.communicate_with_host(process);

  }

 }
 
 /** Loescht eine Selektion auf dem Host bzw. in dessen Datenbank */
 void delete_selection(/*String selection*/){

  System.out.println("Preparing transmission, please wait...");

  String[] process = { 
   // der eigentliche Vorgang wird erst in communicate_with_host generiert.
   // #/* dient nur der Client-internen Unterscheidung Token/Befehl/Daten

   "#DELETE_SELECTION",
   "",  // Selektion.

  };

  /*if(selection==null)
   process[1] = root.get_selection("\r\n  ", Node.GET_IDS)+".";
  else
   process[1] = selection;*/

  process[1] = get_incomplete_selection(true);

  AOT.Aot.communicate_with_host(process);

 }
 
 /** schickt ein Selektionsmuster zum Hostrechner zwecks Vervollstaendigung */
 void complete_selection(/*String selection_pattern*/) {

  System.out.println("Preparing transmission, please wait...");

  String[] process = { 
   // der eigentliche Vorgang wird erst in communicate_with_host generiert.
   // #/* dient nur der Client-internen Unterscheidung Token/Befehl/Daten

   "#GET_COMPLETION",
   "",  // Selektionsmuster
   "*PULL"

  };

  /*if(selection_pattern==null)
   process[1] = root.get_selection("\r\n  ", Node.GET_IDS)+".";
  else
   process[1] = selection_pattern;*/

  process[1] = get_incomplete_selection(true);

  String answer = AOT.Aot.communicate_with_host(process);

  if(host_dialog!=null)
   host_dialog.textarea.setText(answer);
  else
   host_dialog = new HostDialog(AOT.Aot.aotframe!=null?AOT.Aot.aotframe:AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, this, answer);

 }

 
 /** Setzt den AVG-Server auf dem Host zurueck (nur zum internen Gebrauch) */
 void reset_server() {

  System.out.println("Reseting server, please wait...");

  String[] process = { 

   "#RESET",

  };

  AOT.Aot.communicate_with_host(process);

 }


 void display_selection(boolean labels) {

  display_selection(root.get_selection("\r\n  ", labels?Node.GET_LABELS:Node.GET_IDS), 0);

 }

 
 /** selection text view oeffnen */
 void display_selection(String sel, int cursor_pos) {

  if(selection==null)
   selection = new SelectionTextDialog(this, sel);
  
  selection.show();

  selection.textarea.show();

  selection.textarea.requestFocus();

  selection.put_cursor_on_error(cursor_pos);

  // Verbesserungsmgl.: eigene SelectionText-Klasse analog zu class AOTreeString

 }


 /** zur automatischen Aktualisierung des selection text view */
 void refresh_selection_text() { 

  if(selection!=null)
   selection.set(root.get_selection("\r\n  ", selection.labels_box.getState()?Node.GET_LABELS:Node.GET_IDS));

 }


 String get_incomplete_selection(boolean add_point) {

  if(selection!=null) {  // Inhalt des Selection text views liefern (ohne Punkt hinten!)

   if(selection.labels_box.getState()) // (Prolog-Datenbank kann mit Labels nix anfangen)
    return root.id_labels_swap(selection.textarea.getText());
   else
    return selection.textarea.getText();

  } else  // aktuelle Selektion liefern
   return root.get_selection("\r\n  ", Node.GET_IDS)+(add_point?".":"");

 }
      

 /** Minimalen Aktivierungszustand herstellen */
 void reset_activation() {

  if(view.activated_only)
   toggle_activated_only();

  root.deactivate();

  display();

  refresh_selection_text();

 }


 /** Dialog zur Constraints-Verwaltung oeffnen */
 void constraints() {  

  if(cons==null)
   cons = new ConstraintsDialog(this);

  cons.requestFocus();

 }


 /** Dialog mit Liste aller z.Zt. verletzten Constraints oeffnen */
 void injured_constraints() {  

  if(injured_cons==null) 
   injured_cons = new InjuredConstraintsDialog(this);
  else
   injured_cons.refresh();

  injured_cons.requestFocus();

 }

 /** Speichert die aktivierte Selektion (oder, falls offen) den Inhalt 
     vom Selection text view als Bookmark (Aufbewahrung: .cat-Datei)*/
 void add_bookmark() {
 
   AddBookmarkNameDialog dialog = new AddBookmarkNameDialog(this);

 }


 void add_bookmarks(ApplicationFrame application) {

  if(application!=null) {

   application.clear_bookmarks();

   for(int i = 0; i<bookmarks.length; i++)
    bookmarks[i].add_to_menu(application);

  }
  
 }

 
 void activate_bookmark(String name) {

  if(name.equals("Empty selection"))
   reset_activation();
  else 
   for(int i = 0; i<bookmarks.length; i++)
    if(bookmarks[i].name.equals(name)) {
 
     boolean prev_modified = modified;
 
     activate_selection(bookmarks[i].content, true, true);

     set_modified(prev_modified);

     break;

    }

 }

 
 /** Blatt-Inhalt zur Bearbeitung oeffnen (Plain oder HTML)*/
 void edit_leaf_text() {

  Node node = marked_node();

  if(node instanceof Text_Node)
   ((Text_Node)node).edit();
  else
   InfoDialog.info(AOT.APPLET_NAME+" Error", "Selected node cannot have textual content", InfoDialog.OK, null);

 }


 void toggle_activation() {

  if(AOT.Aot.application!=null)
   AOT.Aot.application.setCursor(Frame.WAIT_CURSOR);

  String previous = root.get_selection("\r\n  ", Node.GET_IDS); // alte Selektion sichern

  if(view.activated_only)
   toggle_activated_only();

  Node node = marked_node();

  node.toggle_activation();

  display();

  refresh_selection_text();

  set_selection_modified(true);

  if(AOT.Aot.application!=null)
   AOT.Aot.application.setCursor(Frame.DEFAULT_CURSOR);
}

 
 /** Kontext-Hilfe zum Knoten unter dem Mauszeiger anzeigen (wie Statuszeile im View, nur mehrzeilig) */
 void display_decision_help(String help) {

  if(help==null) 
   help = DecisionHelpDialog.decision_help_howto_text;

  if(decision_help!=null)
   decision_help.set_help(help, false);

 }


 /** Kontext-Hilfe zum markierten Knoten  */
 void display_decision_help(String edge_help, String node_help) {

  if(decision_help!=null)
   decision_help.set_help(edge_help, node_help);
 
 }


 /** markiert den Knoten, der anhand von Kantenidentifikator, oder -label gefunden wurde */
 void find() {
 
  FindDialog dialog = new FindDialog(this);

 }


 /** Kinder des markierten Knotens alphabetisch aufsteigend sortieren */
 void sort() {

  Node node = marked_node();

  node.sort_children();

  display_different();

  set_modified(true);

 }

}

	 
/** siehe Document.find()*/
class FindDialog extends Dialog {
	
 Document doc; 

 TextComponent textarea;

 Checkbox not_from_root, ignore_case, prefix, part;

 Button ok_button, cancel_button, help_button;

 static String previous = null;

 static boolean prev_not_from_root = false, prev_ignore_case = true, prev_prefix = false, prev_part = false;


 FindDialog(Document doc) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Enter edge-label or -identifier to search for", false);

  this.doc = doc;
  
  textarea = new TextField(previous!=null?previous:"", 30);

  textarea.setFont(new Font("Helvetica", Font.PLAIN, 12));

  add("Center", textarea);

  Panel p1 = new Panel(), p2 = new Panel();

  p1.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));

  ok_button = new Button("    Ok    ");

  p1.add(ok_button);

  cancel_button = new Button(" Cancel ");

  p1.add(cancel_button);

  help_button = new Button(" Help ");

  p1.add(help_button);

  not_from_root = new Checkbox("Search marked branch only");

  p2.add(not_from_root);

  not_from_root.setState(prev_not_from_root);

  ignore_case = new Checkbox("Ignore case");

  p2.add(ignore_case);

  ignore_case.setState(prev_ignore_case);

  prefix = new Checkbox("Ignore length");

  p2.add(prefix);

  prefix.setState(prev_prefix);

  part = new Checkbox("Partial match");

  p2.add(part);

  part.setState(prev_part);

  p1.setBackground(Color.white);

  p2.setBackground(Color.white);

  add("South", p1);

  add("North", p2);

  pack();

  move(300, 200);

  show();

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.ACTION_EVENT||e.id==Event.WINDOW_DESTROY) {

   if(e.target==ok_button||e.target==cancel_button||e.id==Event.WINDOW_DESTROY) {

    previous = new String(textarea.getText());

    prev_not_from_root = not_from_root.getState(); 

    prev_ignore_case = ignore_case.getState(); 

    prev_prefix = prefix.getState();

    prev_part = part.getState();

    if(e.target==ok_button) {

     String search_text = textarea.getText().trim();
 
 	 Node node;

	 if(!not_from_root.getState())
      node = doc.root.find_node_by_edge(search_text, true, ignore_case.getState(), null, prefix.getState(), part.getState());
	 else
	  node = doc.marked_node().find_node_by_edge(search_text, true, ignore_case.getState(), doc.marked_node(), prefix.getState(), part.getState());

	 if(node!=null) 
      doc.view.mark_edge(doc.view.find_neuro(node), true);
     else 
      InfoDialog.info(AOT.APPLET_NAME+" (Search)", "Sorry, nothing found", InfoDialog.OK, null);

    }
	  
    hide();

    dispose();

    return true;

   } else if(e.target==help_button) {

    AOT.online_help("cats_edi.html#find");
  
    return true;

   } else if(e.target==prefix) {

    part.setState(false);
    
   } else if(e.target==part) {

    prefix.setState(false);
    
   } 

  } 

  return super.handleEvent(e);

 }

}


class SelectionTextDialog extends Dialog { //(koennte man auch nach Node.java verschieben)

 Document parent;

 TextArea textarea;

 Button activate_button, refresh_button, cancel_button, send_sel_button, complete_sel_button, 
	    constraints_button, placeholder_button, mark_button, mark_term_button, placeholder_term_button;

 Checkbox labels_box; // statt Ids deren Labels anzeigen


 SelectionTextDialog(Document parent, String sel) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Selection text", false);

  this.parent = parent;

  textarea = new TextArea("", 20, 60);

  set(sel);

  textarea.setFont(new Font("Courier", Font.PLAIN, 14));

  add("Center", textarea);

  Panel p = new Panel(), p_side = new Panel();

  p.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5));

  p_side.setLayout(new GridLayout(15, 1, 5, 5));

  activate_button = new Button(" Activate ");

  p.add(activate_button);

  refresh_button = new Button(" Refresh ");

  p.add(refresh_button);

  cancel_button = new Button(" Close ");

  p.add(cancel_button);

  /*vielleicht will das mal wieder wer?:
  send_sel_button = new Button("Store at host");  (seit v0.10.0 reicht Pulldownmenue, da dieses differenziert)

  p.add(send_sel_button);

  complete_sel_button = new Button("Request completion");

  p.add(complete_sel_button);

  constraints_button = new Button("Constraints...");

  p.add(constraints_button); */

  labels_box = new Checkbox("Show labels");

  p_side.add(labels_box);

  mark_button = new Button("Mark arguments");

  p_side.add(mark_button);

  placeholder_button = new Button("Arguments -> _");

  p_side.add(placeholder_button);

  mark_term_button = new Button("Mark subterm");

  p_side.add(mark_term_button);

  placeholder_term_button = new Button("Subterm -> _");

  p_side.add(placeholder_term_button);

  p.setBackground(Color.white);

  p_side.setBackground(Color.white);

  add("South", p);

  add("East", p_side);

  pack(); // (*** unbed. noetig fuer SUN AWT 1.0.2, sonst evtl. Absturz)
resize(bounds().width, bounds().height+30); //** sonst oft Buttons verdeckt wg. "Sicherheitswarnungs-"Zeile unten

 }


 void set(String text) {

  textarea.setText(translate_LF(text));

 }

 void put_cursor_on_error(int error_pos) {

  textarea.select(error_pos, error_pos);

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY) {

   hide();

   dispose();

   parent.selection = null;

   return true;
 
  } else if(e.id==Event.ACTION_EVENT) {

   if(e.target==activate_button) {

    parent.activate_selection(textarea.getText(), true, true);

    return true;

   } else if(e.target==refresh_button) {

    parent.refresh_selection_text();

    return true;

   } else if(e.target==cancel_button) {

    hide();

    dispose();

    parent.selection = null;

    return true;

  /* obsolet: } else if(e.target==send_sel_button) {

	if(labels_box.getState()) // Prolog-Datenbank kann mit Labels nix anfangen
     parent.send_selection(parent.root.id_labels_swap(textarea.getText()));
	else
     parent.send_selection(textarea.getText());

   } else if(e.target==complete_sel_button) {

	if(labels_box.getState()) // Prolog-Datenbank kann mit Labels nix anfangen
     parent.complete_selection(parent.root.id_labels_swap(textarea.getText()));
	else
     parent.complete_selection(textarea.getText());

   } else if(e.target==constraints_button) {

    parent.constraints();
   */

   } else if(e.target==mark_term_button) { 
	// markiert den Unterausdruck, auf dem der Cursor steht

	mark_term();

	textarea.requestFocus();

   } else if(e.target==placeholder_term_button) { 
	// ersetzt Unterausdruck durch Platzhalter _ (nuetzlich fuer Requests)

    mark_term();

	String sel = textarea.getText();

	if(textarea.getSelectionEnd()>textarea.getSelectionStart())
     set(sel.substring(0, textarea.getSelectionStart())+"_"+sel.substring(textarea.getSelectionEnd()));

   } else if(e.target==mark_button) { 
	// markiert Argument(-e) des Ids, auf dem der Cursor steht

	mark_arg();

	textarea.requestFocus();

   } else if(e.target==placeholder_button) { 
	// ersetzt Argumente durch Platzhalter _ (nuetzlich fuer Requests)

	mark_arg();

	String sel = textarea.getText();

	if(textarea.getSelectionEnd()>textarea.getSelectionStart())
     set(sel.substring(0, textarea.getSelectionStart())+"_"+sel.substring(textarea.getSelectionEnd()));
	 
   } else if(e.target==labels_box) {

	set(parent.root.id_labels_swap(textarea.getText()));

    if(labels_box.getState()) { 

     placeholder_button.disable();

     mark_term_button.disable();

     placeholder_term_button.disable();

     mark_button.disable();

    } else {

     placeholder_button.enable();

     mark_term_button.enable();

     placeholder_term_button.enable();

     mark_button.enable();

    }

    return true;

   }
 
  }

  return super.handleEvent(e);

 }

 
 void mark_term() {

    int pos = Math.max(textarea.getSelectionStart(), textarea.getSelectionEnd()), 
        end_pos = pos, start_pos = pos;

    //*** unter Linux SuSE 5.0 tritt folgender seltsamer Fehler auf:
    // der erste Aufruf von getSelection...() liefert immer 0,
    // nach der ersten Selektion vom Anwender gibt's korrekte Werte!, daher:

	if(pos<=0) {

     InfoDialog.info(AOT.APPLET_NAME+" Error", "Please double-click on an identifier before", InfoDialog.OK, null);

     return;

    }	 

	String sel = textarea.getText();
   
	//folgender Algo. deckt nicht alle Faelle ab, tut's aber wohl:

    int brc = 0;
   
    while(end_pos<sel.length()&&(sel.charAt(end_pos)!=','&&sel.charAt(end_pos)!=')'||brc!=0||sel.charAt(end_pos-(end_pos>0?1:0))=='\\')) {

     if(sel.charAt(end_pos)=='(')
      ++brc;
     else if(sel.charAt(end_pos)==')')
      --brc;

     ++end_pos;
    
    }

    brc = 0;

    while(start_pos>0&&(sel.charAt(start_pos)!=','&&sel.charAt(start_pos)!='('||brc!=0||sel.charAt(start_pos-(start_pos>0?1:0))=='\\')) {

     if(sel.charAt(start_pos)=='(')
      ++brc;
     else if(sel.charAt(start_pos)==')')
      --brc;

     --start_pos;
    
    }

    textarea.select(start_pos+1/*-(AOT.BROWSER_Navigator?1:0)*/, end_pos/*-(AOT.BROWSER_Navigator?1:0)*/); //(!)***

 }


 void mark_arg() { 
 
  mark_arg(true);

 }

 /** markiert in textarea das/die Argument(-e) des Identifizierers/Labels,
 auf dem der Cursor steht */
 void mark_arg(boolean start_on_id) { 

    int pos = Math.max(textarea.getSelectionStart(), textarea.getSelectionEnd()), 
        end_pos;

    //*** unter Linux SuSE 5.0 tritt folgender seltsamer Fehler auf:
    // der erste Aufruf von getSelection...() liefert immer 0,
    // nach der ersten Selektion vom Anwender gibt's korrekte Werte!, daher:

	if(pos<=0) {

     InfoDialog.info(AOT.APPLET_NAME+" Error", "Please double-click on an identifier before", InfoDialog.OK, null);

     return;

    }	 

	String sel = textarea.getText();

	//folgender Algo. deckt nicht alle Faelle ab, tut's aber wohl:

    if(start_on_id) {

 	 char right_boundary = labels_box.getState()?'\"':'(';

	 while(pos<sel.length()&&(sel.charAt(pos)!=right_boundary||sel.charAt(pos-(pos>0?1:0))=='\\'))
	  ++pos; 

	 if(right_boundary=='\"')
 	  while(pos<sel.length()&&sel.charAt(pos)!='(')
	   ++pos;

    }

    end_pos = pos;

	boolean inside_string = false;

	int brck = 0;

	while(end_pos<sel.length()) {
 
	 if(sel.charAt(end_pos)=='\\')
	  ++end_pos;
	 else {
		 
	  if(sel.charAt(end_pos)=='\"')
	   inside_string = !inside_string;
	  else if(!inside_string) {
     
	   if(sel.charAt(end_pos)=='(')
		++brck;
	   else if(sel.charAt(end_pos)==')')
		if(--brck<=0)
		 break;

      }
 
     } 

     ++end_pos;

    }

	if(end_pos>pos)
	 textarea.select(pos+1/*-(AOT.BROWSER_Navigator?1:0)*/, end_pos/*-(AOT.BROWSER_Navigator?1:0)*/); //(!)***

 }

 
 /** entfernt \r (noetig, weil das JDK-Java (u.a.) sonst fehlerhaft arbeitet) */
 String translate_LF(String txt) {

  if(AOT.INTERPRETER_Jview)
   return txt;

  String new_txt = "";

  for(int i = 0; i<txt.length();i++) 
   if(txt.charAt(i)!='\r')
    new_txt += txt.charAt(i);

  return new_txt;

 }

}


/** vgl. View.decision_help_label, HTML-faehig */
class DecisionHelpDialog extends Dialog { 

 final static String decision_help_howto_text = "Move the mouse pointer on the edge or node of interest";

 final static String decision_help_howto_mark_text = "Click on the object of interest";

 final static String decision_help_sorry = ""; 

 final static String east = "East", west = "West", north = "North", south = "South";  // (naja...)

 Document parent;

 TextArea edge_textarea = null, node_textarea = null, common_textarea = null;

 BrowserInterface/* direkt keine Panels, siehe BrowserInterface.panel1!:*/ 
   edge_html_panel = null, node_html_panel = null, common_html_panel = null;

 /** fuer die gleichzeitige Darstellung von Edge- und Node-Help */
 Panel split_panel = null; 

 Button cancel_button;

 /** ob Darstellung bei Mausbewegung aktualisiert werden soll (false)
     oder beim Markieren eines Objekts */
 Checkbox hold_check, hide_commentary_check; 

 /** Binaerflags im Zusammenhang mit remove_views() */
 public final static int WEST = 1, EAST = 2;

 public final static int ROWS = 25, COLS = 55;

 
 
 DecisionHelpDialog(Document parent) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Object notes", false);

  this.parent = parent;

  Panel p = new Panel();

  p.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 5));

  cancel_button = new Button(" Close ");

  p.add(cancel_button);

  p.add(hold_check = new Checkbox("Hold"));

  hold_check.setState(true);

  p.add(hide_commentary_check = new Checkbox("Hide node commentary"));

  hide_commentary_check.setState(true);

  postEvent(new Event(hold_check, Event.ACTION_EVENT, new Boolean(true)));

  postEvent(new Event(hide_commentary_check, Event.ACTION_EVENT, new Boolean(true)));

  if(!AOT.INTERPRETER_Jview)
   hide_commentary_check.disable(); //*** Kante+Node-Kommentar gleichzeitig fkt. mit dem JDK-Java nicht!

  p.setBackground(Color.white);

  add(south, p);

  pack();

resize(bounds().width, bounds().height+30); //** sonst oft Buttons verdeckt wg. "Sicherheitswarnungs-"Zeile unten
  move(300, 200);

  show();

 }


 void remove_views(int where) {

  if((where & WEST) !=0)
   if(edge_textarea!=null) {

    if(split_panel!=null)
     split_panel.remove(edge_textarea);
 
    edge_textarea = null;

   }

  if((where & EAST) !=0)
   if(node_textarea!=null) {

    if(split_panel!=null)
     split_panel.remove(node_textarea);

    node_textarea = null;

   }

  if(common_textarea!=null) {

   remove(common_textarea);
 
   common_textarea = null;

  }

  if((where & WEST) !=0)
   if(edge_html_panel!=null) {

    if(split_panel!=null)
     split_panel.remove(edge_html_panel.panel1);
 
    edge_html_panel = null;

   }

  if((where & EAST) !=0)
   if(node_html_panel!=null) {

    if(split_panel!=null)
     split_panel.remove(node_html_panel.panel1);
 
    node_html_panel = null;

   }

   if(common_html_panel!=null) {

    remove(common_html_panel.panel1);
 
    common_html_panel = null;

   }

 }

 void set_help(String help, boolean force) {

  if(!hold_check.getState()||force) {

   if(help!=null) {

    if(split_panel!=null) {

     remove(split_panel);

     split_panel = null;

    }

    if(help.startsWith("<!")||help.startsWith("<HTML")||help.startsWith("<HEAD")) {

     if(common_html_panel==null) {

      remove_views(EAST | WEST);

      common_html_panel = new BrowserInterface(this, bounds().width/2, bounds().height, help, "Center", true, null);
       // die Groessenangaben ^ sind zwar Dummies, aber height muss >= 30 sein, weil sonst die Scrollbalken fehlen!

      validate();

      common_html_panel.inplace = true;

      common_html_panel.URL_Process("internal_.html");

      resize(bounds().width+1, bounds().height+1);/* resize(bounds().width-1, bounds().height-1); */
      show();  // *** ^Hack notw., weil mit dem JDK-Java sonst die Anzeige "haengt"
  
     } else if(!common_html_panel.content.equals(help)) {

      common_html_panel.content = help;
  
      common_html_panel.URL_Process("internal_.html");

     }

     validate();

    } else {

     if(common_textarea==null) {

      remove_views(EAST | WEST);

      common_textarea = new TextArea(decision_help_howto_text, ROWS, COLS);

      common_textarea.setFont(new Font("Dialog", Font.PLAIN, 14));

      add("Center", common_textarea);

      layout();

     } 

     if(!common_textarea.getText().equals(help))
      common_textarea.setText(help);

    }

   }
  
  }

 }


 void set_help(String edge_help, String node_help) {
 // mit anderen Interpretern als jview fkt. die Doppel-Darstellung leider nicht gut!

  if(hold_check.getState()) {

   if(edge_help==null) 
    edge_help = "";

   if(hide_commentary_check.getState()) {

    set_help(edge_help, true);

    return;

   }

   if(true) {

    if(edge_help.startsWith("<!")||edge_help.startsWith("<HTML")||edge_help.startsWith("<HEAD")) {

     if(edge_html_panel==null) {

      remove_views(WEST);

      if(split_panel==null) {

       split_panel = new Panel();

       split_panel.setLayout(new BorderLayout() /*GridLayout(1, 2)*/);

       add("Center", split_panel);

      }

      edge_html_panel = new BrowserInterface(split_panel, split_panel.bounds().width/2, split_panel.bounds().height, edge_help, west, true, null);
       // die Groessenangaben ^ sind zwar Dummies, aber height muss >= 30 sein!

     } 

     edge_html_panel.content = edge_help;

     edge_html_panel.URL_Process("internal_.html"); 

     validate();

    } else {

     if(edge_textarea==null) {

      remove_views(WEST);

      if(split_panel==null) {

       split_panel = new Panel();

       split_panel.setLayout(new BorderLayout());

       add("Center", split_panel);

      }

      edge_textarea = new TextArea(decision_help_howto_mark_text, ROWS, COLS);

      edge_textarea.setFont(new Font("Dialog", Font.PLAIN, 14));

      split_panel.add(west, edge_textarea);

      split_panel.layout();

      layout();

     } 

     edge_textarea.setText(edge_help);

    }

   }


   }

   if(node_help==null) 
    node_help = "";

   if(true) {

    if(node_help.startsWith("<!")||node_help.startsWith("<HTML")||node_help.startsWith("<HEAD")) {

     if(node_html_panel==null) {

      remove_views(EAST);

      if(split_panel==null) {

       split_panel = new Panel();

       split_panel.setLayout(new BorderLayout());

       add("Center", split_panel);

      }

      node_html_panel = new BrowserInterface(split_panel, split_panel.bounds().width/2, split_panel.bounds().height, node_help, east, true, null);
       // die Groessenangaben ^ sind zwar Dummies, aber height muss >= 30 sein!

     }

     node_html_panel.content = node_help;

     node_html_panel.URL_Process("internal_.html");

     validate();

    } else {

     if(node_textarea==null) {

      remove_views(EAST);

      if(split_panel==null) {

       split_panel = new Panel();

       split_panel.setLayout(new BorderLayout());

       add("Center", split_panel);

      }

      node_textarea = new TextArea("", ROWS, COLS);

      node_textarea.setFont(new Font("Dialog", Font.PLAIN, 14));

      split_panel.add(east, node_textarea);

      split_panel.layout();

      layout();

     } 

     node_textarea.setText(node_help);

    }

  }

  validate();

}


 public boolean handleEvent(Event e) {

  if(e.id==Event.WINDOW_DESTROY||e.id==Event.ACTION_EVENT&&e.target==cancel_button) {

   hide();

   dispose();

   parent.decision_help = null;

   return true;
 
  } else if(e.id==Event.ACTION_EVENT&&(e.target==hold_check||e.target==hide_commentary_check)) {

   if(((Boolean)e.arg).booleanValue()==true) {

    if(e.target==hold_check&&AOT.INTERPRETER_Jview) //*** siehe Konstruktor
     hide_commentary_check.enable();

    set_help(decision_help_howto_mark_text, decision_help_howto_mark_text);

   } else {

    if(e.target==hold_check)
     hide_commentary_check.disable();

    set_help(decision_help_howto_text, false);

   } 

  }

  return super.handleEvent(e);

 }

}


/** Hier zeigt der Applet-Host (d.h. die Prolog-Datenbank) indirekt Resultate an */
class HostDialog extends Dialog {

 /** anfaengliche Position*/
 final static int POS_X = 230, POS_Y = 180;

 Document document;

 TextComponent textarea;

 Button ok_button, help_button;


 HostDialog(Frame parent, Document document, String completion) {

  super(parent, "Completion", false/*unbedingt modeless, da mehrere dieser Dialoge zugleich offen sein koennen!*/);

  this.document = document;

  textarea = new TextArea(completion, 20, 70);

  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(" Close ");

  p.add(ok_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 (kein Witz!)

 }


 public boolean handleEvent(Event e) {

  if(e.id==Event.KEY_PRESS&&e.key==27||e.id==Event.WINDOW_DESTROY) { //Esc

   hide();

   dispose();

   AOT.Aot.document.view.canvas.requestFocus();

   document.host_dialog = null;

   return true;

  }

  if(e.id==Event.ACTION_EVENT) {

   if(e.target==ok_button) {  //(eigentl. <Close>)

    hide();

    dispose();

    AOT.Aot.document.view.canvas.requestFocus();

    document.host_dialog = null;

    return true;
  
   } else if(e.target==help_button) {

	AOT.online_help("cats_sel.html");
      
    return true;

   } 

  }

  return super.handleEvent(e);

 }

}


/** Zwischengespeicherte Selektion (gebunden an ein Dokument, 
    gespeichert in der zugeh. .cat-Datei)*/
class Bookmark {

 String name;

 String content;  // content ist ein Selektions-Term (analog wie bei Constraints)

 
 Bookmark(String name, String content) {

  this.name = name;

  this.content = content;

 }


 /** fuegt Bookmark zum Menue im Anwendungs-Frame hinzu */
 void add_to_menu(ApplicationFrame application) {

  if(application!=null)
   application.add_bookmark(this);

 }


 public String toString() {

  return "\r\nselection(\r\n"+
  content  
  +"\r\n,label=\""+AOTParse.escapeString(name)+"\""+").";  

 }

}


/** Fenster zur Erzeugung eines Bookmarks */
class AddBookmarkNameDialog extends Dialog {

 /** anfaengliche Position*/
 final static int POS_X = 420, POS_Y = 220;

 TextComponent textarea;

 Document doc;

 Button ok_button, cancel_button, help_button;


 AddBookmarkNameDialog(Document doc) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Please name the selection", false);

  this.doc = doc;

  textarea = new TextField(60);

  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) {

    Bookmark bookmark = new Bookmark("Noname", 
      doc.get_incomplete_selection(false));

    bookmark.name = new String(textarea.getText());

    Bookmark[] bookmks = new Bookmark[doc.bookmarks.length+1];

    for(int i = 0; i<doc.bookmarks.length; i++)
     bookmks[i] = doc.bookmarks[i];
 
    doc.bookmarks = bookmks; 

    doc.bookmarks[doc.bookmarks.length-1] = bookmark;

    bookmark.add_to_menu(AOT.Aot.application);

    AOT.Aot.document.set_modified(true);
 
    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#bookmarks");
      
    return true;

   }

  } else if(e.id==Event.KEY_ACTION) { 

   //...
  
  }

  return super.handleEvent(e);

 }

}


/** Fenster zur Eingabe eines Kanten-Identifizierers und -Labels */
class SetIdDialog extends Dialog {

 /** anfaengliche Position*/
 final static int POS_X = 400, POS_Y = 200;

 BetterTextField label;

 TextComponent id;

 Document doc;

 Button ok_button, cancel_button, help_button;

 Node target;


 SetIdDialog(Document doc) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Please define the new edge", false);

  this.doc = doc;

  target = doc.marked_node();

  Panel pt = new Panel();

  pt.setLayout(new GridLayout(4, 1));

  id = new TextField(30);

  label = new BetterTextField(50, id);

  label.setFont(new Font("Courier", Font.PLAIN, 12));

  id.setFont(new Font("Courier", Font.PLAIN, 12));

  pt.add(new Label("Label:"));

  pt.add(label);

  pt.add(new Label("Identifier:"));

  pt.add(id);

  add("North", pt);

  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) {

    String id_str = new String(id.getText().trim()), 
           label_str = new String(label.getText().trim());

    if(Edge.check(id_str, AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy)) {

     doc.view.mark_edge(doc.view.find_neuro(target), true);  
     //^notw., falls der Endanwender zwischenzeitlich einen anderen Knoten markiert hat
     
     doc.new_branch(false, null, true);

     doc.marked_node().edge.set_label(label_str);

     doc.marked_node().edge.set_id(id_str);

     doc.toggle_activation();

    }

    //AOT.Aot.document.set_modified(true);  hier nicht, da Aufruf durch Endanwender!
 
    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;

   }

  } 

  return super.handleEvent(e);

 }

}


/** aktualisiert bei Eingaben auch autom. id */
class BetterTextField extends TextField { 

 TextComponent id;


 BetterTextField(int cols, TextComponent id) {

  super(cols);

  this.id = id;

 }

 public boolean handleEvent(Event e) {

  if(e.id==Event.KEY_RELEASE/* nicht KEY_PRESS, weil sonst getText alte Werte liefert*/) {

   super.handleEvent(e);

   String ids = new String();

   for(int i = 0; i<getText().length(); i++) {

    char l = getText().charAt(i);

    if((l>='A'&&l<='Z'||l>='a'&&l<='z'||((l=='_'||l>='0'&&l<='9')&&id.getText().length()>0)))
     ids += Character.toLowerCase(l);
    else if(ids.length()>0)
     ids += "_";

   }

   id.setText(ids); 

  }

  return false; 

 }

}


/** Fenster zum Editieren des Knoten-help-Attributes ("Anwender-Kommentarfeld") */
class CommentaryDialog extends Dialog {

 /** anfaengliche Position*/
 final static int POS_X = 500, POS_Y = 200;

 TextArea commentary;

 Document doc;

 Button ok_button, cancel_button, help_button;

 Node target;


 CommentaryDialog(Document doc) {
  
  super(AOT.Aot.application!=null?AOT.Aot.application:AOT.dummy, "Please comment on the marked node", false);

  this.doc = doc;

  target = doc.marked_node();
  
  commentary = new TextArea(target.help==null?"":target.help, 5, 60);

  commentary.setFont(new Font("Courier", Font.PLAIN, 12));

  add("North", commentary);

  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) {

    target.help = new String(commentary.getText());

    //AOT.Aot.document.set_modified(true);  hier nicht, da Aufruf durch Endanwender!

    AOT.Aot.document.set_selection_modified(true);
 
    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#commentary");
      
    return true;

   }

  } 

  return super.handleEvent(e);

 }

}

