/*
Datei............: Aufg2011.java
Projekt..........: Einführung in die Java-Programmierung
Erstellt.........: 07.12.97, Guido Krüger
Geändert.........: --
Aufgabe..........: Musterlösung zu Aufgabe 20.11
Kommentare.......:

Dieses Programm teilt sich in die Klassen Aufg2011 und die dazu
lokale Klasse MouseDragListener auf. In Aufg2011 befindet sich das
Hauptprogramm sowie das Hauptmenü mit dem Menü zum Laden eines Images
und zum Beenden des Programms. Zum Laden kann zunächst die gewünschte
Bilddatei im Standardfiledialog ausgewählt werden, anschließend wird
das Bild unter Überwachung durch einen ImageTracker vollständig
geladen. Die Zustandsvariable displaystate zeigt dabei den aktuellen 
Bildstatus an und steuert die Ausgabe in der Clientarea. 

Zur Anzeige des Bildausschnitts werden die beiden Variablen imagesize
und viewport verwendet. imagesize gibt die Höhe und Breite des 
geladenen Bildes in Pixeln an. viewport bezeichnet den Ausschnitt des
Bildes, der gerade angezeigt wird durch seine linke obere Ecke und seine
Höhe und Breite. Innerhalb von paint wird zunächst die Titelzeile des
Hauptfenster gesetzt. Anschließend erfolgt, abhängig vom Ladezustand
displaystate die Anzeige einer textuellen Meldung oder die Darstellung
des Bildes. Das Bild wird durch Aufruf von drawImage angezeigt. Die
hier verwendete Variante ist in der Lage, das Bild automatisch zu 
skalieren und in den gewünschten Ausgabebereich einzupassen.

Das Verändern des Bildausschnitts wird in der Klasse MouseDragListener
vorgenommen. Sie wird im Konstruktor des Hauptfensters einmal instanziert
und als Listener für Maus- und Mausbewegungsereignisse registriert. Der
Bildauschnitt kann nun auf zwei Arten vom Anwender verändert werden. 
Einerseits kann durch Aufziehen eines Rechtecks mit Hilfe der linken 
Maustaste der Bildausschnitt gezoomt werden. Anderseits wird beim Drücken
der rechten Maustaste der jeweils vorige Bildausschnitt wiederhergestellt.
Dazu wird der Stack viewportstack verwendet, auf dem der aktuelle
viewport vor dem Zoomen abgelegt und von dem er beim Drücken der rechten
Maustaste wieder rekonstruiert wird. 

Das Dragging kann in drei Schritte unterteilt werden:

- Beim Drücken der linken Maustatse merkt sich das Listenerobjekt in der
  Variable dragpoint den Startpunkt der Drag-Operation. Weiterhin wird
  ein Rechteck r instanziert, das dazu dient, während des Ziehens der
  Maus den aktuellen Ausschnitt zu visualisieren.
- Beim Ziehen der Maus wird mit Hilfe der Methode dragIgnore zunächst 
  überprüft, ob der Mauszeiger den "Zitterbereich" schon verlassen hat.
  Ist dies der Fall, wird das Rechteck r mit der aktuellen Mausposition
  aktualisiert und im XOR-Modus auf den Bildschirm gezeichnet. Falls es
  vom vorigen Dragaufruf noch ein Rechteck auf dem Bildschirm gibt, wird
  dieses durch wiederholte Ausgabe zuvor entfernt.
- Nach dem Loslassen der Maustaste wird der bisherige viewport auf dem
  Stack gesichert und neu berechnet. Anschließend wird das Rechteck
  vom Bildschirm entfernt und durch Aufruf von repaint() der Ausschnitt
  neu gezeichnet.

Anmerkung:

Das Maushandling im JDK 1.1.2 unter Windows 95 funktioniert leider nicht
vollkommen fehlerfrei. Insbesondere beim Klicken auf Rahmenelemente oder
Dialogboxen, die auf dem Ausgabefenster liegen, werden manchmal noch
MausPressed- oder MouseReleased-Events erzeugt. Einige von diesen Problemen
könnte man mit etwas mehr Aufwand lösen, indem beispielsweise mit getInsets
abgefragt wird, ob das Mausevent sich tatsächlich innerhalb des nutzbaren
Bereichs befindet. Wir haben diese Details hier nicht ausprogrammiert, sie
seien Weiterentwicklungen des Programms überlassen.
*/
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

public class Aufg2011
extends Frame
implements ActionListener
{
  //Konstanten
  static final int STATE_NOIMAGE = 0;
  static final int STATE_LOADING = 1;
  static final int STATE_LOADERROR = 2;
  static final int STATE_DISPLAYING = 3;

  //Instanzmerkmale
  String filename;
  Dimension imagesize;
  Rectangle viewport;
  Image image;
  int displaystate;

  public static void main(String[] args)
  {
	Aufg2011 wnd = new Aufg2011();
	wnd.setSize(400, 400);
	wnd.setVisible(true);
  }

  public Aufg2011()
  {
	super("Aufg2011");
	filename = "";
	imagesize = new Dimension();
	viewport = new Rectangle();
	displaystate = STATE_NOIMAGE;
	setBackground(Color.white);
	//Menü erzeugen
	Menu menu = new Menu("Datei");
	//Datei.Öffnen
	MenuItem mi = new MenuItem("Öffnen");
	mi.setActionCommand("DateiOeffnen");
	mi.addActionListener(this);
	menu.add(mi);
	//Datei.Beenden
	menu.addSeparator();
	mi = new MenuItem("Beenden");
	mi.setActionCommand("DateiBeenden");
	mi.addActionListener(this);
	menu.add(mi);
	//Menü zur Menubar hinzufügen
	MenuBar mainmenu = new MenuBar();
	mainmenu.add(menu);
	setMenuBar(mainmenu);
	//Windowlistener registrieren
	addWindowListener(
	  new WindowAdapter() {
        public void windowClosing(WindowEvent event) {
		  System.exit(0);
		}
	  }
    );
	//Mauslistener registrieren
	MouseListener listener = new MouseDragListener();
	addMouseListener(listener);
	addMouseMotionListener((MouseMotionListener)listener);
  }

  public void actionPerformed(ActionEvent event)
  {
	String cmd = event.getActionCommand();
	if (cmd.equals("DateiOeffnen")) {
	  //FileOpenDialog aufrufen
	  FileDialog dlg = new FileDialog(this);
	  dlg.setMode(FileDialog.LOAD);
	  dlg.setFile("*.gif");
	  dlg.setVisible(true);
	  String newfilename = dlg.getFile();
	  String pathname = dlg.getDirectory();
	  if (newfilename != null && newfilename.length() > 0) {
		//Lademeldung ausgeben
		displaystate = STATE_LOADING;
		repaint();
		try { Thread.sleep(300); } catch (InterruptedException e) {	}
		//Image laden;
		image = getToolkit().getImage(pathname + newfilename);
		MediaTracker mt = new MediaTracker(this);
		mt.addImage(image, 0);
		try {
		  //Warten, bis das Image vollständig geladen ist,
		  mt.waitForAll();
		} catch (InterruptedException e) {
		  //nothing
		}
		//Dateinamen für die Titelzeile merken
		filename = newfilename;
		//Abmessungen ermitteln
		imagesize.width = image.getWidth(null);
		imagesize.height = image.getHeight(null);
		if (imagesize.width == -1 || imagesize.height == -1) {
		  displaystate = STATE_LOADERROR;
		} else {
		  viewport.x = 0;
		  viewport.y = 0;
		  viewport.width = imagesize.width;
		  viewport.height = imagesize.height;
		  displaystate = STATE_DISPLAYING;
		}
		repaint();
	  }
	} else if (cmd.equals("DateiBeenden")) {
	  System.exit(0);
	}
  }

  public void paint(Graphics g) 
  {
	//Titelzeile anpassen
	String title = "ImageViewer";
	if (filename.length() > 0) {
	  title += " " + filename;
	  title += " (" + imagesize.width + "," + imagesize.height + ")";
	  title += " (" + viewport.x + "," + viewport.y;
	  title += "," + viewport.width + "," + viewport.height + ")";
	}
	setTitle(title);
	//Image bzw. Ersatzmeldung darstellen
	int left = getInsets().left;
	int top = getInsets().top;
	int width = getSize().width - left - getInsets().right;
	int height = getSize().height - top - getInsets().bottom;
	if (displaystate == STATE_NOIMAGE) {
	  g.setColor(Color.blue);
	  g.drawString("Kein Image geladen", left + 10, top + 20);
	} else if (displaystate == STATE_LOADING) {
	  g.setColor(Color.blue);
	  g.drawString("Image wird geladen...", left + 10, top + 20);
	} else if (displaystate == STATE_LOADERROR) {
	  g.setColor(Color.red);
	  g.drawString("Fehler beim Laden von " + filename, left + 10, top + 20);
	} else if (displaystate == STATE_DISPLAYING) {
	  //Image darstellen
	  g.drawImage(
        image,
        left,
		top,
		left + width,
		top + height,
		viewport.x,
		viewport.y,
		viewport.x + viewport.width,
		viewport.y + viewport.height,
		this
	  );
	}
  }

  class MouseDragListener
  implements MouseListener, MouseMotionListener
  {
	//Instanzmerkmale
	Point dragpoint;
	Rectangle r;
	Graphics g;
	Stack viewportstack;

	public MouseDragListener()
	{
	  viewportstack = new Stack();
	}

	public void mousePressed(MouseEvent event)
	{
	  if (!event.isMetaDown()) { //linke Maustaste
		//Zustandsvariablen setzen
		dragpoint = new Point(event.getX(), event.getY());
		r = new Rectangle(-1, -1, -1, -1);
		//Grafikkontext beschaffen
		g = getGraphics();
		g.setXORMode(getBackground());
		g.setColor(Color.black);
	  }
	}

	public void mouseDragged(MouseEvent event)
	{
	  if (dragpoint != null) {
		if (!dragIgnore(dragpoint, event.getX(), event.getY())) {
		  //Altes Rechteck löschen
		  if (r.x != -1) {
			g.drawRect(r.x, r.y, r.width, r.height);
		  }
		  //Neues Rechteck zeichnen
		  int actx = event.getX();
		  int acty = event.getY();
		  if (actx >= dragpoint.x) {
			r.x = dragpoint.x;
			r.width = actx - dragpoint.x;
		  } else {
			r.x = actx;
			r.width = dragpoint.x - actx;
		  }
		  if (acty >= dragpoint.y) {
			r.y = dragpoint.y;
			r.height = acty - dragpoint.y;
		  } else {
			r.y = acty;
			r.height = dragpoint.y - acty;
		  }
		  g.drawRect(r.x, r.y, r.width, r.height);
		}
	  }
	}

	public void mouseReleased(MouseEvent event)
	{
	  if (!event.isMetaDown() && dragpoint != null) {
		if (!dragIgnore(dragpoint, event.getX(), event.getY())) {
		  //Aktuellen Viewport auf dem Stack sichern
		  Rectangle oldviewport = new Rectangle();
		  oldviewport.x = viewport.x;
		  oldviewport.y = viewport.y;
		  oldviewport.width = viewport.width;
		  oldviewport.height = viewport.height;
		  viewportstack.push(oldviewport);
		  //Neue Größe des sichtbaren Image-Ausschnitts berechnen
		  int left = getInsets().left;
		  int top = getInsets().top;
		  int width = getSize().width - left - getInsets().right;
		  int height = getSize().height - top - getInsets().bottom;
		  int actx = event.getX();
		  int acty = event.getY();
		  viewport.x += (viewport.width * (r.x - left)) / width;
		  viewport.y += (viewport.height * (r.y - top)) / height;
		  viewport.width = viewport.width * r.width / width;
		  viewport.height = viewport.height * r.height / height;
		  //Altes Rechteck löschen
		  if (r.x != -1) {
			g.drawRect(r.x, r.y, r.width, r.height);
		  }
		  //Grafikkontext löschen
		  g.dispose();
		  //Zustandsvariablen zurücksetzen
		  dragpoint = null;
		  r.x = -1;
		  //Fenster neu zeichnen
		  repaint();
		} else {
		  //Altes Rechteck löschen
		  if (r.x != -1) {
			g.drawRect(r.x, r.y, r.width, r.height);
		  }
		  //Grafikkontext löschen
		  g.dispose();
		}
	  } else if (!viewportstack.empty()) {
		viewport = (Rectangle)viewportstack.pop();
		repaint();
	  }
	}

	//Die folgenden Methoden werden hier nicht benutzt, sind aber
	//erforderlich, um die Interfaces zu implementieren.
	public void mouseClicked(MouseEvent event) {}
	public void mouseEntered(MouseEvent event) {}
	public void mouseExited(MouseEvent event) {}
	public void mouseMoved(MouseEvent event) {}
  }

  /**
   * Diese Methode überprüft, ob die aktuellen Mauskoordinaten 
   * innerhalb des eines Rechtecks von 2 Pixeln um den Startpunkt der
   * Dragoperation liegen. Dieser "Zitterfilter" sorgt dafür, daß 
   * nicht zu kleine Bereiche markiert werden (insbesondere keine
   * einzelnen Mausklicks ohne Bewegung).
   */
  private boolean dragIgnore(Point dragpoint, int actx, int acty)
  {
	final int LIMIT = 2;
	return Math.abs(dragpoint.x - actx) <= LIMIT &&
	       Math.abs(dragpoint.y - acty) <= LIMIT;
  }
}


