// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)PlayerEventHandler.java, 1.23, 11/09/96





package marimba.gui;





import java.awt.*;





import marimba.util.ThreadUtil;





/**


 * An event handler for player events that handles events in a seperate


 * thread. It does motion compression, keeps track of the mouse position,


 * fixes a double click bug, ignores multiple mouse downs, and inserts


 * REST and WAKE events


 *


 * @author	Arthur van Hoff


 * @version 	1.23, 11/09/96


 */


class PlayerEventHandler implements Runnable, WidgetConstants {


    final static int REST_EVENT_DELAY = 1200;


    final static int MOTION_EVENT_DELAY = 20;


    final static long MIN_UPDATE_INTERVAL = 50;


    final static long MAX_UPDATE_INTERVAL = 500;





    final static int REPAINT_EVENT = 3285555;


    


    boolean focus;


    boolean rest;


    boolean inside;


    boolean down;


    boolean compress;


    long tm;


    int count;


    int x, y;


    ThreadGroup evtGroup;


    Thread evtThread;


    Event evtQueue;


    PlayerPanel target;





    long lastEvent;


    long lastUpdate;


    long nextUpdate;





    RectList rects;





    /**


     * Create an event handler for a component.


     */


    PlayerEventHandler(PlayerPanel target, boolean compress) {


	this.evtGroup = Thread.currentThread().getThreadGroup();


	this.target = target;


	this.compress = compress;


    }





    /**


     * Return true if two rectangles should be joined.


     * This is true if the two rectangles intersect, or


     * if area of the union of the two rectangles is


     * significantly larger than the sum of the two areas.


     */


    boolean join(Rect r1, Rect r2) {


	if (r1.intersects(r2.x, r2.y, r2.width, r2.height)) {


	    return true;


	}





	if (r1.w != r2.w) {


	    return false;


	}





	int x1 = Math.min(r1.x, r2.x);


	int x2 = Math.max(r1.x + r1.width, r2.x + r2.width);


	int y1 = Math.min(r1.y, r2.y);


	int y2 = Math.max(r1.y + r1.height, r2.y + r2.height);





	return (2 * ((r1.width * r1.height) + (r2.width * r2.height))) >


	    ((x2 - x1) * (y2 - y1));


    }





    /**


     * Add a rectangle to the list, at a given position.


     */


    synchronized void add(Rect r1) {


	if (rects != null) {


	    for (int i = 0 ; i < rects.nrects ; i++) {


		Rect r2 = rects.rects[i];


		if (join(r1, r2)) {


		    rects.delete(i);


		    


		    if ((r1.w == null) || (r2.w == null)) {


			r1.w = null;


		    } else if (r1.w != r2.w) {


			Widget p = r2.w.parent;


			for (; (p != null) && (p != r1.w) ; p = p.parent);


			if (p != r1.w) {


			    p = r1.w.parent;


			    for (; (p != null) && (p != r2.w) ; p = p.parent);


			    if (p == r2.w) {


				r1.w = r2.w;


			    } else {


				r1.w = null;


			    }


			}


		    }


		    r1.add(r2.x, r2.y, r2.width, r2.height);


		    r1.optimize = false;


		    add(r1);


		    return;


		}


	    }


	} else {


	    rects = new RectList();


	}


	rects.add(r1);


    }





    /**


     * Get an event from the queue. If there are no more


     * events it returns null.


     */


    synchronized Event getEvent() {


	try {


	    while (true) {


		long now = System.currentTimeMillis();





		if (evtQueue == null) {


		    evtThread.setPriority(Thread.NORM_PRIORITY);


		}


		


		if (rects == null) {


		    if (evtQueue == null) {


			if (rest || down || !inside) {


			    // Wait for a while and die if no events arrive.


			    //System.out.println("death wait: " + 5000);


			    wait(5000);


			    if ((evtQueue == null) && (rects == null)) {


				evtThread = null;


				return null;


			    }


			    continue;


			} else {


			    // Wait for the next rest event


			    long delay = (lastEvent + REST_EVENT_DELAY) - now;


			    if (delay <= 0) {


				rest = true;


				return new Event(target, now, REST_EVENT, x, y, 0, 0);


			    }


			    //System.out.println("rest wait: " + delay);


			    wait(delay);


			    continue;


			}


		    }


		} else if (evtQueue == null) {


		    long delay = nextUpdate - now;


		    if (delay <= 0) {


			RectList r = rects;


			// It is time for an update


			nextUpdate = 0;


			lastUpdate = now;


			rects = null;


			return new Event(target, REPAINT_EVENT, r);


		    }


		    // wait for the next update time


		    //System.out.println("update wait: " + delay);


		    wait(delay);


		    continue;





		} else if ((now - lastUpdate) > MAX_UPDATE_INTERVAL) {


		    // Maximum interval between updates was exceeded, catch up.


		    RectList r = rects;


		    nextUpdate = 0;


		    lastUpdate = now;


		    rects = null;


		    return new Event(target, REPAINT_EVENT, r);


		}





		// Get the next event


		Event evt = evtQueue;





		// Delay motion events so they can be collapsed


		if (((evt.id == Event.MOUSE_DRAG) || (evt.id == Event.MOUSE_MOVE)) &&


		    ((evt.when + MOTION_EVENT_DELAY) > now)) {


		    long delay = (evt.when + MOTION_EVENT_DELAY) - now;


		    wait (delay);


		    continue;


		}





		// Remember the time of the last event


		lastEvent = now;





		// Wake up if we are resting


		if (rest) {


		    rest = false;


		    return new Event(target, now, WAKE_EVENT, x, y, 0, 0);


		}





		if (!focus) {


		    switch (evt.id) {


		      case Event.MOUSE_DOWN:


		      case Event.KEY_RELEASE:


		      case Event.KEY_ACTION:


		      case Event.KEY_PRESS:


		      case Event.KEY_ACTION_RELEASE:


			//System.out.println("Focus was lost somehow...");


			return new Event(target, System.currentTimeMillis(),


					 Event.GOT_FOCUS, x, y, 0, 0);


		    }


		} else if (PC) {


		    switch (evt.id) {


		      case Event.KEY_RELEASE:


			if (evt.key == '\t' && evt.controlDown()) {


			    // BUGFIX: Control+Tab is only detected when released


			    evt.id = Event.KEY_PRESS;


			    evt.when = System.currentTimeMillis();


			    return evt;


			}


		    }


		}





		// Return the next event in the queue


		evtQueue = evt.evt;


		return evt;


	    }


	} catch (InterruptedException e) {


	    evtThread = null;


	    return null;


	}


    }





    /**


     * Event handler loop.


     */


    public void run() {


	Event evt;


	while ((evt = getEvent()) != null) {


	    switch (evt.id) {


	      case Event.GOT_FOCUS:


		if (focus) {


		    //System.out.println("Ignore duplicate GOT_FOCUS");


		    continue;


		}


		focus = true;


		break;


	      case Event.LOST_FOCUS:


		if (!focus) {


		    //System.out.println("Ignore duplicate LOST_FOCUS");


		    continue;


		}


		focus = false;


		break;





	      case Event.MOUSE_DOWN:


		if (down) {


		    continue;


		}


		count = (((evt.when - tm) < 250) &&


			 (Math.abs(evt.x - x) < 5) && (Math.abs(evt.y - y) < 5)) ? ++count : 1;


		tm = evt.when;


		down = true;


		break;


	      case Event.MOUSE_UP:


		down = false;


		break;


	      case Event.MOUSE_ENTER:


		inside = true;


		break;


	      case Event.MOUSE_EXIT:


		inside = false;


		break;





	      case REPAINT_EVENT: {


		RectList rects = (RectList)evt.arg;


		for (int i = 0 ; i < rects.nrects ; i++) {


		    Rect r = rects.rects[i];


		    try {


			target.update(r.w, r.optimize, r.x, r.y, r.width, r.height);


		    } catch (ThreadDeath e) {


			throw e;


		    } catch (Throwable e) {


			e.printStackTrace();


		    }


		}


		continue;


	      }


	    }





	    switch (evt.id) {


	      case Event.MOUSE_DOWN:


	      case Event.MOUSE_DRAG:


	      case Event.MOUSE_UP:


		evt.clickCount = count;


	      case Event.MOUSE_MOVE:


	      case Event.MOUSE_ENTER:


	      case Event.MOUSE_EXIT:


		x = evt.x;


		y = evt.y;


		break;


	    }


	    try {


		if (!target.handleEvent(evt)) {


		    Component parent = target.getParent();


		    if (parent != null) {


			parent.postEvent(evt);


		    }


		}


	    } catch (ThreadDeath e) {


		throw e;


	    } catch (Throwable e) {


		e.printStackTrace();


	    }


	}


	//System.out.println("destroy event handler");


    }





    /**


     * Put an event in the queue.


     */


    synchronized boolean postEvent(Event evt) {


	if (evt.target != target) {


	    return false;


	}


	if (evtQueue == null) {


	    evtQueue = evt;


	    evt.evt = null;


	    if (evtThread == null) {


		//System.out.println("create event handler");


		//evtThread = ThreadUtil.forkSystem("PlayerEventHandler", this);


		evtThread = new Thread(evtGroup, this, "PlayerEventHandler");


		evtThread.setPriority(Thread.NORM_PRIORITY+1);


		evtThread.start();


	    } else {


		evtThread.setPriority(Thread.NORM_PRIORITY+1);


		notifyAll();


	    }


	} else if (compress && (evt.id == evtQueue.id) &&


		   ((evt.id == Event.MOUSE_DRAG) || (evt.id == Event.MOUSE_MOVE))) {


	    // Remove repeated MOVE or DRAG events





	    //System.out.println("MOTION COMPRESSION: " + (evtQueue.x - evt.x) + "," + (evtQueue.y - evt.y));


	    evt.when = evtQueue.when;


	    evt.evt = evtQueue.evt;


	    evtQueue = evt;


	    notifyAll();


	} else if (compress && (evtQueue.id == Event.MOUSE_MOVE) && (evt.id == Event.MOUSE_DOWN)) {


	    // Remove MOVE before DOWN


	    evt.evt = evtQueue.evt;


	    evtQueue = evt;


	    notifyAll();


	} else if (compress && (evtQueue.id == Event.MOUSE_DRAG) && (evt.id == Event.MOUSE_UP)) {


	    // Remove DRAG before UP


	    evt.evt = evtQueue.evt;


	    evtQueue = evt;


	    notifyAll();


	} else {


	    Event e = evtQueue;


	    for (; e.evt != null ; e = e.evt);


	    evt.evt = e.evt;


	    e.evt = evt;


	}


	return true;


    }





    synchronized void repaint(long delay, Widget w, int x, int y, int width, int height) {


	long now = System.currentTimeMillis();


	long tm = Math.max(now + delay, lastUpdate + MIN_UPDATE_INTERVAL);


	


	add(new Rect(w, x, y, width, height));





	if ((nextUpdate == 0) || (nextUpdate > tm)) {


	    nextUpdate = tm;





	    if (evtThread == null) {


		//System.out.println("create event handler");


		//evtThread = ThreadUtil.forkSystem("PlayerEventHandler", this);


		evtThread = new Thread(evtGroup, this, "PlayerEventHandler");


		evtThread.start();


	    } else {


		notifyAll();


	    }


	}


    }


}


