//Titel:            Testbeispiel für MDI-Fenster

// Beispiel eines MDI-Fenster

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

// 1. Import 
import java.util.Observable;
import java.util.Observer;



// DocListener
import javax.swing.event.*;


import java.util.ArrayList;

public class Observer4 extends JFrame implements ActionListener{

  // Container für die Clientfenster
  private JDesktopPane desktopPane = new JDesktopPane();

    // Variablen für die Menüs
  private JMenuBar menuBar1;   // MenüContainer
  private JMenu mainFile;      // Hauptmenü
  private JMenuItem mnNew;     // Menüeintrag
  private JMenuItem mnClose;   // Menüeintrag


  private JMenuItem mnPrevWindows; 
  private JMenuItem mnNextWindows; 
  private JMenuItem mnTile;         // Menüeintrag
  private JMenuItem mnCascade;      // Menüeintrag
  private JMenuItem mnTileVertical;        
  private JMenuItem mnTileHorizontal;        



   // Konstruktor
  public Observer4() {
    this.setSize( 600, 400);
    setTitle( "Observer mit einem JTextArea");
    setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    setGUI();
  }

  public void setGUI() {
      // BorderLayout setzen
    this.getContentPane().setLayout( new BorderLayout() );

      // Variable DesktopPane einfügen
      // in ihr werden alle Clientfenster eingefügt
    this.getContentPane().add(desktopPane, BorderLayout.CENTER);
    setMenus();
  }



    // setzt die Menüs
  private void setMenus() {
    menuBar1 = new JMenuBar();

      // DATEI
    mainFile = new JMenu("Datei");
    mnNew = new JMenuItem("Neu");
    mnClose = new JMenuItem("Schließen");

    mnClose.addActionListener(this);

      // Event definieren
    mnNew.addActionListener(this);
      // Short Key definieren  STRG+N
    mnNew.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_N, ActionEvent.CTRL_MASK));
    mainFile.add(mnNew);

    mainFile.addSeparator();

    mnClose.addActionListener(this);
    mainFile.add(mnClose);

      // Hauptmenü FILE in den MenüBar einfügen
    menuBar1.add(mainFile);

    // ----------------
    // Menü Windows
    JMenu mainWindows = new JMenu("Fenster");
    mnPrevWindows = new JMenuItem("Vorheriges Fenster");
    mnNextWindows = new JMenuItem("Nächstes Fenster");

    mnCascade = new JMenuItem("Kaskade");
    mnTile = new JMenuItem("Aufteilen");

    mnTileVertical = new JMenuItem("Aufteilen Vertikal");
    mnTileVertical.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0));

    mnTileHorizontal = new JMenuItem("Aufteilen Horizontal");
    mnTileHorizontal.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0));

    mnPrevWindows.addActionListener(this);
    mnNextWindows.addActionListener(this);
    mainWindows.addSeparator();
    mnCascade.addActionListener(this);
    mnTile.addActionListener(this);
    mnTileVertical.addActionListener(this);
    mnTileHorizontal.addActionListener(this);

      // MenüEinträge definieren
    mainWindows.add(mnPrevWindows);
    mainWindows.add(mnNextWindows);
    mainWindows.addSeparator();
    mainWindows.add(mnCascade);
    mainWindows.add(mnTile);
    mainWindows.add(mnTileVertical);
    mainWindows.add(mnTileHorizontal);
    menuBar1.add(mainWindows);

    this.setJMenuBar(menuBar1);

    for (int i = 0; i < menuBar1.getComponentCount(); i++) {
      JMenu main = (JMenu) menuBar1.getComponent(i);
      main.setFont(new Font("Arial", Font.BOLD, 18));
      for (int j = 0; j < main.getItemCount(); j++) {
        Component c = main.getItem(j);
        if (c instanceof JMenuItem) {
          JMenuItem mn = (JMenuItem) c;
          mn.setFont(new Font("Arial", Font.BOLD, 18));
        }
      }
    }
  } // setMenus


   public void actionPerformed(ActionEvent e) {
      if (e.getSource() == mnNew) {
         mnNew_click();
      }


    
       if (e.getSource() == mnPrevWindows ) {
          WindowLayout.mnPrevWindows(desktopPane);
       }
       if (e.getSource() == mnNextWindows ) {
         WindowLayout.mnNextWindows(desktopPane);
       }


      if (e.getSource() == mnCascade) {
         WindowLayout.mnCascade(desktopPane);
      }
      if (e.getSource() == mnTile) {
          WindowLayout.mnTile(desktopPane);
      }
      if (e.getSource() == mnTileVertical) {
         WindowLayout.mnTileVertical(desktopPane);    
      }
      if (e.getSource() == mnTileHorizontal) {
         WindowLayout.mnTileHorizontal(desktopPane);
      }


      if (e.getSource() == mnClose) {
         System.exit(0);
      }
   }



     // Event definieren, CODE
  void mnNew_click() {
    MyInternalFrame frame = new MyInternalFrame();
    int pos = desktopPane.getAllFrames().length+1;
    frame.setBounds(pos*10, pos*10, 450, 300);
    desktopPane.add(frame);
    desktopPane.moveToFront(frame);
  }



   private void  mnPrevWindows_click() {
      JInternalFrame[] frames = desktopPane.getAllFrames();
      if(frames.length > 1)
     { 
         desktopPane.selectFrame(false);
     }
   }
	
   private void mnNextWindows_click() {
       JInternalFrame[] frames = desktopPane.getAllFrames();
        if(frames.length > 1)
       {
          desktopPane.selectFrame(true);
       }
   }

   // -----------------

   // Hauptprogramm
  static public void main(String[] args) {
    Observer4 frame = new Observer4();
    frame.setVisible(true);
  }
  
}


class Datei extends java.util.Observable {
  private static Datei instance = new Datei();

  public String text = "1. Zeile";

  public static Datei getInstance() {
    return instance;
  }

  public void DataChangedFromViewer(int nr, String text) {
   
      this.text =text;  // geänderte Daten speichern
      super.setChanged();
        // Sende Nachricht an ALLE Viewer
      super.notifyObservers( new Integer(nr) );
  }

}



// Clientfenster
class MyInternalFrame extends JInternalFrame implements Observer, ActionListener{
  private static int nummer=0;
  private int fensternummer=0;
  private JTextAreaDocListener editor;
  private boolean updateModusErlaubt=true;

  public MyInternalFrame() {
    setGUI();
    setVisible(true);
  }

  public void setGUI() {

    this.setClosable(true);
    this.setIconifiable(true);
    this.setMaximizable(true);
    this.setResizable(true);
    this.getContentPane().setLayout( new BorderLayout() );

    editor = new JTextAreaDocListener(this);
    this.getContentPane().add( new JScrollPane(editor), BorderLayout.CENTER);
    editor.setFont(new Font("Arial", Font.BOLD, 18));
    fensternummer=++nummer;
    setTitle("Nr: "+fensternummer);

    Datei modell = Datei.getInstance();
    updateModusErlaubt=false;  // hier wird aktualisiert, sperre
    editor.setText(modell.text);
    updateModusErlaubt=true;  // sperre aufgehoben
    modell.addObserver(this);  
  }


// 12. Schritt  Schnittstellen implementieren: Methode Update
  // aufgerufen vom Controller
  public void update(Observable o, Object arg) {
    Datei modell = Datei.getInstance();
    //if (((Integer) arg).intValue() != fensternummer) {
    int sourceViewerTag = ((Integer) arg).intValue();
    if (sourceViewerTag != fensternummer) {
      System.out.println("  update.: fensternummer: "+fensternummer);
      updateModusErlaubt=false;
      editor.setText(modell.text);  // es folgen 2 Nachrichten remove/insert
      updateModusErlaubt=true;
    }
    else {
      updateModusErlaubt=true;
    }
  }



   @Override
  public void actionPerformed(ActionEvent e){
      if (e.getSource()==editor) {
        System.out.println("actionPerf.: fensternummer: "+fensternummer+
              "  updateModusErlaubt: "+updateModusErlaubt);
        System.out.println("     command: "+e.getActionCommand() );
        if (updateModusErlaubt) {
          System.out.println("updateModus: fensternummer: "+fensternummer);
          Datei modell = Datei.getInstance();
          updateModusErlaubt=false;
          modell.DataChangedFromViewer( fensternummer, editor.getText() );        
        }
        else  {
          
        }
      }
  }


}  // MyInternalFrame



class JTextAreaDocListener extends JTextArea   {
  ArrayList<ActionListener> listeners = new ArrayList<ActionListener>();

  public synchronized void addEventListener(ActionListener listener)  {
    listeners.add(listener);
  }
  public synchronized void removeEventListener(ActionListener listener)   {
    listeners.remove(listener);
  }
 
  JTextAreaDocListener thiss;
  
   public JTextAreaDocListener(ActionListener listener) {
     super();
     init(listener);
   }


   public JTextAreaDocListener(ActionListener listener, String sText) {
     super(sText);
     init(listener);
   }

   private void init(ActionListener listener) {
     addEventListener(listener);
     thiss = this;


     this.getDocument().addDocumentListener(new DocumentListener(){
        @Override
         public void changedUpdate(DocumentEvent ev) {
             //System.out.println("changedUpdate");
             /*
              ActionEvent(Object source, int id, String command)
                        Constructs an ActionEvent object.
              ActionEvent(Object source, int id, String command, int modifiers)
                        Constructs an ActionEvent object with modifier keys.
              ActionEvent(Object source, int id, String command, long when, int modifiers)
                        Constructs an ActionEvent object with the specified modifier keys and timestamp.
             */
            
             for (ActionListener listener : listeners) {
               ActionEvent e = new ActionEvent( thiss,0,"update",0,0);
               listener.actionPerformed(e);
             }
         }
         
      @Override
         public void insertUpdate(DocumentEvent ev) {
             //System.out.println("changed insert");
             ActionEvent e = new ActionEvent(thiss,0,"insert",0,0);
             for (ActionListener listener : listeners) {
               listener.actionPerformed(e);
             }
         }
         
      @Override
         public void removeUpdate(DocumentEvent ev) {
             // System.out.println("changed remove");
             ActionEvent e = new ActionEvent(thiss,0,"remove",0,0);
             for (ActionListener listener : listeners) {
               listener.actionPerformed(e);
             }
         }
     });     

    } // init
}  // JTextAreaDocListener




class WindowLayout {

  
    public static void mnPrevWindows(JDesktopPane desktopPane) {     
      JInternalFrame[] frames = desktopPane.getAllFrames();
      if(frames.length > 1)
     { 
         desktopPane.selectFrame(false);
     }
   }
	
   public static void mnNextWindows(JDesktopPane desktopPane) {
       JInternalFrame[] frames = desktopPane.getAllFrames();
        if(frames.length > 1)
       {
          desktopPane.selectFrame(true);
       }
   }


 private static  void setCascadeFrame(JDesktopPane desktopPane) {
   final int w = 250;
   final int h = 150;
   JInternalFrame frame;     // the actual Clientwindow
   JInternalFrame[] ff;  // Array of all internal Clientwindow
   ff = desktopPane.getAllFrames();
   for (int pos = 0; pos<ff.length; pos++) {
      frame = (JInternalFrame)ff[pos];  // typecasting
      frame.setBounds(
     (pos*50 % (desktopPane.getWidth()-w) ),
     (pos*50 % (desktopPane.getHeight()-h) ), 
         w, h);
   } // for
  }  // setCascadeFrame()

   // -----------------
  public static void mnCascade(JDesktopPane desktopPane) {
      int i, j, p, Abstand, w, h, n;
      JInternalFrame f;     // the actual Clientwindow
      JInternalFrame[] ff;  // Array of all internal Clientwindow
      ff = desktopPane.getAllFrames();
      n = ff.length;  // Count of Frames
      // calc the distance of the frames, depends on the count of the frames
      if (n > 0) {
           if (n<=3)
             Abstand = 80;
           else {
             if (n>6)
               Abstand = 25;
             else
               Abstand = 40;
           }
           // calc the width and height of each frame
           w = desktopPane.getWidth() - (n-1)*Abstand;
           h = desktopPane.getHeight() - (n-1)*Abstand;
           if ( (w<80) || (h<80) || (n>20) ) {
             setCascadeFrame(desktopPane);
           }
           else {
             if (w<=0) w = 100;
             if (h<=0) h = 50;
             // the top frame has the index 0 !
             // index i is for the position
             // index j ist for the frames, the last is the top-frame !
             for (i = 0, j=n-1; i <n; i++,j--) {
                 f = (JInternalFrame)ff[j];  // typecasting
                 p = i*Abstand;
                 f.setLocation(p, p);        // position
                 f.setSize(w,h);             // width height
             }
           }
      }
  }   // MnCascade_click() {


  public static void mnTile(JDesktopPane desktopPane) {
    System.out.println("Menüeintrag Tile");
      JInternalFrame[] ff;    // Array for all internal frames
      JInternalFrame f; //
      int hoehe, anz, breite, left, top;
      int indexCol, anzCol, anzRow, anzRowUnten, n, i, h, b1, b2;
      boolean bZweiBreiten;

      ff = desktopPane.getAllFrames();
        // Count > 0 ?
      if (ff.length > 0) {
          // Quadratische Aufteilung

            /*
             5:
                 -
                - -
                - -

             11:
                 - -
                - - -
                - - -
                - - -

            */

          n = ff.length;
          if (n < 1) {
              return;
          }
          if (n == 1) {
              f = (JInternalFrame)ff[0];
              try {
                  f.setMaximum(true);
              }
              catch (Exception e1) {
                  e1.printStackTrace();
              }
              return;
          }
          
          anzCol = (int)Math.round(Math.sqrt(n)); // Wurzel aus Anzahl
          // bestimme Anzahl der unteren (vollen) Zeilen
          anzRowUnten = n / anzCol;  // trunc
            // Abfrage nach Rest
          if (n > (anzRowUnten * anzCol)) {
              anzRow = anzRowUnten + 1;
              bZweiBreiten = true;
          }
          else {
              anzRow = anzRowUnten;
              bZweiBreiten = false;
          }
          //  AnzRow zeigt die Anzahl aller Zeilen

          // nun werden zwei Breiten und eine Höhe ausgerechnet
          // 1. Breite  gilt für die vollen Reihen
          // 2. Breite  gilt für oberste  Reihe, falls sie nicht voll ist
          hoehe = desktopPane.getHeight();
          h = desktopPane.getHeight() / anzRow;
          b1 = desktopPane.getWidth() / anzCol; // volle Breite
          if (bZweiBreiten)
             b2 = desktopPane.getWidth() / (n - anzCol * anzRowUnten); // untere Breite
          else
             b2 = b1;

          // Rückwärts
          anz = 0;
          left = 0;
          top = hoehe - h;
          indexCol = 0;
          for (i = ff.length - 1; i >= 0; i--) {
              f = (JInternalFrame)ff[i];
              //f.setSize(100,100);
              try {
                  f.setMaximum(false);
              }
              catch (Exception e2) {
                  e2.printStackTrace();
              }
              anz++;
              indexCol++;
              if (indexCol > anzCol) {
                  // neue Reihe anfangen
                  left = 0;
                  top -= h;
                  indexCol = 1;
              }
              // Position im Fenster setzen
              f.setLocation(left, top);
              // Breite Höhe setzen
              // Check, ob man in der obersten Zeile sich befindet ?
              if (anz > (anzRowUnten * anzCol)) {
                  f.setSize(b2, h); // width height
                  left += b2;
              }
              else {
                  f.setSize(b1, h); // width height
                  left += b1;
              }
          }
      }
  } // MnTile



  public static void mnTileHorizontal(JDesktopPane desktopPane) {
      JInternalFrame[] ff;    // Array for all internal frames
      JInternalFrame f; //
      int hoehe, breite, top;
      int i, n;
      ff = desktopPane.getAllFrames();
        // Count > 0 ?
      if (ff.length > 0) {
          n = ff.length;
          breite = desktopPane.getWidth() ;
          hoehe = desktopPane.getHeight()/ n;
          if (ff.length==1 ) {
              f = (JInternalFrame)ff[0];
              try {
                  f.setMaximum(false);
                  f.setSize(desktopPane.getWidth(), hoehe);
                  f.setLocation(0,0);
                  return;
              }
              catch (Exception e2) {
                  e2.printStackTrace();
              }            
          }
          top=0;
          for (i = ff.length - 1; i >= 0; i--) {
              f = (JInternalFrame)ff[i];
              //f.setSize(100,100);
              try {
                  f.setMaximum(false);
                  f.setSize(breite, hoehe);
                  f.setLocation(0,top);  // x/y
                  top+=hoehe;
              }
              catch (Exception e2) {
                  e2.printStackTrace();
              }
          }
      }
    
  }



  public static void mnTileVertical(JDesktopPane desktopPane) {
      JInternalFrame[] ff;    // Array for all internal frames
      JInternalFrame f; //
      int hoehe, breite, left, top;
      int i, n;
      ff = desktopPane.getAllFrames();
        // Count > 0 ?
      if (ff.length > 0) {
          n = ff.length;
          breite = desktopPane.getWidth() / n;
          hoehe = desktopPane.getHeight();
          if (ff.length==1 ) {
              f = (JInternalFrame)ff[0];
              try {
                  f.setMaximum(false);
                  f.setSize(desktopPane.getWidth(), hoehe);
                  f.setLocation(0,0);
                  return;
              }
              catch (Exception e2) {
                  e2.printStackTrace();
              }            
          }
          left=0;
          for (i = ff.length - 1; i >= 0; i--) {
              f = (JInternalFrame)ff[i];
              //f.setSize(100,100);
              try {
                  f.setMaximum(false);
                  f.setSize(breite, hoehe);
                  f.setLocation(left,0);  // x/y
                  left+=breite;
              }
              catch (Exception e2) {
                  e2.printStackTrace();
              }
          }
      }
    
  }
  
  
}
