
/**

   View.java
  
   Graphische Anzeige von Und-/Oder-Baeumen (Baum-"Ansicht") 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.util.*;

import Node; // interne Baumrepraesentation ("Baum-Modell")

import InplaceLayout;  // Layout-Manager zur Unterstuetzung von Inplace-Formularen


/** Baum-Ansicht */
class View extends Panel {

  /* *** der Zugriffsmodifizierer "private protected" ist unter J++ 1.0
  leider nicht verfuegbar. Er sollte fast ueberall statt private eingesetzt werden! */
 
  /** Laenge einer Kante*/
  private static int edge_width = 70;

  /** Hoehe einer Kante*/
  private static int edge_height = 34;

  /** Radius der Oder-Kringel (Und = Oder/2)*/
  private static int node_radian = 7; 

  private static Color active_color = Color.black, passive_color = Color.gray,
	             selection_color = Color.gray, selection_color2 = selection_color,
				 drag_source_color = Color.blue, drag_target_color = selection_color/*<diesen Wert nicht aendern*/,
				 decision_point_color = Color.red, inset_color = Color.lightGray /*new Color(255, 223, 191)*/;

  /** aktive Kanten breiter zeichnen?*/
  private static boolean thick_active_lines = true; 

  /** Abstand Diagramm<->Rand */
  private static int inset_x = 10, inset_y = 10; 

  /** Categories Standard-Schrift*/
  private static String default_font = "Dialog";  //***(wg. Problemen mit Fontmetrics vorerst nicht aendern!)

  private static int edge_font_size = 0, text_font_height = 0, edge_font_height = 0;

  /** linke obere Ecke des Baumdiagramms im Canvas */
  private static Point initial_position; 

  /** (siehe collect_edges())*/
  private static boolean collecting_edges = false; 

  /** Hintergrund-Puffer zum Zeichnen verwenden ("Anti-Flacker");
      wird im inplace-Modus unter Motif deaktiviert. */
  static boolean buffered_painting = true;

  /** Scrolling beim Dragging von Objekten erlauben? */
  static boolean scrollable = true;

  /** Formularmodus? */
  static boolean allow_inplace = false; 

  private static Font node_font, edge_font, textnode_font, filename_font, help_font;

  private static FontMetrics node_fontmetrics = null, edge_fontmetrics = null, 
    text_fontmetrics = null;

  /** von diesem Dokument ist das View Bestandteil (Baummodell: document.root) */
  Document document; 

  /** Clientbereich ("Innenflaeche") des Views*/
  ViewCanvas canvas; // direkter Zugriff durch ViewCanvas

  /** Container, der optional canvas enthaelt und selbst in View 
      als Komponente enthalten ist. Er dient dazu, ein fuer inplace-Controls
	  geeignetes Layout (vgl. class InplaceLayout) bereitzustellen */
  Panel inplace_panel = null;

  private Scrollbar hbar, vbar;  

  /** Hilfspanel zur Anordnung mehrerer Components im View-"Sueden" 
   und von Shortcut-Buttons im "Norden" */
  private Panel south_panel;

  Label decision_help_label;

  /** zwecks Scrolling*/
  int offset_x, offset_y;  // direkter Zugriff durch ViewCanvas

  /** Gesamtgroesse des Baums*/
  Dimension tree_size; 

  /** Tabelle von Paaren (Rechteck um Kante, folgenden Knoten)
  (wird beim ersten Zeichnen initialisiert)*/
  private Dictionary edge_table = null; 
  
  /** beschreibt den markierten Kantentext/Knoten*/
  Neuralgic recent_neuro = null;  // direkter Zugriff durch ViewCanvas

  /** Ziel-Kontext der Graphikausgaben*/
  Graphics target_g;  // direkter Zugriff durch ViewCanvas

  /** nur aktivierte Objekte darstellen? */
  boolean activated_only = false;	

  
  static {

   initial_position = new Point(inset_x, inset_y); // Abstand des Diagramms zum Fensterrand
   
   node_font = new Font(default_font, Font.BOLD, 8);

   edge_font = new Font(default_font, Font.PLAIN, 14); 

   filename_font = new Font(default_font, Font.PLAIN, 14);

   textnode_font = new Font(default_font, Font.PLAIN, 14);
   
   help_font = new Font("Helvetica", Font.ITALIC, 14);

   // (FontMetrics kann hier noch nicht abgefragt werden, weil das Graphics-Objekt fehlt)

  }

  
  View(Document document) {
   
   this.document = document;

   canvas = new ViewCanvas(this);  // die eigentliche Darstellungsflaeche

   if(allow_inplace) {

    inplace_panel = new Panel();

	InplaceLayout m_Layout = new InplaceLayout(inplace_panel, 1, 1);

	inplace_panel.setLayout(m_Layout);
	
   } else
	inplace_panel = null;

   tree_size = new Dimension(1000, 13000); // (*** reine Optik, keine Funktionalitaet)

   hbar = new Scrollbar(Scrollbar.HORIZONTAL);
   // *** die Scrollbars lassen sich (zumindest unter Java 1.0/Windows) nicht korrekt programmieren

   vbar = new Scrollbar(Scrollbar.VERTICAL);

   vbar.setBackground(inset_color);

   decision_help_label = new Label();

   decision_help_label.setBackground(inset_color);

   decision_help_label.setFont(help_font);

   south_panel = new Panel();

   south_panel.setLayout(new BorderLayout(0, 0));

   setLayout(new BorderLayout(0, 0));

   if(inplace_panel!=null) {

	add("Center", inplace_panel);

    inplace_panel.add(canvas);

  	((InplaceLayout)(inplace_panel.getLayout())).setShape(inplace_panel, canvas, 0, 0, 1, 1);

   } else   
    add("Center", canvas);

   south_panel.add("North", hbar);

   add("East", vbar);

   south_panel.add("South", decision_help_label); // vgl. Document.display_decision_help()

   south_panel.setBackground(inset_color);

   add("South", south_panel);

  }


  /** entfernt aus inplace_panel alle Komponenten bis auf das View-Canvas
   Wird beim Oeffnen eines neuen Dokuments gebraucht, damit man das
   alte View weiterverwenden kann, ohne "haengende" Komponenten zu bekommen. */
  void remove_atoms() {
   
   if(inplace_panel!=null)
    for(int i = inplace_panel.countComponents()-1; i>=0; i--) {

	 try {

	  Component c = inplace_panel.getComponent(i);

	  if(!(c instanceof Canvas))
	   remove_atom(c);

	 } catch(Exception xcpt) { }

    }

  }


  /** entfernt aus inplace_panel eine Komponente */
  void remove_atom(Component c) {

   ((InplaceLayout)(inplace_panel.getLayout())).m_map.remove(c);

   inplace_panel.remove(c);
    
  }


  /** Lokalisiert inplace-Komponente 
      (Koordinaten relativ zum inplace_panel bzw. ViewCanvas (dasselbe)) */
  Component locate_atom(int x, int y) {

   for(int i = inplace_panel.countComponents()-1; i>=0; i--) {

    try {

	 Component c = inplace_panel.getComponent(i);

     if(c.bounds().inside(x, y)&&!(c instanceof Canvas))
	  return c;

	} catch(Exception xcpt) { }

   }

   return null;

  }


  /** Ziel-Kontext der Graphikausgaben*/
  synchronized Graphics g() {  

   return target_g;

  }


  public boolean handleEvent(Event e) {

   if(e.target==hbar) {

	switch(e.id) {

	 case Event.SCROLL_LINE_UP:

     case Event.SCROLL_LINE_DOWN:

     case Event.SCROLL_PAGE_UP:

     case Event.SCROLL_PAGE_DOWN:

     case Event.SCROLL_ABSOLUTE:
      
	  offset_x = ((Integer)e.arg).intValue();  //*** unter Win95 falsche Werte

	  break;

    }

    canvas.update();  // (*** bewirkte unter Motif seltsamerweise ein vierfaches(!) Neuzeichnen)

    return true;

   } else if(e.target==vbar) {

 	switch(e.id) {
	 
	 case Event.SCROLL_LINE_UP:

	 case Event.SCROLL_LINE_DOWN:
	 
	 case Event.SCROLL_ABSOLUTE:
      
	 case Event.SCROLL_PAGE_UP:  

	 case Event.SCROLL_PAGE_DOWN:

	  offset_y = ((Integer)e.arg).intValue();  

	  break;

	 //*** arg liefert zumindest unter Windows falsche Werte!

    }

    canvas.update(); 

    return true;

   } else if(e.id==Event.KEY_PRESS) {  // ASCII Tasten:

	switch(e.key) {

     case '\n':  // Enter (nur falls kein ApplicationFrame vorhanden; die ausfuehrliche Version davon gibt's in ApplicationFrame)

	  if(AOT.Aot.application==null) {

	   if(e.controlDown())
	    document.edit_leaf_text();
       else 
        document.toggle_activation();

       return true;

	  }

    }
	 
   } else if(e.id==Event.KEY_ACTION) {  // nicht-ASCII Tasten:

	switch(e.key) {

     case Event.PGDN:

	  if(offset_y+canvas.bounds().height<tree_size.height)
 	   offset_y += canvas.bounds().height*.8; 

      canvas.update();

      return true;

     case Event.PGUP:

  	  if((offset_y -= canvas.bounds().height*.8)<0)
	   offset_y = 0;

      canvas.update();

      return true;

     case Event.DOWN:

     case Event.RIGHT:

	  if(!e.controlDown()) {
 
       mark_next();
 
	   return true;

      }

	  break;

     case Event.UP:

	  if(!e.controlDown()) {

	   mark_prev();
 
	   return true;

	  }

	  break;

     case Event.LEFT:

	  if(!e.controlDown()) {

	   mark_parent();
 
	   return true;

	  }

	  break;

     case Event.HOME:  // (<Pos1> auf MFII-Tastaturen)

      mark_first();
 
	  return true;

     case Event.END:

      mark_last();
 
	  return true;

	}

	// (Tasten, die sich speziell auf ApplicationFrame beziehen, sind in AOT.java definiert)

   }

   return super.handleEvent(e);

  }


  public synchronized void reshape(int x, int y, int width, int height) {

   super.reshape(x, y, width, height);

   reshaped();

  }


  void reshaped() {

   if(inplace_panel!=null)
    ((InplaceLayout)(inplace_panel.getLayout())).setShape(inplace_panel, canvas, 0, 0, inplace_panel.bounds().width,  inplace_panel.bounds().height);

   int canvas_width, canvas_height;

   canvas_width = canvas.bounds().width;

   canvas_height = canvas.bounds().height;

   hbar.setValues(offset_x, canvas_width, 1, tree_size.width-canvas_width<offset_x?tree_size.width:tree_size.width-canvas_width*(AOT.INTERPRETER_Jview?1:0));  //***

   vbar.setValues(offset_y, canvas_height, 1, tree_size.height-canvas_height<offset_y?tree_size.height:tree_size.height-canvas_height*(AOT.INTERPRETER_Jview?1:0));

   hbar.setPageIncrement((int)(canvas_width*.8<1?1:canvas_width*.8)); 
   // (*** wird leider von Windows 95 (u.a.?) voellig ignoriert!)

   vbar.setPageIncrement((int)(canvas_height*.8<1?1:canvas_height*.8)); 

   hbar.setLineIncrement(edge_height);

   vbar.setLineIncrement(edge_height);

 }


 /** Flaeche usw. des gerade markierten Knotens*/
 Neuralgic get_recent_neuro() { 

  if(recent_neuro==null) {

   //System.out.println("Internal warning: Searching for neuralgic object");

   if(edge_table==null&&canvas!=null)
	canvas.update();

   if(edge_table!=null) // andernfalls kann die Tabelle nicht berechnet werden, 
    mark_root();        // weil view noch nie gezeichnet wurde (z.B. Clipboard-View)
    
  }

  return recent_neuro;

 }


 /** View (d.h. gesamten Baum) neu zeichnen*/
 synchronized  Dimension draw() {  

  Point position = new Point(initial_position.x-offset_x, initial_position.y-offset_y);

  if(edge_table==null) {

   edge_table = new Hashtable();  // besser geeignet als ein Vektor, da beim "Sammeln" 
    //der Eintraege diese nicht immer ans Tabellenende angehaengt werden koennen. 
    //Grund: Ein Kanten-Eintrag (Neuralgic) braucht Infos, die erst nach dem Durchlauf des Unterbaums feststehen. 

   collecting_edges = true; // edge_table fuellen

  }

  Area area = document.root.display(position, true, this);

  if(collecting_edges) { // Wurzelknoten extra zu edge_table hinzufuegen (da er keine hinfuehrende Kante hat, geschieht dies nicht automatisch in draw_children())

   add_neuralgic_edge(document.root, new Rectangle(0, (int)(-edge_font.getSize()), 0, 0), area);

   collecting_edges = false;

  }

  tree_size.width = area.dimension.width+initial_position.x; //(Abstand<->Rand)

  tree_size.height = area.dimension.height+initial_position.y;

  return area.dimension;

 }


 /** ueberlaedt >nicht< die AWT-Methode print(..) */
 void print() { 

  if(AOT.java_102) {

   System.out.println("Printing does not work yet");

   InfoDialog.info(AOT.APPLET_NAME, "Sorry, no printing yet", InfoDialog.OK, null);

  } else { // Druck ist leider nicht fuer Applets mgl.:

  /* *** muss unter Java 1.0 deaktiviert werden:
		   
   PrintJob pjob = getToolkit().getPrintJob(AOT.Aot.application, "Test-Job", null);

   Graphics pg = pjob.getGraphics().create(0, 0, tree_size.width, tree_size.height);

   target_g = pg;

   canvas.just_paint();

   canvas.update(); */

  }

 }

 
 synchronized Rectangle draw_AND_Node(Point position, boolean active, boolean leaf, String name, boolean compressed) {
  
  Graphics g = g();

  if(position.y>=0&&position.y<2000/*g.getClipRect().height*/) { 
  // (*** diese Abfrage muss so sein, weil das Clipping unter Windows 95/Java 1.0
  // leider nicht richtig funktioniert!)
 
   g.setColor(active?active_color:passive_color);  

   if(leaf&&name==null)
    g.drawLine(position.x+1, position.y-node_radian/2, position.x+1, position.y+node_radian/2);

   else {

	g.fillOval((int)(position.x+node_radian*.5), (int)(position.y-node_radian*.5), (int)(node_radian), (int)(node_radian));
    // man beachte, dass in Java position nicht den Mittelpunkt der Ellipse angibt,
	// sondern die linke obere Ecke des die Ellipse umfassenden Rechtecks.
  
	if(!compressed&&!leaf) {

     g.drawLine(position.x+node_radian, (int)(position.y+node_radian*.5+2), position.x+node_radian, (int)(position.y+node_radian*1.0+2));

	 if(thick_active_lines) {

	  if(!active)
	   g.setColor(Color.white);

      g.drawLine(position.x+node_radian-1, (int)(position.y+node_radian*.5+2), position.x+node_radian-1, (int)(position.y+node_radian*1.0+2));

	 }

	}

   }

   if(name!=null&&AOT.Aot.access_mode==AOT.SUPERUSER) {

	g.setFont(filename_font);

    g.setColor(passive_color);  

    g.drawString("["+name+"]"+(compressed?"...":""),(int)(position.x+(float)node_radian*2),(int)(position.y+node_radian*.6));

   } else if(compressed) {

	g.setFont(filename_font);

    g.setColor(passive_color);  

    g.drawString("...",(int)(position.x+(float)node_radian*2),(int)(position.y+node_radian*.6));  

   }

  }

  return new Rectangle((int)(position.x+offset_x+node_radian*.5-1), (int)(position.y+offset_y-node_radian*0.5-1), 
                 	   (int)((node_radian+2)*(leaf?0:1)), (int)((node_radian+2)*(leaf?0:1)));
   // (gedachtes Rechteck um Knotenkringel)

 }


 synchronized Rectangle draw_OR_Node(Point position, boolean active, Node active_child, String name, boolean compressed) {

  Graphics g = g();

  if(position.y>=0&&position.y<2000/*g.getClipRect().height*/) { 
  // (*** diese Abfrage muss so sein, weil das Clipping unter Windows/Java 1.0
  // leider nicht richtig funktioniert!)

   g.setColor(Color.white); // alten Kringel loeschen, da canvas nicht immer
   // als Ganzes geloescht wird

   g.fillOval(position.x, position.y-node_radian, node_radian*2, node_radian*2);

   g.setColor(active?active_color:passive_color);  

   g.drawOval(position.x, position.y-node_radian, node_radian*2, node_radian*2);

   g.setFont(node_font);

   if(active_child==null) {

    if(active)
	 g.setColor(decision_point_color);

    g.drawString("?",(int)(position.x+(float)node_radian*0.8-(AOT.GUI_Windows?1:0)),(int)(position.y+(float)node_radian*.7)+(AOT.GUI_Windows?1:0));  
   
   } else
    g.drawString("!",(int)(position.x+(float)node_radian*0.8+1),(int)(position.y+(float)node_radian*.7)+(AOT.GUI_Windows?1:0));  

   if(name!=null&&AOT.Aot.access_mode==AOT.SUPERUSER) {

	g.setFont(filename_font);

    g.setColor(passive_color);  

    g.drawString("["+name+"]"+(compressed?"...":""),(int)(position.x+(float)node_radian*2.5),(int)(position.y+node_radian*.6));  

   } else if(compressed) {

	g.setFont(filename_font);

    g.setColor(passive_color);  

    g.drawString("...",(int)(position.x+(float)node_radian*2.5),(int)(position.y+node_radian*.6));  

   }

  }

  return new Rectangle((int)(position.x+offset_x-1), (int)(position.y+offset_y-node_radian-1), 
   			           (int)(node_radian*2+2), (int)(node_radian*2+2));
  // (Rechteck um Knotenkringel)

 }
 

 synchronized Rectangle draw_iOR_Node(Point position, boolean active, Node[] active_childs, String name, boolean compressed) {

  Graphics g = g();

  if(position.y>=0&&position.y<2000/*g.getClipRect().height*/) { 
  // (*** diese Abfrage muss so sein, weil das Clipping unter Windows/Java 1.0
  // leider nicht richtig funktioniert!)

   g.setColor(Color.white); // alten Kringel loeschen, da canvas nicht immer
   // als Ganzes geloescht wird

   g.fillRect(position.x, position.y-node_radian, node_radian*2, node_radian*2);

   g.setColor(active?active_color:passive_color);  

   g.drawRect(position.x, position.y-node_radian, node_radian*2, node_radian*2);

   g.setFont(node_font);

   if(active_childs.length==0) {

    if(active)
	 g.setColor(decision_point_color);

    g.drawString("?",(int)(position.x+(float)node_radian*0.8-1),(int)(position.y+(float)node_radian*.7)+1);  
   
   } else
    g.drawString("!",(int)(position.x+(float)node_radian*0.8+1),(int)(position.y+(float)node_radian*.7)+1);  

   if(name!=null&&AOT.Aot.access_mode==AOT.SUPERUSER) {

	g.setFont(filename_font);

    g.setColor(passive_color);  

    g.drawString("["+name+"]"+(compressed?"...":""),(int)(position.x+(float)node_radian*2.5),(int)(position.y+node_radian*.6));  

   } else if(compressed) {

	g.setFont(filename_font);

    g.setColor(passive_color);  

    g.drawString("...",(int)(position.x+(float)node_radian*2.5),(int)(position.y+node_radian*.6));  

   }

  }

  return new Rectangle((int)(position.x+offset_x-1), (int)(position.y+offset_y-node_radian-1), 
   			           (int)(node_radian*2+2), (int)(node_radian*2+2));
  // (Rechteck um Knotensymbol)

 }


 synchronized Area draw_Text_Node(Point position, boolean active, String text, String name, Text_Node node) {

   boolean inplace = inplace_panel!=null&&
    	             (node.input_area!=null||active)/*sinnvoll?: &&node.single_line()*/;

   if(inplace&&!active||!inplace&&node.input_area!=null&&inplace_panel!=null) {  
	// inplace-Bereich entfernen, wenn Knoten inaktiviert ist 
    // (sonst wuerde Programm extrem langsam)

	node.remove_inplace_areas();

	inplace = false;

   }

   Graphics g = g();

   if(text_fontmetrics==null) {

    text_fontmetrics = g.getFontMetrics(textnode_font);

    edge_fontmetrics = g.getFontMetrics(edge_font);
 
    text_font_height = text_fontmetrics.getHeight();

    edge_font_size = edge_fontmetrics.getFont().getSize();

   }

   int twidth;

   if(position.y>=0&&position.y<2000 /*g.getClipRect().height*/) { 
   // (*** diese Abfrage muss so sein, weil das Clipping unter Windows 95
   // leider nicht richtig funktioniert!)
 
    g.setColor(active?active_color:passive_color);   

    g.setFont(textnode_font);

    if(text.indexOf("\r")>=0)
     text = text.substring(0, text.indexOf("\r"))+"...";
    else if(text.indexOf("\n")>=0)
     text = text.substring(0, text.indexOf("\n"))+"...";

    twidth = (int)(text_fontmetrics.stringWidth(text));
    // (*** zu FontMetrics-Problematik siehe Bemerkung in draw_children())

	if(!inplace)
     g.drawString(text, position.x+4, position.y+text_font_height/2-3);
	else  // Hintergrund des inplace-Bereichs loeschen:
     g.clearRect(position.x+4, position.y-text_font_height/2-3, 500, text_font_height);
	
    if(name!=null&&AOT.Aot.access_mode==AOT.SUPERUSER) {

     g.setFont(filename_font);

     g.setColor(passive_color);  

    }

   } else 
    twidth = (int)(text_fontmetrics.stringWidth(text));
    // (*** zu FontMetrics siehe Bemerkung in draw_children())

   Rectangle node_rect = new Rectangle(position.x+offset_x+4-2, (int)(position.y+edge_font_height/2+offset_y-text_font_height), 
				  				      (int)(twidth*1.1 +3 /* < sonst sieht man bei leerem String nix*/ ), 
									  (int)(text_font_height));
   // (^ das Rechteck um den Blatt-Text)

   if(inplace)  {

    boolean layout_flag = false;

    if(node.input_area==null) { // neuen inplace-Bereich oeffnen

	 node.open_inplace_area(inplace_panel);
 
     node.input_area.move_inplace(position.x, position.y-text_font_height/2-3); // Dummy-Positionierung (es muss vor ..layout().. immer eine Pos. angegeben sein)

     node.input_area.add_buttons(false); // zum Anzeige-/Eingabe-Bereich passende Buttons (z.B. Open, <<<, >>>) hinzufuegen

	 layout_flag = true;
 	  
    } 

    node.input_area.move_inplace(position.x, position.y-text_font_height/2-3);

    if(node.open_button!=null)
     node.open_button.move_inplace(position.x+node.input_area.preferredSize().width+3, position.y-text_font_height/2+1);

    if(node.back_button!=null)
     node.back_button.move_inplace(position.x+node.input_area.preferredSize().width+3, position.y-text_font_height/2+1+node.open_button.preferredSize().height+2);

    if(node.forward_button!=null)
     node.forward_button.move_inplace(position.x+node.input_area.preferredSize().width+3+node.back_button.preferredSize().width, position.y-text_font_height/2+1+node.open_button.preferredSize().height+2);

	if(layout_flag) {

     refresh_inplace();

     node.input_area.layout(); // weil input_area selbst auch wieder ein Container sein kann

    }

   }

   return new Area(node_rect, new Dimension(twidth<edge_width*2?edge_width*2:twidth, (inplace?(int)((float)(node.input_area.bounds().height-0)/(float)edge_height+1.3)*edge_height:edge_height)));

 }


 synchronized Dimension draw_children(Node[] children, Point position, Node active_child, boolean all_active) {

  Node[] active_childs = new Node[1];

  active_childs[0] = active_child;

  return draw_children(children, position, active_childs, all_active);

 }


 /** Zeichnet Veraestellungen eines Knotens.
   Liefert die Groesse des den Ast umgebenden Rechtecks.
   Parameter:
   all_active = true: alle child-Knoten aktiv (parent AND-Knoten)
   sonst: active_child = null: kein aktiver child-Knoten
   sonst: nur Knoten active_child aktiv (parent OR-Knoten) */
 synchronized Dimension draw_children(Node[] children, Point position, Node[] active_childs, boolean all_active) {
 
  // *** im folgenden fkt. FontMetrics fuer proport. Schriften leider nicht richtig, 
  // daher wird an verschiedenen Stellen mit auf die Schrift Helvetica/Arial p8/10/12
  // zugeschnittenen Werten gearbeitet!

  Graphics g = g();

  int child_vsize = edge_height, vsize = child_vsize, hsize = 0;

  int initial_y = position.y, last_active_y = 0;

  int drawn = 0;

  position.x += node_radian;

  for(int i = 1; i<=children.length; i++) {

   boolean is_child_active = false;

   if(all_active)
	is_child_active = true;
   else 
	for(int j = 0; j<active_childs.length; j++)
	 if(children[i-1]==active_childs[j]) {

	  is_child_active = true;

	  break;

     }

   if(!activated_only||is_child_active) {

	++drawn;

    g.setColor(is_child_active?active_color:passive_color);  

    position.y += child_vsize;

    if(is_child_active)
     last_active_y = position.y;

    Point tabbed = new Point(position.x+edge_width, position.y);

    drawLine(g, position.x+1, position.y, tabbed.x-2+((children[i-1] instanceof AND_Node)?(int)(node_radian/2):0), tabbed.y);

    if(thick_active_lines) {
	 
	 if(!is_child_active)
      g.setColor(Color.white);  

     drawLine(g, position.x+1, position.y+1, tabbed.x-2+((children[i-1] instanceof AND_Node)?(int)(node_radian/2):0), tabbed.y+1);

    }

    if(thick_active_lines&&!is_child_active)
     g.setColor(is_child_active?active_color:passive_color);
   
    String text = children[i-1].edge_label();

    if(text==null)
	 text = new String(" ");

    g.setFont(edge_font);

    if(edge_fontmetrics==null) {

     edge_fontmetrics = g.getFontMetrics(edge_font);  // (*** zu FontMetrics siehe Bemerkung oben)    

     edge_font_height = edge_fontmetrics.getHeight();

     edge_font_size = edge_fontmetrics.getFont().getSize(); // (hier nicht die Schrift-Hoehe verwenden!)

    }

    int edge_pos_x = position.x+5, 
		/* *** keine allgemeingueltige Bestimmung mgl.!: */ 
		edge_pos_y = (int)(position.y-edge_font_size/1.3 -1);
   
    g.drawString(text, edge_pos_x, edge_pos_y);

    Area area = children[i-1].display(tabbed, is_child_active, this);

    Dimension cs = area.dimension;

    if(collecting_edges) {  // edge_table fuellen

     int twidth = (int)(edge_fontmetrics.stringWidth(text));

     Rectangle edge_rect = new Rectangle(edge_pos_x+offset_x-2, (int)(edge_pos_y+offset_y-edge_font_size),
 					                     (int)(twidth*1.1), edge_font_height);
 
	 add_neuralgic_edge(children[i-1], edge_rect, area);

    } 

    child_vsize = cs.height;

    hsize = hsize>cs.width+edge_width?hsize:cs.width+edge_width;

    vsize += child_vsize;

   }

  }

  if(drawn>0) {

   int start_y = initial_y+node_radian+2;

   g.setColor(passive_color);  

   drawLine(g, position.x, start_y, position.x, position.y);

   if(thick_active_lines) {

    g.setColor(Color.white);  

    drawLine(g, position.x-1, start_y, position.x-1, position.y);

   }

   if(last_active_y>0) {

    g.setColor(active_color);  

    drawLine(g, position.x, start_y, position.x, last_active_y);

    if(thick_active_lines)
     drawLine(g, position.x-1, start_y, position.x-1, last_active_y);

   } 

  }

  return new Dimension(hsize, vsize);

 }


 Dimension edge_dimension() {

  return new Dimension(edge_width, edge_height);

 }


 static void drawLine(Graphics g, int x, int y, int xe, int ye) {

  if(Math.abs(y)<45000/*g.getClipRect().height*/&&Math.abs(ye)<45000/*g.getClipRect().height*/)  
  // *** ^ notwendig wg. Clipping-Fehler in Java
   g.drawLine(x, y, xe, ye);

 }


 /** Fuegt eine Kante bzw. den folgenden Knoten node zu edge_table hinzu*/
 synchronized void add_neuralgic_edge(Node node, Rectangle edge_rect, Area area) {

  Rectangle node_rect = area.node_rect; // eigentlicher Knotenbereich

  Neuralgic neuro = new Neuralgic(edge_rect, node_rect, node, node instanceof Text_Node?area.dimension.height:edge_height); 
   // Rechtecke mitspeichern, um Kantentext/Knoten spaeter schnell markieren zu koennen

  int hash = find_neuralgic_row(0, edge_rect.y);

  edge_table.put(new Integer(hash), neuro);

 }


 /** Sucht den Index des zum (absoluten, d.h. Offset-bereinigten) Punkt x,y 
  gehoerigen "empfindlichen Streifen" im Graphen.
  Dabei wird ausgenutzt, daß die Kantentexte im Baum vertikal gleichmaessig angeordnet sind. */
 synchronized int find_neuralgic_row(int dummy_x, int y) {

  return (int)((y-initial_position.y-node_radian+edge_height*3/*ggn. Rundungsfehler*/)/edge_height)+1+3;

 }  


 /** beim naechsten Zeichnen Neuralgics-Tabelle updaten */
 void renew_neuralgics_next() {

  edge_table = null;

 }

 
 boolean painted_yet() { 
 
  return edge_table!=null;

 }


 /** Sucht den Neuralgic, der die Position x, y beinhaltet*/
 synchronized Neuralgic get_neuro(int x, int y) {
  if(edge_table==null)
    return null;

  Neuralgic neuro = (Neuralgic)edge_table.get(new Integer(find_neuralgic_row(x, y)));

  if(neuro!=null) {
 
   Rectangle enlarged = new Rectangle(neuro.rect1.x, neuro.rect1.y, neuro.rect1.width, neuro.rect1.height); 
   // (um auch noch die Kante selbst zu umfassen)

   enlarged.height += edge_font.getSize()+2+1;

   if(enlarged.width<edge_width)
    enlarged.width = edge_width;

   if(!enlarged.inside(x, y) && !neuro.rect2.inside(x, y))
    return null;

  }

  return neuro;

 }  


 synchronized void mark_edge(Neuralgic neuro) {

  mark_edge(neuro, false);

 }
 

 /** markiert einen "empfindlichen" Bereich graphisch*/
 synchronized void mark_edge(Neuralgic neuro, boolean auto_scroll) {
  
  Graphics g = g();

  if(neuro!=null) {

   if(recent_neuro!=null) { // alte Markierung ggf. entfernen
 
	g.setColor(recent_neuro!=canvas.source_neuro?Color.white:drag_source_color);

    g.drawRoundRect(recent_neuro.rect1.x-offset_x, recent_neuro.rect1.y-offset_y, recent_neuro.rect1.width, recent_neuro.rect1.height+2-1, 7, 7);

    if(recent_neuro.node instanceof Text_Node||recent_neuro.node instanceof iOR_Node)
     g.drawRect(recent_neuro.rect2.x-offset_x, recent_neuro.rect2.y-offset_y, recent_neuro.rect2.width, recent_neuro.rect2.height);
    else
     g.drawOval(recent_neuro.rect2.x-offset_x, recent_neuro.rect2.y-offset_y, recent_neuro.rect2.width, recent_neuro.rect2.height);

   }

   Neuralgic prev_recent_neuro = recent_neuro;

   recent_neuro = neuro;

   if(auto_scroll) {
    // Knoten ggf. ins Blickfeld holen:
   
    if(!(new Rectangle(offset_x+edge_width/2, offset_y+edge_height/2, canvas.bounds().width-edge_width, canvas.bounds().height-edge_height)).inside(neuro.rect2.x, neuro.rect2.y)) {

     offset_x += neuro.rect2.x-(offset_x+canvas.bounds().width)+edge_width;

	 if(offset_x<0)
 	  offset_x = 0;

     offset_y += neuro.rect2.y-(offset_y+canvas.bounds().height)+edge_height;

	 if(offset_y<0)
 	  offset_y = 0;

	 canvas.update();

    }

   }

   // Markieren:
  
   g.setColor(canvas.source_neuro==neuro||!canvas.dragging?selection_color:drag_target_color);

   g.drawRoundRect(neuro.rect1.x-offset_x, neuro.rect1.y-offset_y, neuro.rect1.width, neuro.rect1.height+2-1, 7, 7);

   g.setColor(canvas.source_neuro==neuro||!canvas.dragging?selection_color2:drag_target_color);

   if(neuro.node instanceof Text_Node) {

    if(inplace_panel!=null&&((Text_Node)neuro.node).input_area!=null) {

     ((Text_Node)neuro.node).input_area.requestFocus();  // ueberladen!

	} else {

     g.drawRect(neuro.rect2.x-offset_x, neuro.rect2.y-offset_y, neuro.rect2.width, neuro.rect2.height);

     if(AOT.GUI_Windows||!AOT.java_102) //(besser: if(AOT.INTERPRETER_Jview)... ?
	  requestFocus(); //*** <<problematisch unter Win95/Java 1.0:
	                  // Grund: requestFocus() erzeugt intern einen Dummy-Mausklick

	}

   } else {

	if(neuro.node instanceof iOR_Node)
     g.drawRect(neuro.rect2.x-offset_x, neuro.rect2.y-offset_y, neuro.rect2.width, neuro.rect2.height);
	else
     g.drawOval(neuro.rect2.x-offset_x, neuro.rect2.y-offset_y, neuro.rect2.width, neuro.rect2.height);
    
    if(!AOT.GUI_Windows||!AOT.java_102) //(besser: if(AOT.INTERPRETER_Jview)... ?
	 requestFocus(); //*** <<problematisch unter Win95/Java 1.0:
	                 // Grund: requestFocus() erzeugt intern einen Dummy-Mausklick

   }

   if(prev_recent_neuro!=neuro) {  // damit noch nicht aktivierte Properties nicht versehentlich verlorengehen
    
	if(prev_recent_neuro!=null&&inplace_panel!=null)
	 if(prev_recent_neuro.node instanceof Text_Node)
	  if(((Text_Node)(prev_recent_neuro.node)).input_area!=null)
	   if(((Text_Node)(prev_recent_neuro.node)).input_area.text_comp!=null)
        ((Text_Node)(prev_recent_neuro.node)).input_area.text_comp.setText(((Text_Node)(prev_recent_neuro.node)).text);

	document.refresh_properties();

   }

  }

  if(neuro!=null) { //(?)

   document.display_decision_help(neuro.node.edge.help, neuro.node.help);

  }

 }


 /** markiert den Wurzelknoten, ohne ihn ins Blickfeld zu holen (vgl. mark_first)*/
 void mark_root() {  

  mark_edge(find_neuro(document.root));

 }


 void mark_first() {  

  mark_edge(find_neuro(document.root), true);

 }


 /** den im Diagramm dem markieren Knoten vorangehenden Knoten markieren*/
 void mark_prev() { 

  int row = find_neuralgic_row(get_recent_neuro().rect1.x, get_recent_neuro().rect1.y);

  Neuralgic neuro=null;

  int max_count = 0;

  while(neuro==null&&++max_count<200) // 200 steht fuer ca. 200*33 Hoehen-Pixel
   neuro = (Neuralgic)edge_table.get(new Integer(--row));

  if(neuro!=null)
   mark_edge(neuro, true);
 
 }


 /** den im Diagramm dem markieren Knoten folgenden Knoten markieren*/
 void mark_next() { 

  //System.out.println((float)get_recent_neuro().height+"    "+ (float)edge_height);
  
  int row = find_neuralgic_row(get_recent_neuro().rect1.x, get_recent_neuro().rect1.y);

  Neuralgic neuro = null;

  int max_count = 0;

  while(neuro==null&&++max_count<200) // 200 steht fuer ca. 200*33 Hoehen-Pixel
   neuro = (Neuralgic)edge_table.get(
     new Integer((int)((float)get_recent_neuro().height/(float)edge_height)+(row++)
                 )
                );

 if(neuro!=null) 
  mark_edge(neuro, true);

 }

 /** im Diagramm den Vaterknoten des markieren Knoten markieren*/
 void mark_parent() { 

  Node node = get_recent_neuro().node;

  if(node.parent!=null)
   mark_edge(find_neuro(node.parent), true);

 }


 /** den im Diagramm letzten Knoten markieren*/
 void mark_last() { 

  //mark_edge((Neuralgic)edge_table.get(new Integer(edge_table.size()+2)), true); 

  int row = find_neuralgic_row(get_recent_neuro().rect1.x, get_recent_neuro().rect1.y);

  Neuralgic neuro = get_recent_neuro();

  int max_count = 0;

  while(++max_count<32767) { // (der Holzhammer...)

   Neuralgic neuro_tmp = (Neuralgic)edge_table.get(new Integer(++row));

   if(neuro_tmp!=null)
    neuro = neuro_tmp;

  }

  if(neuro!=null)
   mark_edge(neuro, true);
 
 }


 /** sucht neuralgische Flaeche zu Knoten*/
 synchronized Neuralgic find_neuro(Node node) { 

  Neuralgic neuro = null;
 
  try {

   Enumeration enum = edge_table.elements();

   while(enum.hasMoreElements())
    if((neuro = (Neuralgic)enum.nextElement()).node==node) {

     //System.out.println("found "+neuro+"  "+node);

     return(neuro);
  
    }

  } catch(NullPointerException xcpt) {

   //System.out.println("Internal warning: find_neuro() fail!");

  } // vgl. paint()

  return neuro;

 }


 boolean inplace_enabled() {

  return inplace_panel!=null;

 }

 
 /** fuegt eine neue inplace-Komponente hinzu (ohne sie schon zu positionieren) */
 synchronized void add_inplace(Component c) {

  inplace_panel.add(c);
  //...

 }


 synchronized void refresh_inplace() {

  inplace_panel.layout();

 }


 /** Positioniert eine inplace-Komponente und legt ihre Groesse fest */
 synchronized void move_inplace(Component comp, int x, int y, int width, int height) {

  ((InplaceLayout)(inplace_panel.getLayout())).setShape(inplace_panel, comp, x, y, width, height);

 }

}


/** Innenflaeche des Views */
class ViewCanvas extends Canvas { 

 /** siehe mouseUp()*/
 static boolean cntrl; 

 /** siehe mouseDrag()*/
 static Neuralgic source_neuro = null, target_neuro = null; 
 // "Ziehen" mit der Maus fkt. z.Zt. nur mit dem Paar Dokument/Clipboard

 /** dasselbe wie parent(), aber schneller (wird sehr haeufig gebraucht)*/
 View parent = null; 

 /** Hintergrund-Puffer*/
 Image m_image = null; 

 /** zugehoeriges Graphik-Kontext*/
 Graphics m_g = null; 

 /** letzte Groesse der Puffer-Flaeche*/
 Dimension m_dimImage = null; 
 
 /** "Zieht" der Anwender gerade mit der Maus?*/
 boolean dragging = false;

 /** linke Maustaste gedrueckt? */
 boolean mouse_down = false;

 /** Wohin scrollt das Canvas gerade? */
 int scrolling_x = 0, scrolling_y = 0;

 /** Run-Objekt fuers autom. Scrollen (muss per Thread realisiert werden, weil
 andernfalls beim Scrollen keine Events mehr geliefert wuerden!) */
 ScrollRunable scroll_runnable = null;

	   //UpdateRunable update_runable = null;


  /** Schrittweite beim autom. Scrollen bei gedrueckter linker Maustaste */
 final static int STEPRATE = 30;
 
 /** Verzoegerung beim autom. Scrollen bei gedrueckter linker Maustaste (ms) */
 final static int DELAYRATE = 100;


 ViewCanvas(View parent) {
 
  this.parent = parent;

 }


 synchronized public void paint(Graphics g)	{

  try {

   parent.target_g = getGraphics();

   just_paint();

  } catch(NullPointerException xcpt) {} // tritt sporadisch beim Programmstart auf, falls das GUI-Messagehandling 
  // dem Thread "hinterherhinkt" und so das Canvas vor dem ersten Zeichnen nicht sichtbar ist

 }


 /** Baum und Markierung ohne Neuberechnung der Neuralgics zeichnen*/
 synchronized  public void just_paint()	{
 
  Dimension ts = parent.draw(); // zeichnet View (d.h. Wurzelknoten)

  if(parent.recent_neuro!=null)
   parent.mark_edge(parent.recent_neuro);

 }


 synchronized public void update(Graphics g) {

  ResizeImage(); // passt Puffer ggf. an Canvas-Groesse an

  if(m_g==null) {

   m_g = g;

   if(m_g==null)
    return;

  }

  // Puffer loeschen:

  Color fg = getForeground(), bg = getBackground();

  m_g.setColor(bg);

  m_g.fillRect(0, 0, m_dimImage.width, m_dimImage.height);

  m_g.setColor(fg);

  parent.target_g = m_g; // in Hintergrund-Puffer zeichnen

  just_paint();

  parent.target_g = g;

  if(m_image!=null) //(immer, es sei denn, der Speicher reichte nicht)
   g.drawImage(m_image, 0, 0, Color.black/*Hintergrund-Farbe*/, null); // tatsaechliche Ausgabe

  parent.reshaped();

 }


 synchronized void update() { 

  update(getGraphics()); 
 /*if(update_runable==null) {

    update_runable = new UpdateRunable();

    update_runable.canvas = this;

    update_runable.startNewThread();
     
     }*/
  

  if(parent!=null) 
   if(parent.inplace_enabled()) {

    if(AOT.BROWSER_Navigator)
     try { Thread.currentThread().sleep(500); } catch(Exception e) {}

    parent.refresh_inplace();

   }

 }


 /** passt (Puffer-)Image-Groesse an Canvas-Groesse an*/
 synchronized boolean ResizeImage() { 

  if(!View.buffered_painting) {  // ohne Hintergrund-Pufferung
   
   m_image = null;

   m_g = null;

   m_dimImage = new Dimension(size().width, size().height);  

   return true;

  }

  if(m_dimImage!=null)
   if(m_dimImage.equals(size()))
    return false;

  m_dimImage = new Dimension(size().width, size().height);  

  m_image = createImage(m_dimImage.width, m_dimImage.height);

  if(m_image!=null)
   m_g = m_image.getGraphics();
  else 
   m_g = null;

  return true;

 }


 public boolean mouseMove(Event evt, int x, int y) {  

  try {

   Neuralgic neuro = parent.get_neuro(x+parent.offset_x, y+parent.offset_y);

   if(neuro!=null) {
 
    String help;

    if(neuro.rect2.inside(x+parent.offset_x, y+parent.offset_y)&&neuro.node.help!=null)
  	 help = neuro.node.help;
    else if(neuro.node.edge.help!=null)
 	 help = neuro.node.edge.help;
	else
	 help = new String();

	parent.document.display_decision_help(help);

	if(help.indexOf("\n")>=0)
     help = help.substring(0, help.indexOf("\n"))+"..."; 

    parent.decision_help_label.setText(help);

   } else {

	parent.document.display_decision_help(null);

    parent.decision_help_label.setText("");

   }

  } catch(NullPointerException xcpt) {} // tritt sporadisch beim Programmstart auf, falls das GUI-Messagehandling 
  // dem Thread "hinterherhinkt" und so das Canvas vor dem ersten Zeichnen nicht sichtbar ist

  return false;

 }


 public boolean mouseDown(Event evt, int x, int y) {  
  /* *** ernstes Problem unter Windows/Java 1.0: falls requestFocus() aufgerufen wird,
  erzeugt Windows "grundlos" eine unbestimmte Anzahl von Pseudo-Mausklick-Ereignissen! */

  // Achtung: auch mouseUp() und mouseDrag()-Ereignisse werden durch einfachen Klick erzeugt!

  mouse_down = true;

  if(evt.clickCount>=3) // verhindert das Schlimmste!
   return true;

  Neuralgic neuro = parent.get_neuro(x+parent.offset_x, y+parent.offset_y);

  if(neuro!=null) {

   parent.mark_edge(neuro);

   if((evt.modifiers&Event.META_MASK)!=0)  // rechte Maustaste an Frame weitergeben
    return false; // wird vom GUI an AOT.Aot.application weitergereicht
   else if(AOT.Aot.application!=null) // "Notabschaltung" des Kontextmenues:
    AOT.Aot.application.mouseDown(evt, x, y);
   
   if(evt.clickCount>=2) { // Doppelklick ==> Aktivierung umschalten/ Ast (de-)komprimieren

    if(neuro.rect2.inside(x+parent.offset_x, y+parent.offset_y)) { 

	 if(neuro.node instanceof Text_Node) { 
		 
	  // Textblatt editieren

	  ((Text_Node)(neuro.node)).edit();

	 } else {
		
	  // Knoten-Kringel angeklickt => Ast ein-/ausklappen
 
      neuro.node.toggle_compression();
	  
	 }
 
  	 parent.renew_neuralgics_next();

     update();

    } else { 

     // Aktivierungszustand umschalten

	 parent.document.toggle_activation();

	 if(parent.activated_only) {

  	  parent.renew_neuralgics_next();

      update();

	 }

    }

   } else if(source_neuro==null&&AOT.Aot.application!=null) {

	source_neuro = neuro; // Dragging starten 
	// (dazu nicht nur mouseDrag verwenden, da dann kein fensteruebergreifendes
	// Ziehen moeglich ist)

   }

  }

  return true;

 }


 public boolean mouseUp(Event evt, int x, int y) {  
 // Folgende Routine zum "Ziehen" eines Astes zu einem anderen Knoten
 // ist auf das Paar "Dokument"<->"Clipboard" abgestimmt
 // und spricht deren beider Views ausnahmsweise direkt an (ueber das gemeinsame Applet).

  mouse_down = false;

  dragging = false;
	 
  //AOT.Aot.application.setCursor(Frame.DEFAULT_CURSOR); // *** nutzt nix unter Windows 95

  if(source_neuro!=null) { // Ast wurde ueber einem Knoten fallengelassen

   if(evt!=null)
    cntrl = ((evt.modifiers&Event.CTRL_MASK)!=0);  
    // kann man an dem Ort, an dem target_neuro!=null wird, evtl. nicht mehr feststellen!

   target_neuro = parent.get_neuro(x+parent.offset_x, y+parent.offset_y);
  
   if(target_neuro!=null) {

    if(target_neuro!=source_neuro) {  // Verschieben auf sich selbst geht nicht (Notausgang fuer den Anwender)

	 if(parent==AOT.Aot.document.view) {

      AOT.Aot.application.document.prepare_undo();

      AOT.Aot.application.undo.enable();

      AOT.Aot.application.undo_setLabel("Undo [Dragging]");

     }

	 if(!cntrl&&source_neuro.node.parent!=null) { // Verschieben statt Kopieren
	  // Aufrufe jetzt ausnahmsweise quer durch die Klassenhierarchie:
		 
	  Node parent = source_neuro.node.parent;

      target_neuro.node.add_child(source_neuro.node, false, null);

      parent.remove_child(source_neuro.node);

      AOT.Aot.clipboard.view.recent_neuro =

      AOT.Aot.document.view.recent_neuro = null;

     } else {

      try {

       target_neuro.node.add_child((Node)(source_neuro.node.clone()), false, null);

      } catch(Exception xcpt) { 
    
       System.out.println(xcpt); 
   
      }

	 }

     source_neuro = target_neuro = null;

     AOT.Aot.document.view.renew_neuralgics_next();

	 AOT.Aot.document.view.canvas.update();

     AOT.Aot.clipboard.view.renew_neuralgics_next();

     AOT.Aot.clipboard.view.canvas.update();

	 AOT.Aot.document.set_modified(true);

	}

   } else if(evt!=null&&AOT.Aot.clipboard_frame!=null) {
	// "Fallenlassen" ueber einem anderen Frame erlauben:
  
    if(parent!=AOT.Aot.clipboard.view)
     AOT.Aot.clipboard.view.canvas.mouseUp(null,
	  x-AOT.Aot.clipboard_frame.bounds().x+AOT.Aot.application.bounds().x, 
 	  y-AOT.Aot.clipboard_frame.bounds().y+AOT.Aot.application.bounds().y);
    else
     AOT.Aot.document.view.canvas.mouseUp(null, 
	  x+AOT.Aot.clipboard_frame.bounds().x-AOT.Aot.application.bounds().x, 
 	  y+AOT.Aot.clipboard_frame.bounds().y-AOT.Aot.application.bounds().y);

   }

   source_neuro = target_neuro = null;

  }

  return true;

 }

 
 public boolean mouseDrag(Event evt, int x, int y) { // auch bei einfachem Klick(!)

  if(AOT.Aot.application!=null) { //(kein Dragging innerhalb des Web-Browsers)
   
   dragging = true;

   //AOT.Aot.application.setCursor(Frame.MOVE_CURSOR); //*** nutzt nix unter Java 1.0

   Neuralgic neuro = parent.get_neuro(x+parent.offset_x, y+parent.offset_y);
   
   if(neuro!=null)
    parent.mark_edge(neuro);
   else if(evt!=null&&AOT.Aot.clipboard_frame!=null) {
	// "Ziehen" vom/zum Clipboard erlauben, d.h. ueber Frame-Grenze hinweg:
  
    if(parent!=AOT.Aot.clipboard.view)
     AOT.Aot.clipboard.view.canvas.mouseDrag(null, // Umrechnung der Clientkoordinaten:
  	  x-AOT.Aot.clipboard_frame.bounds().x+AOT.Aot.application.bounds().x,  
	  y-AOT.Aot.clipboard_frame.bounds().y+AOT.Aot.application.bounds().y);
    else
     AOT.Aot.document.view.canvas.mouseDrag(null, 
 	  x+AOT.Aot.clipboard_frame.bounds().x-AOT.Aot.application.bounds().x, 
	  y+AOT.Aot.clipboard_frame.bounds().y-AOT.Aot.application.bounds().y);

   }

   return true;

  } 

  return false;

 }


 /** autom. Scrolling, wenn der Mauszeiger bei gedr. Maustaste View verlaesst */
 void scroll() {

  parent.offset_x += scrolling_x;

  parent.offset_y += scrolling_y;

  boolean update = true; 

  if(parent.offset_x<=0) {  //(<= !)

   parent.offset_x = 0;

   update = false;
 
  }  else if(parent.offset_x>parent.tree_size.width-bounds().width) {

   parent.offset_x = parent.tree_size.width-bounds().width;

   update = false;

  }

  if(parent.offset_y<=0) {  //(<= !)

   parent.offset_y = 0;

   update = false;

  } else if(parent.offset_y>parent.tree_size.height-bounds().height) {

   parent.offset_y = parent.tree_size.height-bounds().height;

   update = false;

  }

  if(update)
   update();

 }


 /** Mauszeiger betritt ViewCanvas: autom. Scrolling stoppen */
 public boolean mouseEnter(Event evt, int  x, int  y) { 
 
  if(scroll_runnable!=null&&(scrolling_x!=0||scrolling_y!=0)) {
	  	  
   scrolling_x = scrolling_y = 0;

   scroll_runnable.thread.stop();

  }

  return false;

 }


 /** Mauszeiger verlaesst ViewCanvas: u.a. autom. Scrolling starten */
 public boolean mouseExit(Event evt, int  x, int  y) { 

  // *** Hack, um das fehlende Ereignis "TextLeafArea.mouseMove" zu simulieren:
  if(parent.allow_inplace) {

   // so geht's leider nicht: ...getParent().locate(x, y)... (denn das ViewCanvas liegt "davor")

   Component inplace_atom = parent.locate_atom(x, y);

   if(inplace_atom instanceof TextLeafArea) {

    ((TextLeafArea)inplace_atom).mouseMove(null, 0, 0); // zwecks Anzeige von Decision-Help-Text

	return false;

   }

  } 
  
  if(mouse_down&&parent.scrollable) {

   scrolling_x = scrolling_y = 0;

   if(y<=0)
	scrolling_y = -STEPRATE;
   else if(x<=0)
	scrolling_x = -STEPRATE;
   else if(y>bounds().height)
	scrolling_y = STEPRATE;
   else if(x>bounds().width)
	scrolling_x = STEPRATE;

   if(true||  scroll_runnable==null) {

    scroll_runnable = new ScrollRunable();

    scroll_runnable.canvas = this;

    scroll_runnable.startNewThread();

   } else
	;//scroll_runnable.thread.resume(); +++

  }

  return false;

 }


}


/** Beschreibung eines "empfindlichen Bereichs" im Panel und des zugehoerigen Kantentextes*/
class Neuralgic {

 /** umgebende Rechtecke (eins fuer Kantentext, eins fuer folgenden Knoten)*/
 Rectangle rect1, rect2;  

 /** Gesamtgroesse (speichern, weil diese seit v1.0.0 von rect1 abweichen kann!)*/
 int height; 

 /** gemeint ist Knoten selbst oder zufuehrende Kante*/
 Node node;  


 Neuralgic(Rectangle rect1, Rectangle rect2, Node node, int height) {

  this.rect1 = rect1;
  
  this.rect2 = rect2;

  this.height = height;
  
  this.node = node;

 }

}


/** siehe Statusbereich von ViewCanvas */
class ScrollRunable implements Runnable {

 
 Thread thread;

 ViewCanvas canvas;


 public void run() {

  for(;;) {

   canvas.scroll();
   
   try { thread.sleep(canvas.DELAYRATE); } catch(Exception e) {}

  }

 }


 void startNewThread() {

  thread = new Thread(this);

  thread.start();

 }

}


/** siehe update() von ViewCanvas */
/*class UpdateRunable implements Runnable {

 
 Thread thread;

 ViewCanvas canvas;


 public void run() {

  canvas.update(canvas.getGraphics()); 

  if(canvas.parent!=null)
   if(canvas.parent.inplace_enabled())
    canvas.parent.refresh_inplace();


 }


 void startNewThread() {

  thread = new Thread(this);

  thread.start();

 }

}
*/


/** Groesse und umgebendes Rechteck eines Astes/Knotens*/
class Area {  

 /** Gesamtgroesse*/
 Dimension dimension;  

 /** umgebendes Rechteck der neuralgischen Flaeche*/
 Rectangle node_rect;  


 Area(Rectangle node_rect, Dimension dimension) {

  this.dimension = dimension;

  this.node_rect = node_rect;

 }
 
}

