/*
 * OkosAWT.java
 * néhány segédosztály az AWT inkompatibilitásai egy részének kivédésére
 * by pts@fazekas.hu at Sat Jun 30 12:58:54 CEST 2001
 * további doksi: a README-ben és e file legkülső class-ának fejkommentjében
 *
 * Kincskereső Kisgömböc (C) Early May 2001 by eNTitánok (Rév Szilvia,
 * Szabó Péter <pts@inf.bme.hu>, Szurdi Miklós, Weizengruber Attila).
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or   
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package eNTitanok.gfx;
import java.applet.Applet;
import java.awt.Component;
import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Label;
import java.awt.Insets;
import java.awt.Window;
import java.awt.Dimension;
import java.awt.Panel;
import java.awt.Font;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseListener;
import java.io.InputStream;

/**
 * Néhány segédosztály az AWT hülyeségei és inkompatibilitásai egy részének
 * kivédésére.
 */
public class OkosAWT {
  /**
   * Akkor váltódik ki, ha egy egyszeres értékadású változónak többször
   * akarunk értéket adni.
   */
  public static class TobbszorError extends Error {}

  /**
   * createImage() hívásokhoz használjuk.
   */
  private static Component mainComponent;
  private static Toolkit mainToolkit;
  /**
   * @param mainComponent Általában Applet-et szokás megadni, mert annak
   *        van getCodeBase() metódusa.
   */
  public static void setMainComponent(Component mainComponent_) {
    if (mainComponent_==null) return;
    if (mainComponent!=null && mainComponent!=mainComponent)
      throw new TobbszorError();
    mainComponent=mainComponent_;
    mainToolkit=mainComponent.getToolkit();
    if (mainToolkit==null) mainToolkit=Toolkit.getDefaultToolkit();
    if (mainToolkit==null) throw new NullPointerException();
  }

  public static Toolkit getMainToolkit() {
    return mainToolkit==null?Toolkit.getDefaultToolkit():mainToolkit;
  }
  public static Component getMainComponent() {
    return mainComponent;
  }
  public static ClassLoader getMainClassLoader() {
    return mainComponent.getClass().getClassLoader();
  }
  
  /**
   * Csak akkor változtatja meg, ha null volt.
   */
  public static void setMainComponentWeak(Component mainComponent_) {
    if (mainComponent==null) setMainComponent(mainComponent_);
  }
  /**
   * mainComponent.getCodeBase()-hez relatívan egy kép betöltését kezdeményezi
   * (getImage() és prepareImage()). mainComponent a hívás előtt legyen
   * beállítva.
   */
  public static Image getImageBase(String filenev) {
    Image kep;
    if (mainComponent instanceof Applet) {
      Applet ap=(Applet)mainComponent;
      kep=ap.getCodeBase()==null?
        getMainToolkit().getImage(filenev):
        ap.getImage(ap.getCodeBase(), filenev);
    } else kep=getMainToolkit().getImage(filenev);
    mainComponent.prepareImage(kep, mainComponent)// azonnal megkezdi a letöltést
    return kep;
  } // getImageBase()
  /**
   * A megadott, a programkód mellett található erőforrásfile-t adja vissza.
   * @param eroforras / jelekkel elválasztott, kiterjesztéssel rendelkező,
   *        relatív filenév.
   * @return <code>null</code>, ha nincs ilyen erőforrás
   */
  public static InputStream getEroforrasStreamOf(ClassLoader classLoader, String eroforras) {
    return (classLoader==null)?ClassLoader.getSystemResourceAsStream(eroforras)
                              :classLoader.getResourceAsStream(eroforras);
  }
  /**
   * A megadott, a programkód mellett található erőforrásfile tartalmát adja
   * vissza.
   * @param eroforras / jelekkel elválasztott, kiterjesztéssel rendelkező,
   *        relatív filenév.
   * @return <code>null</code>, ha nincs ilyen erőforrás
   */
  public static byte[] getEroforrasByteTOf(ClassLoader classLoader, String eroforras) {
    // az InputStream-ből amúgy is byte-okat lehet olvasni
    byte jott[]=new byte[4096];
    byte t[]=new byte[4096];
    int hossz;
    int len;
    hossz=0;
    try {
      InputStream is=getEroforrasStreamOf(classLoader, eroforras);
      while ((len=is.read(jott))>1) {
        while (len+hossz>t.length) {
          byte tregi[]=t;
          t=new byte[2*tregi.length];
          System.arraycopy(tregi,0,t,0,hossz);
        }
        System.arraycopy(jott,0,t,hossz,len);
        hossz+=len;
      }
      is.close();
      byte ret[]=new byte[hossz];
      System.arraycopy(t,0,ret,0,hossz);
      return ret;
    } catch (Exception e) {
      return null;
    }
  } /// getEroforrasByteT()
  /**
   * A megadott, a programkód mellett található erőforrásfile tartalmát adja
   * vissza. A visszaadott karakterek 0..255-ig terjednek.
   * @param eroforras / jelekkel elválasztott, kiterjesztéssel rendelkező,
   *        relatív filenév.
   * @return <code>null</code>, ha nincs ilyen erőforrás
   */
  public static String getEroforrasStringOf(ClassLoader classLoader, String eroforras) {
    byte r[]=getEroforrasByteTOf(classLoader, eroforras);
    return r==null?null:new String(r);
    } /// getEroforrasString()
   
  public static class OkosCanvas extends Canvas {
    protected Dimension dim;
    protected int w, h;
    protected Font font2;
    public void setFont2(Font font2) { this.font2=font2; }
    public Font getFont2() { return font2; }
    protected Dimension getDim() {
      if (dim==null) dim=new Dimension(w,h); 
      return dim;
    }
    public Dimension getPreferredSize() { return getDim()}
    public void setSize(int w, int h) {
      dim=new Dimension(w,h);
      this.w=w; this.h=h;
      super.setSize(w,h);
    }
    public void atmeretez(int w, int h) { setSize(w, h)}
    public void setSize(Dimension d) { setSize(d.width, d.height)}
  } /// class OkosCanvas
  
  /**
   * Annyival több (kevesebb), mint az OkosCanvas, hogy csak fix pixelméretben
   * hajlandó megjelenni.
   */
  public static class OkosFixCanvas extends OkosCanvas {
    public Dimension getMinimumSize() { return getDim()}
    public Dimension getMaximumSize() { return getDim()}
  } /// class OkosFixCanvas

  public static class OkosPanel extends Panel {
    protected int w, h;
    public Dimension getPreferredSize() {
      return new Dimension(w, h);
    }
    public void setSize(int w, int h) {
      this.w=w; this.h=h;
      super.setSize(w,h);
    }
    public void setSize(Dimension d) { setSize(d.width, d.height)}
  }

  /**
   * Néhány hasznos, technikai jellegű funkcióval egészíti ki az Applet
   * osztályt. Támogatást nyújt Applet-ek application-ből történő létrehozáshoz.
   */
  public static class OkosApplet extends Applet {
    protected int w, h;
    protected boolean application_e;
    // ^^^ az applet-et kézzel, application-ből hoztuk-e létre?
    protected Font font2;
    public void setFont2(Font font2) { this.font2=font2; }
    public Font getFont2() { return font2; }
    public Dimension getPreferredSize() {
      return new Dimension(w, h);
    }
    public void setSize(int w, int h) {
      this.w=w; this.h=h;
      super.setSize(w,h);
    }
    /*/ // deprecation miatt nem
    public void resize(int w, int h) {
      // by pts@fazekas.hu at Sun May 13 21:06:05 CEST 2001
      setSize(w, h);
    }
    /***/
    public void setSize(Dimension d) { setSize(d.width, d.height)}
    public OkosApplet() { this(false)}
    public OkosApplet(boolean application_e) {
      OkosAWT.setMainComponentWeak(this);
      this.application_e=application_e;
    }
    public java.net.URL getCodeBase() {
      // JDK1.1 bugyuta, hibát jelezne
      return application_e?null:super.getCodeBase();
    }
    /**
     * A megadott, a programkód mellett található erőforrásfile-t adja vissza.
     * @param eroforras / jelekkel elválasztott, kiterjesztéssel rendelkező,
     *        relatív filenév.
     * @return <code>null</code>, ha nincs ilyen erőforrás
     */
    public InputStream getEroforrasStream(String eroforras) {
      return getEroforrasStreamOf(getClass().getClassLoader(), eroforras);
    }
    /**
     * A megadott, a programkód mellett található erőforrásfile tartalmát adja
     * vissza.
     * @param eroforras / jelekkel elválasztott, kiterjesztéssel rendelkező,
     *        relatív filenév.
     * @return <code>null</code>, ha nincs ilyen erőforrás
     */
    public byte[] getEroforrasByteT(String eroforras) {
      return getEroforrasByteTOf(getClass().getClassLoader(), eroforras);
    } /// getEroforrasByteT()
    /**
     * A megadott, a programkód mellett található erőforrásfile tartalmát adja
     * vissza. A visszaadott karakterek 0..255-ig terjednek.
     * @param eroforras / jelekkel elválasztott, kiterjesztéssel rendelkező,
     *        relatív filenév.
     * @return <code>null</code>, ha nincs ilyen erőforrás
     */
    public String getEroforrasString(String eroforras) {
      return getEroforrasStringOf(getClass().getClassLoader(), eroforras);
    } /// getEroforrasString()
  } /// class OkosApplet

  /**
   * Az OkosFrame egy olyan Frame (kerettel és esetleg menüsorral rendelkező,
   * ,,toplevel'' ablak), ami egyetlen :Component-et jelenít meg önmagán
   * belül, mégpedig úgy, hogy semmi (azaz 0, azaz nulla pixelnyi) helyet
   * hagy a saját széle és a Component széle között. Tehát az :OkosFrame
   * mérete (amit a .getSize() visszaad) épp annyival nagyobb a
   * :Component méreténél, amennyit az ablak címsora és kerete elfoglal.
   *
   * Sajnos JDK1.3-ban ezt megváltoztatták, úgyhogy ott az OkosFrame nagyobb
   * ablakot hoz létre a szükségesnél.
   *
   * A :Component méretét a programból az :OkosFrame setSizeC()
   * metódusával érdemes állítani, mert ez a metódus a :Component méretét
   * a megadott értékre változtatja, _és_ az :OkosFrame méretét ennek
   * megfelelően állítja. A :Component setSize() és resize() metódusait
   * érdemes elkerülni. Az :OkosFrame setSize() metódusát tilos meghívni,
   * mert félreértésre adhat okot, hogy mit csinál (helyette: setSizeC()
   * vagy setSizeF()). Ha a felhasználó az :OkosFrame-et (pl. az egérrel)
   * átméretezi, akkor vele együtt nő a
   * :Component mérete is.
   *
   * A :Component getPreferredSize() függvényének a kívánt méretet kell
   * visszadnia. Ez teljesül például :TextField-re (setColumns() után).
   * Ez :Panel, :Canvas és :Applet objektumokra nem teljesül, mert ezek
   * olyan bugyuták, hogy mindig az aktuális méretük jó nekik. Tehát az
   * _NEM_ fog működni, ha inicializáláskor meghívjuk a :Canvas setSize()
   * függvényét, mivel a :Canvas ezt a méretet néhány röpke pillanat alatt
   * elfelejti. Canvas helyett érdemes OkosCanvas-t használni.
   *
   * A egyszerű programozó elsőre azt gondolná, hogy az OkosFrame által
   * ellátott egyszerű, alapvető feladatot a Java AWT alapból tudja, és nem
   * kell rengeteg extra osztályt létrehozni... De kell.
   */
  public static class OkosFrame extends Frame implements WindowListenerComponentListener {
    public static class NeeztError extends Error {}
    protected int dx, dy; // mennyivel nagyobb az :OkosFrame, mint a :Component
    protected boolean dok;
    protected boolean nyitva;
    public static final int A_SEMMI=0, A_BEZAR=1, A_KILEP=2, A_APPLETKILEP=3;
    protected int autoKilep; // :OkosFrame bezárásakor program vége?
    protected Component c;
    public OkosFrame(String title, Component c, int autoKilep) { super(title);  init2(c)this.autoKilep=autoKilep; }
    public OkosFrame(String title, Component c) { super(title);  init2(c)}
    public OkosFrame(Component c) { super(); init2(c)}
    private void init2(Component c) {
      OkosAWT.setMainComponentWeak(c);
      // ^^^ nem this, hanem c. (bár sehol sem használjuk ki, de így konzisztens
      //     az OkosApplet-tel)
      this.c=c;
      autoKilep=c instanceof Applet ? A_SEMMI : A_KILEP;
      dok=false; dx=40; dy=40; // ha nem elég nagy, bizonyos események meg se hívódnak :-((
      // ^^^ majd az első megjelenítéskor kiderül az igazi
      Dimension d=getPreferredSize();
      // setResizable(true);
      setSizeF(d);
      // setBackground(Color.pink);
      // ^^^ debug célokra. Néha látszódhat (pl. TextField-nél 1 pixel vastagon)
      setLayout(new BorderLayout(0,0));
      /* ^^^ BorderLayout: tiszta, jól működő, kiszámítható, testreszabható
       * LayoutManager. Nagy hátránya, hogy legfeljebb 5 komponenst képes
       * kezelni. Csak a CENTER-be rakott komponens méreteződik át mindkét
       * irányban. (Ezt az automatikus átméreteződést semelyik más
       * LayoutManager-nél nem sikerült elérnem. Régi szép, TCL-es idők. Az ember
       * elolvasta a pack(n) man page-et, megértette, hogy működnek a dolgok,
       * kigondtolta, kipróbálta, és tényleg működtek. Most az ember elolvassa a
       * 3 Java könyvet, megnézi a példaprogramokat, visszafejti a JDK-s .class
       * file-okat, és még mindig nem világos, hogyan lehetne megoldani azt az
       * egyszerű, mindennapos :Component-elhelyezési stratégiát, amihez TCL-ben
       * mindösse 2 sort kellet írni.)
       */
      addWindowListener(this);
      addComponentListener(this);
      //super.setSize(100,100);
      add(c);
      //invalidate();
      // pack();
    }
    public Dimension getPreferredSize() {
      Dimension d=c.getPreferredSize();
      return new Dimension(dx+d.width, dy+d.height);
    }
    public Dimension getMinimumSize() {
     Dimension d=c.getMinimumSize();
     return new Dimension(dx+d.width, dy+d.height);
    }
    public Dimension getMaximumSize() {
      Dimension d=c.getMaximumSize();
      return new Dimension(dx+d.width, dy+d.height);
    }
    private void szamol() {
      /* Kiszámoljuk dx-et és dy-t. A Java hülyesége miatt ezt csak akkor
       * tudjuk megtenni, ha az ablak már megnyílt, és a componentResized()
       * meghívdott.
       */
      if (!nyitva || dok) return;
      // System.out.println("wo F.w="+this.getSize().width+" F.h="+this.getSize().height);
      // System.out.println("wo L.w="+c.getSize().width+" L.h="+c.getSize().height);
      Dimension dc=c.getSize();
      Dimension d=this.getSize();
      dx=d.width-dc.width;
      dy=d.height-dc.height;
      // System.out.println("dx="+dx+" dy="+dy);
      dok=true;
      meretKovet();
    }
    public void meretKovet() {
      Dimension dc=c.getPreferredSize();
      // System.out.println(dc.width); System.out.println(dc.height);
      // Brr, többszálúság.
      synchronized(this) {
        boolean b=isResizable();
        setResizable(true);
        super.setSize(dc.width+dx, dc.height+dy);
        setResizable(b);
      }
    }
    //public void setSize(int w, int h) {
    //  throw new NeeztError(); // "hivd setSizeF()-et vagy setSizeC()-t");
    //}
    public void setSize(Dimension d) { setSize(0,0)}
    public void setSizeF(int w, int h) {
      /* @param (w,h): az :OkosFrame új mérete */
      c.setSize(w-dx, h-dy);
      super.setSize(w,h);
    }
    public void setSizeF(Dimension d) { setSizeF(d.width,d.height)}
    public void setSizeC(int w, int h) {
      /* @param (w,h): a :Component új mérete */
      c.setSize(w,h);
      super.setSize(w+dx, h+dy);
    }
    public void setSizeC(Dimension d) { setSizeC(d.width,d.height)}
    // validate() és invalidate() átdefiniálható, meg is hívódnak, de nem
    // lehet konzisztensen (vagy csak egérrel lesz átméretezhető, vagy csak
    // programból)
    // public void validate() { System.out.println("VAL."); super.validate(); }
    // public void invalidate() { System.out.println("INVAL."); meretKovet(); super.invalidate(); }
    public void componentShown(ComponentEvent e) {}
    public void componentHidden(ComponentEvent e) {}
    public void componentResized(ComponentEvent e) {
      // validate();
      // System.out.println("RRR");
      //nyitva=true; // Win32 JDK1.2.2 alatt lehet, Linux JDK1.1.7 alatt nem
      szamol();
    }
    public void componentMoved(ComponentEvent e) {}
    public void windowOpened(WindowEvent e) {
      /* az ablak első megnyílása után határahozható csak meg dx és dy. Tehát
       * figyeljük az eseményt, és megtesszük.
       */
      nyitva=true;
      super.setSize(getPreferredSize())// Win32 JDK1.2.2 miatt kell
      // validate();
      // System.out.println("ZZZ");
    }
    public void windowClosing(WindowEvent e) {
      if (autoKilep==A_BEZAR) ((Frame)e.getComponent()).setVisible(false);
      else if (autoKilep==A_KILEP) System.exit(0);
      else if (autoKilep==A_APPLETKILEP) {
        ((Applet)c).stop();
        ((Applet)c).destroy();
        System.exit(0);
      }
    }
    public void windowClosed(WindowEvent e) { dok=false; nyitva=false}
    public void windowIconified(WindowEvent e) {}
    public void windowDeiconified(WindowEvent e) {}
    public void windowActivated(WindowEvent e) {}
    public void windowDeactivated(WindowEvent e) {}
  } /// class OkosFrame
} /// class OkosAWT