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


// @(#)PlayerPanel.java, 1.75, 12/05/96





package marimba.gui;





import java.io.*;


import java.net.*;


import java.awt.*;


import java.util.*;





/**


 * A Panel for displaying Presentations. This class takes care of all


 * the event distribution and paiting for the presentation.


 *


 * @author	Arthur van Hoff


 * @version 	1.75, 12/05/96


 */


public class PlayerPanel extends Panel implements WidgetConstants {


    final static Event marker = new Event(null, 0, null);


    final static BackingStore back = new BackingStore();


    public static boolean GFIX = NT;





    protected Presentation presentation;


    protected int nwidgets;


    protected PopupWidget widgets[] = new PopupWidget[4];





    Object context;


    boolean showing;


    Image iconImg;


    Widget focusWidget;


    Widget mouseWidget;


    Widget trackWidget;


    boolean firstUpdate;


    PlayerEventHandler evtHandler = new PlayerEventHandler(this, true);





    /**


     * Constructor


     */


    public PlayerPanel() {


    }





    /**


     * Constructor with context


     */


    public PlayerPanel(Object context) {


	this.context = context;


    }





    /**


     * True when editing.


     */


    public boolean editing() {


	return false;


    }





    /**


     * Get the current presentation.


     */


    public Presentation getPresentation() {


	return presentation;


    }





    /**


     * Get the player's context.


     */


    public Object getContext() {


	return context;


    }





    /**


     * Set the player's context.


     */


    public void setContext(Object context) {


	this.context = context;


    }





    /**


     * Get the default button. Return null when no default button exists.


     * If more default buttons exist, the first one found is returned.


     */


    public Widget getDefaultButton() {


	return getDefaultButton(widgets[nwidgets-1]);


    }





    /**


     * Search for default button recursive.


     */


    protected Widget getDefaultButton(Widget w) {


	if (!w.visible) {


	    return null;


	}


	if ((w instanceof CommandButtonWidget) && ((CommandButtonWidget)w).isDefault()) {


	    return w;


	}


	for (int i = 0; i < w.nwidgets; i++) {


	    Widget b = getDefaultButton(w.widgets[i]);


	    if (b != null) {


		return b;


	    }


	}


	return null;


    }





    /**


     * Mouse location. This will return null if the


     * mouse is not inside the window.


     */


    public Point mouseLocation() {


	if (evtHandler.inside) {


	    return new Point(evtHandler.x, evtHandler.y);


	}


	return null;


    }





    /**


     * Reset everything.


     */


    void resetPlayer() {


	if (showing) {


	    for (int i = nwidgets ; i-- > 0 ; ) {


		Widget w = widgets[i];


		w.stopAll();


		w.visible = false;


	    }


	}


	while (nwidgets > 0) {


	    remove(widgets[nwidgets-1]);


	}





	presentation = null;





	// initialize


	focusWidget = null;


	mouseWidget = null;


    }





    /**


     * Set the presentation.


     */


    public void setPresentation(Presentation p) {


	if (p == presentation) {


	    return;


	}





	// Reset everything


	resetPlayer();


	presentation = p;


	iconImg = null;





	invalidate();


	if (presentation != null) {


	    presentation.x = presentation.y = 0;


	    fit();


	    setBackground(presentation.background);


	    PopupWidget pw = new PopupWidget();


	    pw.lineMode = NONE;


	    pw.fillMode = NONE;


	    pw.wFont = getFont();


	    if (pw.wFont == null) {


		pw.wFont = Widget.defaultFont;


	    }


	    pw.wForeground = getForeground();


	    if (pw.wForeground == null) {


		pw.wForeground = Color.black;


	    }


	    pw.wBackground = getBackground();


	    if (pw.wBackground == null) {


		pw.wBackground = Color.gray;


	    }


	    pw.wHilite = Widget.defaultHilite;


	    pw.add(p);


	    add(pw);


	    presentation.waitForPattern();


	    presentation.show();


	    firstUpdate = true;


	    invalidate();


	}





	validate();


	repaint();


    }





    /**


     * Replace the current presentation.


     */


    protected void replacePresentation(Presentation p) {


	if (p == presentation) {


	    return;


	}





	// Reset everything


	resetPlayer();


	presentation = p;


	iconImg = null;





	invalidate();


	if (presentation != null) {


	    //setBackground(presentation.background);


	    PopupWidget pw = new PopupWidget();


	    pw.lineMode = NONE;


	    pw.fillMode = NONE;


	    pw.wFont = getFont();


	    if (pw.wFont == null) {


		pw.wFont = Widget.defaultFont;


	    }


	    pw.wForeground = getForeground();


	    if (pw.wForeground == null) {


		pw.wForeground = Color.black;


	    }


	    pw.wBackground = getBackground();


	    if (pw.wBackground == null) {


		pw.wBackground = Color.gray;


	    }


	    pw.wHilite = Widget.defaultHilite;


	    pw.add(p);


	    add(pw);


	    presentation.waitForPattern();


	    firstUpdate = true;


	    invalidate();


	}


	validate();


	repaint();


    }





    /**


     * Fit the presentation in the player.


     */


    public void fit() {


	Container p = getParent();


	if (p instanceof Window) {


	    Window win = (Window)p;


	    win.pack();


	    if (PC) {


		// This is needed to resize frames correctly on the PC


		try {


		    Thread.sleep(100);


		} catch (InterruptedException e) {


		}


		win.resize(win.preferredSize());


	    }


	}


	if (p instanceof Frame) {


	    Frame frm = (Frame)p;


	    frm.setResizable(presentation.resizable);





	    if (presentation.title != null) {


		frm.setTitle(presentation.title);


	    }


	    if (presentation.icon != null) {


		iconImg = presentation.getImage(presentation.icon);


		if (iconImg != null) {


		    if (getToolkit().prepareImage(iconImg, -1, -1, this)) {


			frm.setIconImage(iconImg);


		    }


		}


	    }


	} else if (p instanceof Dialog) {


	    Dialog dlg = (Dialog)p;


	    if (presentation.title != null) {


		dlg.setTitle(presentation.title);


	    }


	}


    }





    /**


     * Set the icon image once the image has arrived.


     */


    public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) {


	if ((img == iconImg) && ((flags & ALLBITS) != 0)) {


	    ((Frame)getParent()).setIconImage(iconImg);


	}


	return super.imageUpdate(img, flags, x, y, width, height);


    }





    /**


     * Preferred Size


     */


    public Dimension preferredSize() {


	Presentation p = presentation;


	return (p != null) ? p.size() : size();


    }





    /**


     * Minimum Size.


     */


    public Dimension minimumSize() {


	return preferredSize();


    }





    /**


     * Layout this panel.


     */


    public void layout() {


	if (nwidgets > 0) {


	    Dimension d = size();


	    widgets[0].reshape(0, 0, d.width, d.height);


	}


    }





    /**


     * Paint the editor overlay.


     */


    public void paintOverlay(Graphics g, int x, int y, int width, int height) {


    }





    /**


     * Paint the panel


     */


    public void paint(Graphics g) {


	// On the first update paint the background directly to


	// the screen. This makes the downloading of fonts on


	// Solaris a little less obvious.





	if (firstUpdate) {


	    firstUpdate = false;


	    presentation.paint(g);


	    presentation.firstFocus();


	}





	Rectangle r = null;


	if (!PC) {


	    r = g.getClipRect();


	}


	if (r != null) {


	    if (GFIX) {


		// This is needed to make updates work on the PC if


		// the "drag window content" option is selected.


		try {


		    g = getGraphics();


		} catch (NullPointerException e) {


		    // This happens on the PC once the window is disposed


		    return;


		}


		try {


		    Dimension d = size();


		    update(g, null, false, 0, 0, d.width, d.height);


		} finally {


		    g.dispose();


		}


	    } else {


		evtHandler.repaint(0, null, r.x, r.y, r.width, r.height);


	    }


	} else {


	    Dimension d = size();


	    if (GFIX) {


		// This is needed to make updates work on the PC if


		// the "drag window content" option is selected.


		try {


		    g = getGraphics();


		} catch (NullPointerException e) {


		    // This happens on the PC once the window is disposed


		    return;


		}


		try {


		    update(g, null, false, 0, 0, d.width, d.height);


		} finally {


		    g.dispose();


		}


	    } else {


		evtHandler.repaint(0, null, 0, 0, d.width, d.height);


	    }


	}


    }





    /**


     * Update the panel


     */


    public void update(Graphics g) {


	Rectangle r = g.getClipRect();





	if (r != null) {


	    if (GFIX) {


		update(g, null, false, r.x, r.y, r.width, r.height);


	    } else {


		evtHandler.repaint(0, null, r.x, r.y, r.width, r.height);


	    }


	} else {


	    Dimension d = size();


	    if (GFIX) {


		update(g, null, false, 0, 0, d.width, d.height);


	    } else {


		evtHandler.repaint(0, null, 0, 0, d.width, d.height);


	    }


	}


    }





    void update(Widget w, boolean optimize, int x, int y, int width, int height) {


	if (GFIX) {


	    super.repaint(0, x, y, width, height);


	} else {


	    update(null, w, optimize, x, y, width, height);


	}


    }


    /**


     * Update the panel


     */


    void update(Graphics g, Widget w, boolean optimize, int x, int y, int width, int height) {


	//System.out.println("UPDATE: " + x + "," + y + "," + width + "x" + height + " - " + w);





	synchronized(back) {


	    // Make sure the presentation has been started


	    if (!showing) {


		if (!isShowing()) {


		    return;


		}


		start();


	    }





	    Graphics gc = back.getGraphics(this);


	    try {


		gc.clipRect(x, y, width, height);





		if (w != null) {


		    int px = 0;


		    int py = 0;


		    if (!(w instanceof PopupWidget)) {


			for (Widget p = w.parent ; p != null ; p = p.parent) {


			    px += p.x + p.tx;


			    py += p.y + p.ty;


			    if (p instanceof PopupWidget) {


				break;


			    }


			}


		    }





		    Graphics gc2 = gc.create();


		    try {


			gc2.translate(px, py);


			if ((x == px + w.x) && (y == py + w.y) && (width == w.width) && (height == w.height)) {


			    if (optimize) {


				//System.out.println("OPTIMIZE: " + w);


				if (w.directGraphics && g == null) {


				    g = getGraphics();


				    try {


					g.clipRect(x, y, width, height);


					g.translate(px, py);


					w.updateAll(g);


					return;


				    } finally {


					g.dispose();


				    }


				} else if (back.validArea(w, x, y, width, height)) {


				    w.updateAll(gc2);


				} else {


				    //System.out.println("PAINTALL1: " + w);


				    back.addArea(w, x, y, width, height);


				    w.paintAll(gc2, x - px, y - py, width,


					       height);


				}


			    } else {


				//System.out.println("PAINTALL1: " + w);


				back.addArea(w, x, y, width, height);


				w.paintAll(gc2, x - px, y - py, width, height);


			    }


			} else {


			    //System.out.println("PAINTALL2: " + w);


			    back.addArea(w, x, y, width, height);


			    w.paintAll(gc2, x - px, y - py, width, height);


			}


		    } finally {


			gc2.dispose();


		    }


		} else {


		    //System.out.println("\npaint: " + new Rectangle(x, y, width, height));


		    back.clearArea(x, y, width, height);


		    gc.setColor(getBackground());


		    gc.fillRect(x, y, width, height);


		    for (int i = 0 ; i < nwidgets ; i++) {


			widgets[i].paintAll(gc, x, y, width, height);


		    }


		}


		paintOverlay(gc, x, y, width, height);


	    } finally {


		gc.dispose();


	    }





	    // Render the back buffer onto the screen


	    if (g == null) {


		try {


		    g = getGraphics();


		    try {


			g.clipRect(x, y, width, height);


			back.paint(g);


		    } finally {


			g.dispose();


		    }


		} catch (NullPointerException e) {


		    // This happens on the PC when the panel is


		    // not visible.


		}


	    } else {


		g.clipRect(x, y, width, height);


		back.paint(g);


	    }


	}


    }





    /**


     * Repaint everything.


     */


    public void repaint() {


	if (GFIX) {


	    super.repaint();


	} else {


	    Dimension d = size();


	    evtHandler.repaint(0, null, 0, 0, d.width, d.height);


	}


    }





    /**


     * Repaint an area of the screen.


     */


    public void repaint(long tm, int x, int y, int width, int height) {


	evtHandler.repaint(tm, null, x, y, width, height);


    }





    /**


     * Repaint this widget.


     */


    void repaint(long tm , Widget w, Widget c, int x, int y, int width, int height) {


	//System.out.println("REPAINT: " + new Rectangle(x, y, width, height));


	int i = 0;


	while ((i < nwidgets) && (widgets[i++] != c));


	while (i < nwidgets) {


	    Widget ww = widgets[i++];


	    if (ww.visible && ww.overlap(x, y, width, height)) {


		evtHandler.repaint(tm, null, x, y, width, height);


		return;


	    }


	}


	evtHandler.repaint(tm, w, x, y, width, height);


    }





    /**


     * Add a widget to the player.


     */


    synchronized void add(PopupWidget w) {


	if (w.player != null) {


	    w.player.remove(w);


	}


	


	if (nwidgets == widgets.length) {


	    PopupWidget newwidgets[] = new PopupWidget[nwidgets * 2];


	    System.arraycopy(widgets, 0, newwidgets, 0, nwidgets);


	    widgets = newwidgets;


	}


	widgets[nwidgets++] = w;


	w.player = this;


	w.initAll();


	if (showing) {


	    w.visible = true;


	    w.startAll();


	    w.repaint();


	}


	


	if (mouseWidget != null) {


	    mouseWidget = w;


	}


    }





    /**


     * Remove a widget from the player.


     */


    synchronized void remove(PopupWidget w) {


	for (int i = 0 ; i < nwidgets ; i++) {


	    if (widgets[i] == w) {


		System.arraycopy(widgets, i+1, widgets, i, --nwidgets - i);


		if (mouseWidget == w) {


		    mouseWidget = w.parent;


		}


		if (showing) {


		    w.stopAll();


		    w.visible = false;


		}


		w.destroyAll();


		w.player = null;


		w.parent = null;


		repaint(0, w.x, w.y, w.width, w.height);


		return;


	    }


	}


    }





    /**


     * Call this when the panel is first shown on the screen.


     */


    public void start() {


	if (!showing) {


	    showing = true;


	    for (int i = nwidgets ; i-- > 0 ; ) {


		Widget w = widgets[i];


		w.visible = true;


		w.startAll();


		w.validate();


	    }


	}


    }





    /**


     * Call this when the panel is taken from the screen.


     */


    public void stop() {


	if (showing) {


	    showing = false;


	    for (int i = nwidgets ; i-- > 0 ; ) {


		Widget w = widgets[i];


		w.stopAll();


		w.visible = false;


	    }


	}


    }





    /**


     * Call this when the panel is disposed.


     */


    public void destroy() {


	while (nwidgets > 0) {


	    remove(widgets[nwidgets-1]);


	}


    }





    /**


     * Clear all widgets owned by a widget


     */


    synchronized void clearPopups(Widget w) {


	for (int i = nwidgets ; i-- > 0 ;) {


	    if (widgets[i].parent == w) {


		remove(widgets[i]);


	    }


	}


    }





    /**


     * Clear all popups.


     */


    public synchronized void clearPopups() {


	while (nwidgets > 1) {


	    remove(widgets[nwidgets-1]);


	}


    }





    /**


     * Return the current focus.


     */


    public Widget currentFocus() {


	return focusWidget;


    }





    /**


     * Set the focus.


     */


    void requestFocus(Widget w) {


	// do nothing if w already has the focus or if w is not shown


	if ( focusWidget == w || (w != null && !w.isShowing()) ) {


	    return;


	}





	if (focusWidget != null) {


	    if (evtHandler.focus) {


		focusWidget.focus = false;


		focusWidget.handleEvent(new Event(focusWidget, Event.LOST_FOCUS, null));


	    }


	}


	focusWidget = w;


	if (w != null) {


	    // This is wrong. We should always call requestFocus, but on


	    // the PC it raises the window.


	    if (evtHandler.focus) {


		if (!(getParent() instanceof PlayerFrame)) {


		    requestFocus();


		}


		w.focus = true;


		w.handleEvent(new Event(w, Event.GOT_FOCUS, null));


	    }


	}


    }





    /**


     * Focus on the first widget.


     */


    public void firstFocus() {


	Presentation p = presentation;


	if (p != null) {


	    p.firstFocus();


	}


    }





    /**


     * Allow the editor to filter events.


     */


    boolean filterEvent(Event evt) {


	return false;


    }





    /**


     * Locate the top most widget located at position x,y


     * in global coordinates.


     */


    public Widget locateWidget(int x, int y) {


	Widget w = null;


	if (nwidgets > 0) {


	    if ((w = widgets[nwidgets-1].locateWidget(x, y)) == null) {


		w = widgets[nwidgets-1];


	    }


	}


	return w;


    }





    /**


     * Locate the widget for this event.


     */


    Widget track(Widget w) {


	if (trackWidget != w) {


	    if (trackWidget != null) {


		widgetEvent(trackWidget, new Event(null, System.currentTimeMillis(),


			Event.MOUSE_EXIT, evtHandler.x, evtHandler.y, 0, 0));


	    }


	    trackWidget = w;


	    if (trackWidget != null) {


		widgetEvent(trackWidget, new Event(null, System.currentTimeMillis(),


			Event.MOUSE_ENTER, evtHandler.x, evtHandler.y, 0, 0));


	    }


	}


	return w;


    }





    /**


     * Send an event to a widget.


     */


    boolean widgetEvent(Widget w, Event evt) {


	if (w == null) {


	    return false;


	}





	int x = 0;


	int y = 0;


	for (Widget p = w ; p != null ; p = p.parent) {


	    x += p.x + p.tx;


	    y += p.y + p.ty;


	    if (p instanceof PopupWidget) {


		break;


	    }


	}





	evt.translate(-x, -y);


	evt.target = w;


	return w.postEvent(evt);


    }





    /**


     * Post an event to this presentation.


     */


    public boolean handleEvent(Event evt) {


	if (presentation == null) {


	    return super.handleEvent(evt);


	}


	if (evt.target == this) {


	    switch (evt.id) {


	      case Event.GOT_FOCUS:


		if (focusWidget != null) {


		    if (focusWidget.isShowing()) {


			focusWidget.focus = true;


			focusWidget.handleEvent(new Event(focusWidget, Event.GOT_FOCUS, null));


		    } else {


			focusWidget = null;


		    }


		}


		return true;





	      case Event.LOST_FOCUS:


		if (focusWidget != null) {


		    focusWidget.focus = false;


		    focusWidget.handleEvent(new Event(focusWidget, Event.LOST_FOCUS, null));


		}


		return true;


	    }





	    if (filterEvent(evt)) {


		return true;


	    }





	    switch (evt.id) {


	      case Event.MOUSE_ENTER: {


		track(locateWidget(evt.x, evt.y));


		Container parent = getParent();


		if (parent instanceof Frame) {


		    ((Frame)parent).setCursor(presentation.cursor);


		}


		break;


	      }





	      case Event.MOUSE_EXIT:


		track(null);


		break;


		


	      case Event.MOUSE_MOVE: 


		return widgetEvent(track(locateWidget(evt.x, evt.y)), evt);





	      case Event.MOUSE_DOWN:


		return widgetEvent(mouseWidget = track(locateWidget(evt.x, evt.y)), evt);


		    


	      case Event.MOUSE_DRAG:


		return widgetEvent(mouseWidget, evt);


		  


 	      case Event.MOUSE_UP: {


		boolean result = widgetEvent(mouseWidget, evt);


		mouseWidget = null;


		track(locateWidget(evtHandler.x, evtHandler.y));


		return result;


	      }





	      case REST_EVENT:


		track(locateWidget(evtHandler.x, evtHandler.y));


		if ((trackWidget != null) && trackWidget.isShowing()) {


		    return widgetEvent(trackWidget, evt);


		}


		break;





	      case WAKE_EVENT:


		return widgetEvent(trackWidget, evt);





	      case Event.KEY_RELEASE:


	      case Event.KEY_ACTION:


	      case Event.KEY_PRESS:


	      case Event.KEY_ACTION_RELEASE:


		if ((focusWidget != null) && (evt.target != focusWidget) && focusWidget.isShowing()) {


		    evt.target = focusWidget;


		    return focusWidget.postEvent(evt);


		}


		return false;


	    }


	} else if ((evt.evt != marker) && (evt.target instanceof Component)) {


	    // Check if this is an event from an AWT component


	    // inside this presentation


	    Component comp = (Component)evt.target;


	    while ((comp != null) && (comp.getParent() != this)) {


		comp = comp.getParent();


	    }


	    if (comp != null) {


		// Find the corresponding AWTWidget


		AWTWidget w = findAWTWidget(presentation, comp);


		if (w != null) {


		    // Translate the event


		    for (Widget p = w ; p != null ; p = p.parent) {


			evt.translate(-(p.x + p.tx), -(p.y + p.ty));


			if (p instanceof PopupWidget) {


			    break;


			}


		    }


		    // Mark the event to avoid an infinite loop


		    evt.evt = marker;


		    return w.postEvent(evt);


		}


	    }


	}


	return super.handleEvent(evt);


    }





    AWTWidget findAWTWidget(Widget c, Component comp) {


	if (c instanceof AWTWidget) {


	    AWTWidget w = (AWTWidget)c;


	    if (w.comp == comp) {


		return w;


	    }


	}


	for (int i = c.nwidgets ; i-- > 0 ;) {


	    AWTWidget w = findAWTWidget(c.widgets[i], comp);


	    if (w != null) {


		return w;


	    }


	}


	return null;


    }





    /**


     * Put an event in the queue.


     */


    public boolean postEvent(Event evt) {


	return ((evt.target == this) && evtHandler.postEvent(evt)) || super.postEvent(evt);


    }





    /**


     * Flush the backing store


     */


    public static void flush() {


	back.flush();


    }





    /**


     * Debugging only.


     */


    public void list(PrintStream out, int indent) {


	super.list(out, indent);


	Presentation p = presentation;


	if (p != null) {


	    for (int i = 0 ; i < nwidgets ; i++) {


		widgets[i].list(out, indent+1);


	    }


	}


    }


}


