//------------------------------------------------------------------------------
// Mandelbrot.java 1.05 (Applet)
// Copyright (C) 2001, 2002 by Alexander Adam
// Tested with Java 1.2.2
// No Warranty of any kind.
//------------------------------------------------------------------------------
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/** 
  * Java Applet Mandelbrot<br>
  * <b>No Warranty of any kind.<b>
  * @author Alexander Adam<br>Copyright (C) 2001
  * @version 1.05
  */
public class Mandelbrot
extends java.applet.Applet
implements Runnable {

  private Thread          appletThread; // applet Thread
  private MandelbrotPanel mandelbrotPanel;
  private Button          resetButton;
  private Button          backButton;
  private Button          redrawButton;
  private Button          newButton;

  // Language ( Locale (Country) )
  private String lang;
  private String lang_de;

  //
  // String consatnts
  //
  private final String sColors_en     = "Colordepth";
  private final String sColors_de     = "Farbtiefe";
  private final String sRange_en      = "Range";
  private final String sRange_de      = "Bereich";
  private final String sCalculate_en  = "calculating";
  private final String sCalculate_de  = "rechnet";
  private final String sReset_en      = "Reset";
  private final String sReset_de      = "Grundbild";
  private final String sBack_en       = "Back";
  private final String sBack_de       = "Zurueck";
  private final String sRedraw_en     = "Draw again";
  private final String sRedraw_de     = "Aktualisieren";
  private final String sNewWin_en     = "New Window";
  private final String sNewWin_de     = "Neues Fenster";
  private final String sProp_en       = "proportional";
  private final String sProp_de       = "proportional";
  private final String sMore_en       = "More Options";
  private final String sMore_de       = "Weitere Einstellungen";
  private final String sHueOffset_en  = "Hue Offset";
  private final String sHueOffset_de  = "Farbverschiebung";
  private final String sHueRange_en   = "Hue Range";
  private final String sHueRange_de   = "Farbspektrum";
  private final String sSuperHue_en   = "Super Hue";
  private final String sSuperHue_de   = "Super Farbe";
  private final String sMaxWinX_en    = "New Window X";
  private final String sMaxWinX_de    = "Neues Fenster X";
  private final String sMaxWinY_en    = "New Window Y";
  private final String sMaxWinY_de    = "Neues Fenster Y";

  //
  // Default Mandelbrot set parameters
  //
  private static final double  iC0x     = -0.5f;  // c0: real part (x)
  private static final double  iC0y     = 0.0f;   // c0: imaginary part (y)
  private static final double  iRangeX  = 3.2f;   // calculation x-Range
  private static final double  iRangeY  = 2.4f;   // calculation y-Range
  private static final int     iColors  = 96;     // max calculation depth
  private static final int     iMaxCol  = 4096;   // for scrollbar
  private static final int     iHueOffset = 0;    // hue offset
  private static final int     iHueRange = 1024;  // for scrollbar
  private static final int     iMaxWinX  = 1600;  // for newWindow
  private static final int     iMaxWinY  = 1200;  // for newWindow
  private static final boolean iProp   = true;    // keep proportions

  //
  // Mandelbrot set parameters
  //
  private Label             colorsValueLabel;
  private Button            moreOptionsButton;
  private MoreOptionsDialog moreOptionsDialog;

  private int colors = iColors;                   // max calculation depth
  private int hueOffset = iHueOffset;             // hue offset
  private int hueRange = iHueRange;               // hue range
  private int superHue = iHueOffset;              // hue super
  private boolean prop = iProp;                   // keep proportions
  private int newSizeX = (int)(iRangeX * 100.0f);
  private int newSizeY = (int)(iRangeY * 100.0f);


  //
  // Calculation Stack
  //
  private Stack mandelbrotSetStack = new Stack();

  /**
   * applet init
   */
  public void init() {
    // Get Locale
    lang = Locale.getDefault().getLanguage();
    lang_de = Locale.GERMAN.getLanguage();

    // create mandelbrotPanel instance
    mandelbrotPanel = new MandelbrotPanel( this, iC0x, iC0y, iRangeX, iRangeY, colors, prop );

    // set some additional parameters
    float hue;
    hue = (float)hueOffset / (float)iHueRange;
    mandelbrotPanel.setHueOffset(hue);
    mandelbrotPanel.setSuperHue(hue);
    hue = (float)hueRange / (float)iHueRange;
    mandelbrotPanel.setHueRange(hue);

    // create applet control panels ...
    Panel optionsPanel = new Panel();
    Panel buttonsPanel = new Panel();

    resetButton = new Button();
    backButton = new Button();
    redrawButton = new Button();
    newButton = new Button();
    Checkbox propCheckbox = new Checkbox();
    moreOptionsButton = new Button();

    colorsValueLabel = new Label();

    propCheckbox.setState(prop);
    propCheckbox.addItemListener( new PropListener() );

    if ( lang.equals(lang_de) ) {    
      resetButton.setLabel(sReset_de);
      backButton.setLabel(sBack_de);
      redrawButton.setLabel(sRedraw_de);
      newButton.setLabel(sNewWin_de);
      propCheckbox.setLabel(sProp_de);
      colorsValueLabel.setText( sColors_de+" "+colors+"    " );
      moreOptionsButton.setLabel(sMore_de);
    } else {
      resetButton.setLabel(sReset_en);
      backButton.setLabel(sBack_en);
      redrawButton.setLabel(sRedraw_en);
      newButton.setLabel(sNewWin_en);
      propCheckbox.setLabel(sProp_en);
      colorsValueLabel.setText( sColors_en+" "+colors+"    " );
      moreOptionsButton.setLabel(sMore_en);
    }

    resetButton.setEnabled( false );
    backButton.setEnabled( false );
    redrawButton.setEnabled( false );
    newButton.setEnabled( false );

    moreOptionsButton.addActionListener( new MoreOptionsButtonListener() );

    resetButton.addActionListener( new ResetListener() );
    backButton.addActionListener( new BackListener() );
    redrawButton.addActionListener( new RedrawListener() );
    newButton.addActionListener( new NewWinListener() );

    buttonsPanel.add( resetButton );
    buttonsPanel.add( backButton );
    buttonsPanel.add( redrawButton );
    buttonsPanel.add( newButton );

    optionsPanel.add( propCheckbox );
    optionsPanel.add( colorsValueLabel );
    optionsPanel.add( moreOptionsButton );

    setLayout( new BorderLayout() );
    add(optionsPanel,BorderLayout.NORTH);
    add(mandelbrotPanel,BorderLayout.CENTER);
    add(buttonsPanel,BorderLayout.SOUTH);
  }

  /**
   * start applet thread
   */
  public void start() {
    if ( appletThread == null ) {
      appletThread = new Thread(this);
      appletThread.start();
    }
  }

  /**
   * stop applet thread
   */
  public void stop() {
    appletThread = null;
  }

  /**
   * run applet thread
   */
  public void run() {
    Thread thisThread = Thread.currentThread();

    resetButton.setEnabled(false);
    backButton.setEnabled(false);
    redrawButton.setEnabled(false);
    newButton.setEnabled(false);

    if ( appletThread == thisThread ) {
      /**
       * calculate mandelbrot set
       */
      if ( lang.equals(lang_de) ) {    
        getAppletContext().showStatus(sCalculate_de+" ...");
      } else {
        getAppletContext().showStatus(sCalculate_en+" ...");
      }

      setCursor( Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      mandelbrotPanel.calculate();
      setCursor( Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

      // add parameters to stack
      MandelbrotSetParameters mandelbrotSetParameters = 
        new MandelbrotSetParameters( mandelbrotPanel.getX0(), mandelbrotPanel.getY0(),
                                     mandelbrotPanel.getRangeX(), mandelbrotPanel.getRangeY() );

      if (mandelbrotSetStack.size() > 0) {
        MandelbrotSetParameters oldMandelbrotSetParameters = 
          (MandelbrotSetParameters)mandelbrotSetStack.peek();
        if ( mandelbrotSetParameters.getC0X() != oldMandelbrotSetParameters.getC0X() ||
             mandelbrotSetParameters.getC0Y() != oldMandelbrotSetParameters.getC0Y() ||
             mandelbrotSetParameters.getRangeX() != oldMandelbrotSetParameters.getRangeX() ||
             mandelbrotSetParameters.getRangeY() != oldMandelbrotSetParameters.getRangeY() ) {
          mandelbrotSetStack.push((Object)mandelbrotSetParameters);
        }
      } else {
        mandelbrotSetStack.push((Object)mandelbrotSetParameters);
      }

      String info = "c=("+mandelbrotPanel.getX0()+"/"+mandelbrotPanel.getY0()+") ";
      if ( lang.equals(lang_de) ) {    
        info += sRange_de;
      } else {
        info += sRange_en;
      }
      info += "=("+mandelbrotPanel.getRangeX()+"x"+mandelbrotPanel.getRangeY()+")";
      getAppletContext().showStatus(info);

      mandelbrotPanel.repaint();
    }

    resetButton.setEnabled(true);
    redrawButton.setEnabled(true);
    newButton.setEnabled(true);

    if (mandelbrotSetStack.size() > 1) {
      backButton.setEnabled(true);
    }
  }

  /**
    * ActionListener for moreOptionsButton
    */
  private class MoreOptionsButtonListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {
      if (moreOptionsDialog == null) {
        Component frameComponent = (Component)evt.getSource();
        while (! (frameComponent instanceof Frame)) {
          frameComponent = frameComponent.getParent();
        }
        String sDialogTitle;
        if ( lang.equals(lang_de) ) {    
          sDialogTitle = sMore_de;
        } else {
          sDialogTitle = sMore_en;
        }
        moreOptionsDialog = new MoreOptionsDialog((Frame)frameComponent,sDialogTitle,false);
      }

      if (!moreOptionsDialog.isVisible()) {
        moreOptionsDialog.pack();
        moreOptionsDialog.show();
        moreOptionsButton.setEnabled(false);
      }
    }
  }

  /**
    * ActionListener for resetButton
    */
  private class ResetListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {
      // prepare mandelbrotPanel
      mandelbrotPanel.setX0( iC0x );
      mandelbrotPanel.setY0( iC0y );
      mandelbrotPanel.setRangeX( iRangeX );
      mandelbrotPanel.setRangeY( iRangeY );

      // clear stack
      mandelbrotSetStack.removeAllElements();

      stop();
      start();
    }
  }

  /**
    * ActionListener for backButton
    */
  private class BackListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {
      if (mandelbrotSetStack.size() > 1) {
        // pop current
        mandelbrotSetStack.pop();
        // get last ( go back )
        MandelbrotSetParameters mandelbrotSetParameters = 
          (MandelbrotSetParameters)mandelbrotSetStack.pop();

        // prepare mandelbrotPanel
        mandelbrotPanel.setX0( mandelbrotSetParameters.getC0X() );
        mandelbrotPanel.setY0( mandelbrotSetParameters.getC0Y() );
        mandelbrotPanel.setRangeX( mandelbrotSetParameters.getRangeX() );
        mandelbrotPanel.setRangeY( mandelbrotSetParameters.getRangeY() );

        stop();
        start();
      }
    }
  }

  /**
    * ActionListener for redrawButton
    */
  private class RedrawListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {
      stop();
      start();
    }
  }

  /**
    * ActionListener for newButton
    */
  private class NewWinListener implements ActionListener {

    public void actionPerformed(ActionEvent evt) {

      Component frameComponent = (Component)evt.getSource();
      while (! (frameComponent instanceof Frame)) {
        frameComponent = frameComponent.getParent();
      }

      double x0 = mandelbrotPanel.getX0();
      double y0 = mandelbrotPanel.getY0();
      double rx = mandelbrotPanel.getRangeX();
      double ry = mandelbrotPanel.getRangeY();

      double precX = rx / (double)newSizeX;  // calculation steps ( precision_x )
      double precY = ry / (double)newSizeY;  // calculation steps ( precision_y )

      if (prop) {
        if (precX > precY) {
          ry = (double)newSizeY * precX;
        } else if (precX < precY) {
          rx = (double)newSizeX * precY;
        }
      }

      MandelbrotPanel newMandelbrotPanelWin = 
        new MandelbrotPanel( null, x0, y0, rx, ry, colors, prop );

      // set some additional parameters
      float hue;
      hue = (float)hueOffset / (float)iHueRange;
      newMandelbrotPanelWin.setHueOffset(hue);
      hue = (float)superHue / (float)iHueRange;
      newMandelbrotPanelWin.setSuperHue(hue);
      hue = (float)hueRange / (float)iHueRange;
      newMandelbrotPanelWin.setHueRange(hue);

      NewWindowDialog newWin = 
        new NewWindowDialog((Frame)frameComponent,"Mandelbrot",false,newMandelbrotPanelWin);

      newWin.setSize(newSizeX+16,newSizeY+64);
      if (!newWin.isVisible()) {
        newWin.show();
      }

      setCursor( Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
      newMandelbrotPanelWin.calculate(newSizeX,newSizeY);
      setCursor( Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
  }

  /**
    * ItemListener for Prop Checkbox
    */
  private class PropListener implements ItemListener {

    public void itemStateChanged(ItemEvent evt) {
      Checkbox source = (Checkbox)evt.getSource();
      prop = source.getState();
      mandelbrotPanel.setProp(prop);
    }
  }

  private class MandelbrotSetParameters {

    private double  c0x;
    private double  c0y;
    private double  rangeX;
    private double  rangeY;

    MandelbrotSetParameters( double x, double y, double rx, double ry) {
      c0x = x;
      c0y = y;
      rangeX = rx;
      rangeY = ry;
    }

    public double getC0X() {
      return c0x;
    }

    public double getC0Y() {
      return c0y;
    }

    public double getRangeX() {
      return rangeX;
    }

    public double getRangeY() {
      return rangeY;
    }
  }

  private class MoreOptionsDialog extends Dialog {
    Label     dialogColorsValueLabel;
    Label     hueOffsetValueLabel;
    Label     hueRangeValueLabel;
    Label     superHueValueLabel;
    Label     sizeXValueLabel;
    Label     sizeYValueLabel;

    MoreOptionsDialog( Frame frame, String title, boolean modal ) {
      super( frame, title, modal );

      // set Dialog Layout
      setLayout( new GridLayout(3,2) );
      addWindowListener(new DialogWindowAdapter());

      //
      // options Panels
      //

      // colors Panel
      Panel     colorsPanel = new Panel();
      Label     colorsLabel = new Label();
      Scrollbar colorsScroller = new Scrollbar( Scrollbar.HORIZONTAL, 1, 10, 1, iMaxCol + 10 );

      if ( lang.equals(lang_de) ) {    
        colorsLabel.setText(sColors_de);
      } else {
        colorsLabel.setText(sColors_en);
      }

      colorsScroller.setValue(colors);
      colorsScroller.addAdjustmentListener(new ColorsListener() );

      dialogColorsValueLabel = new Label();
      dialogColorsValueLabel.setText( " "+colors+"    " );

      colorsPanel.add( colorsLabel );
      colorsPanel.add( colorsScroller );
      colorsPanel.add( dialogColorsValueLabel );

      // hue offset Panel
      Panel     hueOffsetPanel = new Panel();
      Label     hueOffsetLabel = new Label();
      Scrollbar hueOffsetScroller = new Scrollbar( Scrollbar.HORIZONTAL, 0, 10, 0, iHueRange + 10 );

      if ( lang.equals(lang_de) ) {    
        hueOffsetLabel.setText(sHueOffset_de);
      } else {
        hueOffsetLabel.setText(sHueOffset_en);
      }

      hueOffsetScroller.setValue(hueOffset);
      hueOffsetScroller.addAdjustmentListener(new HueOffsetListener() );

      hueOffsetValueLabel = new Label();
      hueOffsetValueLabel.setText( " "+hueOffset+"   " );

      float fHueOffset = (float)hueOffset / (float)iHueRange;
      hueOffsetScroller.setBackground(Color.getHSBColor(fHueOffset, (float)1.0,(float)1.0));

      hueOffsetPanel.add( hueOffsetLabel );
      hueOffsetPanel.add( hueOffsetScroller );
      hueOffsetPanel.add( hueOffsetValueLabel );

      // hue range Panel
      Panel     hueRangePanel = new Panel();
      Label     hueRangeLabel = new Label();
      Scrollbar hueRangeScroller = new Scrollbar( Scrollbar.HORIZONTAL, 0, 10, 0, iHueRange + 10 );

      if ( lang.equals(lang_de) ) {    
        hueRangeLabel.setText(sHueRange_de);
      } else {
        hueRangeLabel.setText(sHueRange_en);
      }

      hueRangeScroller.setValue(hueRange);
      hueRangeScroller.addAdjustmentListener(new HueRangeListener() );

      hueRangeValueLabel = new Label();
      hueRangeValueLabel.setText( " "+hueRange+"   " );

      hueRangePanel.add( hueRangeLabel );
      hueRangePanel.add( hueRangeScroller );
      hueRangePanel.add( hueRangeValueLabel );

      // super hue Panel
      Panel     superHuePanel = new Panel();
      Label     superHueLabel = new Label();
      Scrollbar superHueScroller = new Scrollbar( Scrollbar.HORIZONTAL, 0, 10, 0, iHueRange + 10 );

      if ( lang.equals(lang_de) ) {    
        superHueLabel.setText(sSuperHue_de);
      } else {
        superHueLabel.setText(sSuperHue_en);
      }

      superHueScroller.setValue(superHue);
      superHueScroller.addAdjustmentListener(new SuperHueListener() );

      superHueValueLabel = new Label();
      superHueValueLabel.setText( " "+superHue+"   " );

      float fSuperHue = (float)superHue / (float)iHueRange;
      float br = (float)1.0;
      if (superHue == 0) {
        br = (float)0.0;
      }
      superHueScroller.setBackground(Color.getHSBColor(fSuperHue, (float)1.0, br));

      superHuePanel.add( superHueLabel );
      superHuePanel.add( superHueScroller );
      superHuePanel.add( superHueValueLabel );

      // size X Panel
      Panel     sizeXPanel = new Panel();
      Label     sizeXLabel = new Label();
      Scrollbar sizeXScroller = new Scrollbar( Scrollbar.HORIZONTAL, 0, 10, 32, iMaxWinX + 10 );

      if ( lang.equals(lang_de) ) {
        sizeXLabel.setText(sMaxWinX_de);
      } else {
        sizeXLabel.setText(sMaxWinX_en);
      }

      sizeXScroller.setValue(newSizeX);
      sizeXScroller.addAdjustmentListener(new SizeXListener() );

      sizeXValueLabel = new Label();
      sizeXValueLabel.setText( " "+newSizeX+"   " );

      sizeXPanel.add( sizeXLabel );
      sizeXPanel.add( sizeXScroller );
      sizeXPanel.add( sizeXValueLabel );

      // size Y Panel
      Panel     sizeYPanel = new Panel();
      Label     sizeYLabel = new Label();
      Scrollbar sizeYScroller = new Scrollbar( Scrollbar.HORIZONTAL, 0, 10, 24, iMaxWinY + 10 );

      if ( lang.equals(lang_de) ) {
        sizeYLabel.setText(sMaxWinY_de);
      } else {
        sizeYLabel.setText(sMaxWinY_en);
      }

      sizeYScroller.setValue(newSizeY);
      sizeYScroller.addAdjustmentListener(new SizeYListener() );

      sizeYValueLabel = new Label();
      sizeYValueLabel.setText( " "+newSizeY+"   " );

      sizeYPanel.add( sizeYLabel );
      sizeYPanel.add( sizeYScroller );
      sizeYPanel.add( sizeYValueLabel );

      // add Panels to Dialog
      add( colorsPanel );
      add( hueOffsetPanel );
      add( hueRangePanel );
      add( superHuePanel );
      add( sizeXPanel );
      add( sizeYPanel );
    }

    /**
     * AdjustmentListener for colors
     */
    private class ColorsListener implements AdjustmentListener {

      public void adjustmentValueChanged(AdjustmentEvent evt) {
        Scrollbar source = (Scrollbar)evt.getSource();
        colors = source.getValue();
        if ( lang.equals(lang_de) ) {    
          colorsValueLabel.setText( sColors_de+" "+colors+"    " );
        } else {
          colorsValueLabel.setText( sColors_en+" "+colors+"    " );
        }
        dialogColorsValueLabel.setText( " "+colors+"    " );
        mandelbrotPanel.setColors(colors);
      }
    }

    /**
     * AdjustmentListener for hueOffset
     */
    private class HueOffsetListener implements AdjustmentListener {

      public void adjustmentValueChanged(AdjustmentEvent evt) {
        Scrollbar source = (Scrollbar)evt.getSource();
        hueOffset = source.getValue();
        hueOffsetValueLabel.setText( " "+hueOffset+"   " );
        float hue = (float)hueOffset / (float)iHueRange;
        source.setBackground(Color.getHSBColor(hue, (float)1.0,(float)1.0));
        mandelbrotPanel.setHueOffset(hue);
      }
    }

    /**
     * AdjustmentListener for hueRange
     */
    private class HueRangeListener implements AdjustmentListener {

      public void adjustmentValueChanged(AdjustmentEvent evt) {
        Scrollbar source = (Scrollbar)evt.getSource();
        hueRange = source.getValue();
        hueRangeValueLabel.setText( " "+hueRange+"   " );
        float hue = (float)hueRange / (float)iHueRange;
        mandelbrotPanel.setHueRange(hue);
      }
    }

    /**
     * AdjustmentListener for superHue
     */
    private class SuperHueListener implements AdjustmentListener {

      public void adjustmentValueChanged(AdjustmentEvent evt) {
        Scrollbar source = (Scrollbar)evt.getSource();
        superHue = source.getValue();
        superHueValueLabel.setText( " "+superHue+"   " );
        float hue = (float)superHue / (float)iHueRange;
        float br = (float)1.0;
        if (superHue == 0) {
          br = (float)0.0;
        }
        source.setBackground(Color.getHSBColor(hue, (float)1.0, br));
        mandelbrotPanel.setSuperHue(hue);
      }
    }

    /**
     * AdjustmentListener for newSizeX
     */
    private class SizeXListener implements AdjustmentListener {

      public void adjustmentValueChanged(AdjustmentEvent evt) {
        Scrollbar source = (Scrollbar)evt.getSource();
        newSizeX = source.getValue();
        sizeXValueLabel.setText( " "+newSizeX+"   " );
      }
    }

    /**
     * AdjustmentListener for newSizeY
     */
    private class SizeYListener implements AdjustmentListener {

      public void adjustmentValueChanged(AdjustmentEvent evt) {
        Scrollbar source = (Scrollbar)evt.getSource();
        newSizeY = source.getValue();
        sizeYValueLabel.setText( " "+newSizeY+"   " );
      }
    }

    /**
     * WindowAdapter for Options Dialog
     */
    private class DialogWindowAdapter extends WindowAdapter {
      public void windowClosing(WindowEvent e) {
        e.getWindow().hide();
        moreOptionsButton.setEnabled(true);
      }
    }
  }

  private class NewWindowDialog extends Dialog {

    NewWindowDialog( Frame frame, String title, boolean modal, MandelbrotPanel newMandelbrotPanelWin ) {
      super( frame, title, modal );

      addWindowListener(new NewWindowAdapter());
      add(newMandelbrotPanelWin);
    }

    /**
     * NewWindowAdapter
     */
    private class NewWindowAdapter extends WindowAdapter {
      public void windowClosing(WindowEvent e) {
        e.getWindow().dispose();
      }
    }
  }
}

