package javacodebook.gui.tooltip;

import java.awt.*;
import java.awt.event.*;
import java.util.Hashtable;
/**
 * @author Benjamin Rusch
 * 
 * Diese Klasse einfach in den Klassenpfad übernehmen, und über 
 * ToolTipManager.setToolTipText(komponente, "TooltipText hier eingeben")
 * den Tooltiptext setzen.
 */

public class ToolTipManager implements MouseListener, Runnable {
	
	/**
	 * Hintergrundfarbe vom Tooltip
	 */
	private static final Color TOOLTIP_COLOR = new Color(200, 250,200);
	
	/**
	 * Die Anzahl der Millisekunden, die gewartet werden soll bis der Tooltip 
	 * erscheint
	 */
	private static final int PAUSE_TIME = 1000;
	
	/**
	 * Es soll pro Application nur ein ToolTipManger-Objekt geben, daher wird 
	 * dieser hier als Singleton realisiert. 
	 */
	private static ToolTipManager singleton = new ToolTipManager();
	
	/**
	 * Die Komponente über der die Maus gerade positioniert ist.
	 */
	private Component currentComponent;
	
	/**
	 * Zuordnung zwischen Tooltiptext und Komponente
	 */
	private Hashtable componentToTipMap = new Hashtable();
	
	/**
	 * Das Label zeigt den Tooltiptext an.
	 */
	private Label label = new Label();
	
	/**
	 * Dieser Thread stellt fest ob die Maus lange genug über der Komponente war.
	 * Erst dann wird der Tooltiptext angzeigt.
	 */
	private Thread timerThread = new Thread(this);
	
	/**
	 * Das Fenster in dem der Tooltip angezeigt wird
	 */
	private Window window;
	
	/**
	 * Dieser Konstuktor wird bei der Initialisierung vom Attribut singleton 
	 * aufgerufen. Da er private ist wird gewährleistet, das es bei diesem einen 
	 * Aufruf pro Application bleibt.
	 */
	private ToolTipManager() {
		label.setBackground(TOOLTIP_COLOR);
		timerThread.start();
	}

	/**
	* ToolTipFenster wird gebaut.
	*/
	private void createWindow() {
		// Das Frame in dem die currentComponent eingebettet ist wird gefunden.
       	Component top = currentComponent;
       	while (true) {
        	Container parent = top.getParent();
           	if (parent == null) break;
           	top = parent;
       	}
       

		// Das Gefunde Frame wird "owner" vom Tooltip-Window.
       	window = new Window((Frame) top);
        window.add(label, BorderLayout.CENTER);
	}

    /**
     * Lässt Tooltip verschwinden. Wird aufgerufen, wenn Komponente gedrückt, 
     * oder Maus Komponente verlassen hat.
     */
	private void hideTip() {
    	// wird Komponente verlassen oder gedrückt bevor Tooltip angezeigt wurde,
    	// verhindert dieser Interrupt die ungewünschte Darstellung des Tooltips. 
    	timerThread.interrupt();
       
		// Falls Tooltip schon angezeigt, wird er hier unsichtbar gemacht
     	if (window != null) {
        	window.setVisible(false);
       	}
	}


    /**
     * Fügt Tooltip mit angegebenem "toolTipText" einer bestehenden Komponente 
     * "component" hinzu.
     */
   public static void setToolTipText(Component component, String toolTipText) {
       singleton.componentToTipMap.put(component, toolTipText);        
       component.addMouseListener(singleton);
   }


    /**
     * Komponente über der Maus gerade steht wird ermittelt, und Thread der 
     * für die Darstellung der Komponente verantwortlich ist wird aufgeweckt.
     */
	public void mouseEntered(MouseEvent e) {
       	currentComponent = (Component) e.getSource();
       
		// Man benötigt hierbei den Monitor auf das Objekt welches aufgeweckt 
		// werden soll.  Bekommt man durch synchronized
		synchronized (this) {
        	notify();
		}
	}


	/**
	 * Wird aufgerufen wenn Maus Komponente verlässt, Tooltip wird unsichtbar 
	 * gemacht.
	 */
	public void mouseExited(MouseEvent e) {
    	if (e.getSource() == currentComponent) {
			hideTip();
		}
	}


 	/**
	 * Wird aufgerufen wenn Komponente geklickt wird, Tooltip wird unsichtbar 
	 * gemacht.
	 */
 	public void mousePressed(MouseEvent e) {
 		if (e.getSource() == currentComponent) {
        	hideTip();
		}
	}


    // Werden nicht benötigt, müssen aber überschrieben werden, da Sie im 
    // MouseListener-Interface vorhanden sind
    public void mouseReleased(MouseEvent e) {}
   	public void mouseClicked(MouseEvent e) {}


    /**
     * Tooltip wird von übergebener Komponente weggenommen
     */
	public static void removeToolTipText(Component component) {
       singleton.componentToTipMap.remove(component);        
       component.removeMouseListener(singleton);
   	}


    /**
     * Implementierung des Threads. Wartet die PAUSE_TIME ab, und wenn kein 
     * interrupt aufgerufen wurde, wird Tooltip angezeigt.
     */
	public synchronized void run() {
   		while (true) {
			try {
				synchronized (this) {
					wait();
				}
               
               	Thread.sleep(PAUSE_TIME);
                   
               	// Tooltiptext wird ermittelt
               	String tip = (String) componentToTipMap.get(currentComponent);
               	label.setText(tip);
               
				//	Fenster wird ggf. neu gebaut
               	if (window == null) {
                	createWindow();
               	}
               	window.pack();
    

                // Tooltip wird unter die Komponente gesetzt
               	Rectangle bounds = currentComponent.getBounds();
               	Point location = currentComponent.getLocationOnScreen();
               	window.setLocation(location.x, location.y + bounds.height); 
                
               	window.setVisible(true);
			} catch (InterruptedException e) {
    			// Thread wurde unterbrochen        
    		}
       }
   }
}

