/* Das Plotfenster, in dem der Graph dargestellt wird */

import java.awt.*;
import java.awt.event.*;

/** PlotFrame - Klasse stellt Ausdruck graphisch dar */
public class PlotFrame extends Frame {
	
 // Variablendeklaration   
 double sxmin,sxmax,symin,symax; // Startwerte der Grenzen
 double rangeWidth, rangeHeight; // gewählter Range
 PlotCanvas pCanvas; // die Zeichenfläche
 PlotFrame dummyThis; // unangenehme Konstrukte...
 Plotter parent; // Schön verzahnt... 
 MParse parser; // Eine MParse-Referenz

 // Exportiere Variablen 
 /** minimal darzustellender x-Wert */
 public double xmin;
 /** maximal darzustellender x-Wert */
 public double xmax;
 /** minimal darzustellender y-Wert */
 public double ymin;
 /** maximal darzustellender y-Wert */
 public double ymax;
 /** Referenz auf die nächste Instanz (für Fenster-Liste) */
 public PlotFrame next; 
 /** Der geparste Funktionsausdruck */
 public String AusdruckString; 

 /** Der Constructor 
   * @param Plotter Referenz auf Mutterinstanz vom Typ Plotter
   * @param String Zu parsender Funktionsausdruck
   * @param xmin minimal darzustellender x-Wert 
   * @param xmax maximal darzustellender x-Wert 
   * @param ymin minimal darzustellender y-Wert 
   * @param ymax maximal darzustellender y-Wert 
   */
 PlotFrame (Plotter parent, String AusdruckString, double xmin,double xmax,double ymin,double ymax){

   super ("Funktion: "+AusdruckString); // Fenstertitel = Funktionsausdruck

  // Das Parsen des Strings, Anlegen eines neuen MParse-Objekts
   parser = new MParse();
   try {
      parser.parse( AusdruckString ); // Parsen
      double FuncValue = parser.evaluate( xmin ); // Einen Wert testen
   } catch ( MParseException MPEX ) {
      this.AusdruckString = MPEX.msg; // Das darf nicht sein, daher wird gekillt
      return;
   }

  // Übernehmen der Variablen
   this.AusdruckString = AusdruckString;   
   this.parent = parent; // Für Call-Back
   this.xmax = xmax; // Werte abspeichern
   this.xmin = xmin;
   this.ymin = ymin;
   this.ymax = ymax;

  // Initialisierung
   InitPlotFrame(); 
 }

 /** Die eigentliche Plot-Klasse -> Erweiterung der Canvas-Klasse */
 class PlotCanvas extends Canvas{

  // Globale Variabeln der PlotCanvas-Klasse
  int MouseX, MouseY;
  boolean ShiftPressed;
  Dimension pSize;

  /** PlotCanvas - Constructor mit MouseListener für Scrollen/Zoomen */
  PlotCanvas () { 

    // Tastatur-Handling
     KeyListener unserKeyListener = new KeyAdapter()
     {
        public void keyPressed(KeyEvent e) { // Taste gedrückt
           if (e.getKeyCode()==KeyEvent.VK_SHIFT) { ShiftPressed = true; }
        }

        public void keyReleased(KeyEvent e) { // Taste losgelassen
           if (e.getKeyCode()==KeyEvent.VK_SHIFT) { ShiftPressed = false; }
        }
     };

    // Mouse-Handling
     MouseListener unserMouseListener = new MouseAdapter()
     {
        public void mousePressed(MouseEvent e) { // Mouse gedrückt
           MouseX = e.getX();
           MouseY = e.getY();
        }
        public void mouseClicked(MouseEvent e) { // Zurücksetzen
           if (e.getClickCount()==2) { // Bei Doppelklick
              xmin = sxmin;
              xmax = sxmax;
              ymin = symin;
              ymax = symax;
              rangeWidth = Math.abs(xmax - xmin); // Breite und...
              rangeHeight = Math.abs(ymax - ymin); // ... Höhe der Ausgabe ausrechnen
              repaint();
           }
        }
     };

    // Mousebewegungshandling
     MouseMotionListener unserMotionListener = new MouseMotionAdapter()
     {
        public void mouseDragged(MouseEvent e) {

          // Doppelklickhandling
           if (e.getClickCount()==2) { // Bei Doppelklick
              xmin = sxmin;
              xmax = sxmax;
              ymin = symin;
              ymax = symax;
              rangeWidth = Math.abs(xmax - xmin); // Breite und...
              rangeHeight = Math.abs(ymax - ymin); // ... Höhe der Ausgabe ausrechnen
              repaint();
              return;
           }       

          // Abmessungen unserer Zeichenfläche holen
           pSize = getSize (); // .height, .width sind die Maße...

          // Neue Grenzen berechnen 
           if (!ShiftPressed) { // Wenn Shift nicht gedrückt, dann verschieben
              xmin += (MouseX - e.getX())*rangeWidth / pSize.width;
              xmax += (MouseX - e.getX())*rangeWidth / pSize.width;
              ymin -= (MouseY - e.getY())*rangeHeight / pSize.height;
              ymax -= (MouseY - e.getY())*rangeHeight / pSize.height;
   	      rangeWidth = Math.abs(xmax - xmin); // Breite und...
              rangeHeight = Math.abs(ymax - ymin); // ... Höhe der Ausgabe ausrechnen
           } else { // Wenn Shift gedrückt, dann spezial-zoom
              if (MouseX - e.getX() > 0) { 
                 xmin -= (MouseX - e.getX())*rangeWidth / pSize.width;
                 xmax += (MouseX - e.getX())*rangeWidth / pSize.width;
              } else {
                 xmin -= (MouseX - e.getX())*rangeWidth / pSize.width;
                 xmax += (MouseX - e.getX())*rangeWidth / pSize.width;
              }
              if (MouseY - e.getY() > 0) { 
                 ymin -= (MouseY - e.getY())*rangeHeight / pSize.height;
                 ymax += (MouseY - e.getY())*rangeHeight / pSize.height;
              } else {
                 ymin -= (MouseY - e.getY())*rangeHeight / pSize.height;
                 ymax += (MouseY - e.getY())*rangeHeight / pSize.height;
              }
              rangeWidth = Math.abs(xmax - xmin); // Breite und...
              rangeHeight = Math.abs(ymax - ymin); // ... Höhe der Ausgabe ausrechnen
           }

           MouseX = e.getX(); // Neue "alte" Werte Zwischenspeichern
           MouseY = e.getY();    
           repaint();
        }
     };

     addMouseListener(unserMouseListener); // Mouse-Event-Handling aktiv.
     addMouseMotionListener(unserMotionListener); 
     addKeyListener(unserKeyListener); 
   }

   public void paint(Graphics g){

     // Variablen deklarieren
      double FuncValue, xValue; // x- und y-Wert der Funktion
      int screenX, screenY, saveScreenY; // Pixelpositionen
      long i, mulFaktor, addFaktor; // Für Achsenbeschriftung
      boolean lastWasRed=false; // Flag: gesetzt wenn INF oder NaN

     // Abmessungen unserer Zeichenfläche holen
      pSize = getSize (); // .height, .width sind die Maße...
      if ((pSize.height<80) || (pSize.width<80)) { // wenn Fenster zu klein, dann -
         g.setColor(Color.red);
         g.drawLine (0, 0, pSize.width -1, pSize.height -1);
         g.drawLine (0, pSize.height -1, pSize.width -1, 0);
         return;
      }

     // Für Beschriftungen...
      g.setFont (new Font("SansSerif", Font.PLAIN, 12));      

     // Achsen zeichnen, wenn diese im Range liegen
      if ((xmin <= 0) && (xmax >=0)) { // Y-Achse
         g.setColor (Color.black);
         screenX = (int)( (0-xmin) * pSize.width / rangeWidth);
         g.drawLine ( screenX, 0, screenX, pSize.height -1 );
         g.drawString ("y-Achse", screenX-47, 13);

        // Skala auf Y-Achse einzeichnen
         mulFaktor = Math.round (rangeHeight/10); // MulFaktor berechnen 
         if (mulFaktor != 0) {
            if (ymin > 0) {
               addFaktor = Math.round(ymin)-1;
            } else if (ymax < 0) {
               addFaktor = -Math.round(ymax)-1;
            } else { addFaktor = 0; } // Wenn 0 in der Mitte liegt 

            for (i = 1; i < 12; i++) {
               screenY = (int)( pSize.height - (i*mulFaktor+addFaktor-ymin) * pSize.height / rangeHeight);
               if ((screenY > 0) && (screenY < pSize.height-1)) {
                  g.drawLine ( screenX-2, screenY, screenX+2, screenY); // horizontales Strichlein rechts von 0
                  g.drawString (new Long(i*mulFaktor+addFaktor).toString(), screenX+5, screenY+5);
               }
               screenY = (int)( pSize.height - (0-ymin-i*mulFaktor-addFaktor) * pSize.height / rangeHeight);
               if ((screenY > 0) && (screenY < pSize.height-1)) {
                  g.drawLine ( screenX-2, screenY, screenX+2, screenY); // horizontales Strichlein links von 0
                  g.drawString ("-"+new Long(i*mulFaktor+addFaktor).toString(), screenX+5, screenY+5);
               }
            }
         }
      }
      if ((ymin <= 0) && (ymax >=0)) { // X-Achse
         g.setColor (Color.black);
         screenY = pSize.height - (int)( (0-ymin) * pSize.height / rangeHeight);
         g.drawLine ( 0, screenY, pSize.width-1, screenY); 
         g.drawString ("x-Achse",pSize.width-47, screenY-4); 

        // Skala auf X-Achse einzeichnen
         mulFaktor = Math.round (rangeWidth/10); // MulFaktor berechnen
         if (mulFaktor != 0) {
            if (xmin > 0) {
               addFaktor = Math.round(xmin)-1;
            } else if (xmax < 0) {
               addFaktor = -Math.round(xmax)-1;
            } else { addFaktor = 0; } // Wenn 0 in der Mitte liegt 

            for (i = 1; i < 12; i++) {
               screenX = (int)( (i*mulFaktor+addFaktor-xmin) * pSize.width / rangeWidth);
               if ((screenX > 0) && (screenX < pSize.width-1)) {
                  g.drawLine ( screenX, screenY-2, screenX, screenY+2); // vertikales Strichlein rechts von 0
                  g.drawString (new Long(i*mulFaktor+addFaktor).toString(), screenX-2, screenY+14); 
               }
               screenX = (int)( (0-xmin-i*mulFaktor-addFaktor) * pSize.width / rangeWidth);
               if ((screenX > 0) && (screenX < pSize.width-1)) {
                  g.drawLine ( screenX, screenY-2, screenX, screenY+2); // vertikales Strichlein links von 0
                  g.drawString ("-"+new Long(i*mulFaktor+addFaktor).toString(), screenX-6, screenY+14);
               }
            }
         }
      }

     // Ersten Wert berechnen   
     // FuncValue = 1/(xmin-1); // später mit Func.evaluate(2*xmin) ersetzen
      FuncValue = parser.evaluate( xmin );
      saveScreenY = pSize.height - (int)((FuncValue - ymin) * pSize.height / rangeHeight);

     // Loop über Range
      g.setColor (Color.blue);

      for ( screenX = 1; screenX < pSize.width; screenX++ ) { 
      
        // Skalieren 
         xValue = (screenX * rangeWidth / pSize.width) + xmin;
      
        // Funktionswert des Punktes berechnen
        // später: FuncValue = Func.evaluate( xValue ); // -> Fabian
        // FuncValue = 1/(xValue-1);
         FuncValue = parser.result.evaluate( xValue );
      
        // Extremcheck und plotten
         if ((FuncValue==Double.POSITIVE_INFINITY) || (FuncValue==Double.NEGATIVE_INFINITY) || (FuncValue==Double.NaN)) {
            g.setColor (Color.red);
            g.drawLine (screenX, 0, screenX, pSize.height -1); 
            g.setColor (Color.blue);
           // lastWasRed = true; // Flag setzen
         } else { // in normalem Bereich, dann plotten
            screenY = pSize.height - (int)((FuncValue - ymin) * pSize.height / rangeHeight);
            // Clippin...
            if (((saveScreenY >= 0) && (saveScreenY < pSize.height)) || ((screenY >= 0) && (screenY < pSize.height)))
              if (lastWasRed) { g.drawLine ( screenX, screenY, screenX, screenY ); lastWasRed = false; } 
              else { g.drawLine ( screenX-1, saveScreenY, screenX, screenY ); }
            saveScreenY = screenY;
         }
      }
   }
 }

 private void InitPlotFrame(){

   // Ein paar allgemeinere Inits und Checks
    if (xmax == Double.NaN) { xmax = 5; }
    if (xmin == Double.NaN) { xmin = -5; }
    if (ymax == Double.NaN) { ymax = 5; }
    if (ymin == Double.NaN) { ymin = -5; }

    sxmax = xmax; // Startwerte der Grenzen speichern
    sxmin = xmin;
    symin = ymin;
    symax = ymax;    

    rangeWidth = Math.abs(xmax - xmin); // Breite und...
    rangeHeight = Math.abs(ymax - ymin); // ... Höhe der Ausgabe ausrechnen

    dummyThis = this;
  
   // Ein paar AWT-Inits
    WindowListener ExitListener = new WindowAdapter()
    {
      // Event-Methode die beim Schließen aufgerufen wird
       public void windowClosing (WindowEvent ThisEvent) {
          setVisible (false); // Fenster verschwinden lassen
          parent.KickMe(dummyThis); // Aus Fensterliste kicken...
          // dispose(); // und killen...
       }
    };

    addWindowListener(ExitListener); // Event-Handling aktiv.

    pCanvas = new PlotCanvas();
    add (pCanvas);  
    setSize(400, 400); // Größe wie in html-Datei
    show(); // und anzeigen (visible=true)...
 }
}

