package org.jcon.ui;

import org.jcon.util.Hub;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.Vector;
import javax.swing.JDialog;
import javax.swing.JFrame;

/**
 * Manages windows by providing a variety of window
 * related services. The most important is taming the
 * need to provide a Frame to instantiate a JDialog. This
 * is immature awt design since it makes implementations
 * too complex. An example is the need for any class to
 * call a library method to display an error in a JDialog.
 * <p>
 * The awt bug of the proper frame not receiving
 * the focus after a dialog is closed is also fixed.
 * However the awt bug of the entire app losing focus is
 I not fixed.
 * <p>
 * To use the services: <pre>
 * - Call WindowMgr.registerFrame(frame) just after
 *      creating a frame. Do this for all your frames.
 *      Or use createRegisteredFrame().
 * - Call WindowMgr.createDialog(boolean) to create all
 *      your dialogs. </pre>
 * <p>
 * This class is thread safe.
 *
 * @author Jack Harich
 */
public class WindowMgr implements WindowListener, Runnable {

//---------- Private Fields ------------------------------
// The vector is maintained in activation order
// with the last element being the last Window activated.
private static Vector regWindows = new Vector();

private static JFrame  dummyFrame;
private static WindowMgr windowMgr = new WindowMgr();
private static boolean debug = false;
private static boolean sendingGlobalEvents = false;
private static boolean systemHasFocus = false;

//---------- WindowListener Implementatons ---------------
public void windowClosing(WindowEvent evt) { }

public void windowClosed(WindowEvent evt) {
    Window window = evt.getWindow();
    unregisterWindow(window);
    //print(".windowClosed() - frame title = " + frame.getTitle());
    
    if (evt.getWindow() instanceof JDialog) {
       // Must be a dialog we created. See run() doc.
        new Thread(windowMgr).start();
    }
}
public void windowDeiconified(WindowEvent evt) {
    RegWindow regWindow = getRegWindow(evt.getWindow());
    regWindow.setIconified(false);
}
public void windowIconified(WindowEvent evt) {
    RegWindow regWindow = getRegWindow(evt.getWindow());
    regWindow.setIconified(true);
}
public void windowOpened(WindowEvent evt) { }

public void windowActivated(WindowEvent evt) {
    Window window = evt.getWindow();
    RegWindow regWindow = getRegWindow(window);
    regWindow.setActivated(true);
    // Put regWindow at end of collection
    synchronized (this) {
        regWindows.removeElement(regWindow);
        regWindows.addElement(regWindow);
    }
    //print(".windowActivated() - frame title = " + frame.getTitle());
    checkSystemActivated();
}
public void windowDeactivated(WindowEvent evt) {
    RegWindow regWindow = getRegWindow(evt.getWindow());
    regWindow.setActivated(false);
    //print(".windowDeactivated() - frame title = " + frame.getTitle());
    checkSystemDeactivated();
}
//---------- Runnable Implementation ---------------------
/**
 * This is called automatically after the dialog is closed.
 * It will put the focus on the last active Frame.
 * <p>
 * A thread and 100ms sleep is used to circumvent the fact
 * that the awt thread may have not yet allowed other
 * frames to fire their window closed event, or other such
 * behavior. The client should never call this method.
 */
public void run() {
    try {
        Thread.currentThread().sleep(100); // Let awt thread close window
    } catch(InterruptedException ex) { }

    // Request focus on last active Frame
    JFrame frame = getLastActiveFrame();
    if (frame != null) {
        frame.requestFocus();
        print(" - requested focus on " + frame.getTitle());
    }
}
//---------- Properties ----------------------------------
/**
 * Starts or stops the sending of global events. These are
 * sent to Hub.getGlobalRouter().
 * <p>
 * The event name "SystemActivated" is sent when the first
 * window in the system gains focus. "SystemDeactivated" is
 * sent when no window in the system has the focus any more.
 * This is designed to allow a status bar, for example, to
 * show on SystemActivated and hide on SystemDeactivated,
 * so that the status bar doesn't bother other systems.
 */
public synchronized static void setSendingGlobalEvents(boolean sending) {
    sendingGlobalEvents = sending;
}
public static boolean isSendingGlobalEvents() {
    return sendingGlobalEvents;
}
//---------- Public Methods ------------------------------
/**
 * Registers a Frame so that the WindowMgr's services can
 * use the Frame. This should be done before the Frame is
 * first shown. There is no need to unregister a Frame.
 * There is no effect if the Frame is already registered.
 */
public synchronized static void registerFrame(Frame frame) {
    if (getRegWindow(frame) != null) {
        print(".registerFrame() " +
            "- The Frame is already registered: " + frame.getTitle());
    } else {
        frame.addWindowListener(windowMgr);
        // Add to regWindows
        windowMgr.createRegWindow(frame);
    }
}
public synchronized static void registerFrame(JFrame frame) {
    if (getRegWindow(frame) != null) {
        print(".registerFrame() " +
            "- The Frame is already registered: " + frame.getTitle());
    } else {
        frame.addWindowListener(windowMgr);
        // Add to regWindows
        windowMgr.createRegWindow(frame);
    }
}
/**
 * Returns a registered Frame with no title. This is
 * preferred to registerFrame(), which may be forgotten.
 */
public synchronized static JFrame createRegisteredFrame() {
    JFrame frame = new JFrame();
    registerFrame(frame);
    return frame;
}
/**
 * Unregisters a Window, which completely removes it from
 * WindowMgr's services. This is called automatically when
 * a registered window is closed. This method is provided
 * for symmetry and the rare occasions where useful.
 */
private synchronized static void unregisterWindow(Window window) {
    RegWindow regWindow = getRegWindow(window);
    if (regWindow != null) {
        regWindow.getWindow().removeWindowListener(windowMgr);
        regWindows.removeElement(regWindow);
    }
}
/**
 * Returns a JDialog using the active Frame. If no Frame is
 * active then a dummy Frame is used. Also registers the Dialog.
 */
public synchronized static JDialog createDialog(boolean isModal) {
    Frame frame = getLastActiveFrame();
    if (frame == null) {
        if (dummyFrame == null) dummyFrame = new JFrame("Dummy");
        frame = dummyFrame;
    }
    JDialog dialog = new JDialog(frame, isModal);
    dialog.addWindowListener(windowMgr);
    
    // Add to regWindows
    windowMgr.createRegWindow(dialog);
    
    return dialog;
}
/**
 * Returns the last active Frame or null if none. This
 * method is used internally but is public for other use,
 * such as when creating a FileDialog.
 *
 * The "last active Frame" is the last frame that was
 * activated, is visible and is not iconified. Thus it is
 * possible that, even if several frames are registered,
 * none are the last active Frame. Registered Dialogs are
 * ignored.
 */
public synchronized static JFrame getLastActiveFrame() {
    if (regWindows.isEmpty()) {
        return null;
    } else {
        // Search starting at end of Vector
        for (int i = regWindows.size() - 1; i >= 0; i--) {
            RegWindow regWindow = (RegWindow)regWindows.elementAt(i);
            if (regWindow.isVisible() &&
              ! regWindow.isIconified() &&
               (regWindow.getWindow() instanceof JFrame) ) {
                    return (JFrame)regWindow.getWindow();
            }
        }
        return null;
    }
}
/**
* Returns the last active Window or null if none. This
* is useful for positioning a window, such a Dialog,
* relative to the currently active Window.
*/
public synchronized static Window getLastActiveWindow() {
    if (regWindows.isEmpty()) {
        return null;
    } else {
        // Search starting at end of Vector
        for (int i = regWindows.size() - 1; i >= 0; i--) {
print(".getLastActiveWindow() - " + i);        
            RegWindow regWindow = (RegWindow)regWindows.elementAt(i);
            if (regWindow.isVisible() &&
              ! regWindow.isIconified() ) {
                    return regWindow.getWindow();
            }
        }
        return null;
    }
}
/**
 * Returns a Vector of the currently registered Windows. This
 * is useful for typical window behavior such as tile,
 * cascade, cycle, etc or application specific behavior.
 * <p>
 * Currently we have not implemented typical window behavior
 * in this class. This is probably best implemented in
 * another class, such as WindowMgrServices, to keep the
 * responsibilities of this class minimal.
 */
public synchronized Vector getRegisteredWindows() {
    return (Vector)regWindows.clone();
}
//---------- Private Methods -----------------------------
// Send event if previously was unactivated
private synchronized static void checkSystemActivated() {
    if (! sendingGlobalEvents) return;
    if (systemHasFocus) return;

    // SystemActivated event
    systemHasFocus = true;
    fireGlobalMessage("SystemActivated");
}
private synchronized static void checkSystemDeactivated() {
    if (! sendingGlobalEvents) return;

    // Fire event if no window has focus
    for (int i = regWindows.size(); --i >= 0; ) {
        RegWindow regWindow = (RegWindow)regWindows.elementAt(i);
        // *** A kludge, must be a better way
        if (regWindow.isActivated()) {
            //print(".checkSystemDeactivated() - frame " +
            //    regFrame.getFrame().getTitle() + " is activated");
            return;
        }
    }
    // None activated, system has lost focus
    systemHasFocus = false;
    fireGlobalMessage("SystemDeactivated");
}
private static void fireGlobalMessage(String eventName) {
    print("fireGlobalMessage() - " + eventName);
    Hub.getGlobalRouter().fire(eventName);
}
// Returns the first RegWindow containing widnow, null if none
private static RegWindow getRegWindow(Window window) {
    for (int i = 0; i < regWindows.size(); i++) {
        RegWindow regWindow = (RegWindow)regWindows.elementAt(i);
        if (window == regWindow.getWindow()) return regWindow;
    }
    return null;
}
private RegWindow createRegWindow(Window window) {
    RegWindow regWindow = new RegWindow(window);
    regWindows.addElement(regWindow);    
    return regWindow;
}
//--- Std
private static void print(String text) {
    if (debug) System.out.println("WindowMgr" + text);
}
//========== Inner Classes ===============================

// All this because of no Window.isIconified()
// It is impossible to determine from documentation if
// Component.isShowing() would do this.

class RegWindow { // Short for "Registered Window"

private Window  window;
private boolean isIconified;
private boolean isActivated;

RegWindow(Window window) {
    this.window = window;
}
Window getWindow() {
    return window;
}
boolean isVisible() {
    return window.isVisible();
}
//----- isIconified
void setIconified(boolean isIconified) {
    this.isIconified = isIconified;
}
boolean isIconified() {
    return isIconified;
}
// This is needed since we have no Component.hasFocus()
// hasFocus() is due in Java 1.2
//----- isActivated
void setActivated(boolean activated) {
    isActivated = activated;
}
boolean isActivated() {
    return isActivated;
}

} // End inner class


} // End outer class
