/* class Window
*
* Copyright (C) 2001-2003 R M Pitman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package charva.awt;
import java.util.Enumeration;
import java.util.Vector;
import charva.awt.event.AWTEvent;
import charva.awt.event.AdjustmentEvent;
import charva.awt.event.GarbageCollectionEvent;
import charva.awt.event.InvocationEvent;
import charva.awt.event.PaintEvent;
import charva.awt.event.ScrollEvent;
import charva.awt.event.SyncEvent;
import charva.awt.event.WindowEvent;
import charva.awt.event.WindowListener;
/**
* The Window class represents a "toplevel" window with no decorative frame.
* The window is initially invisible; you must use the show() method to make it
* visible.
*/
public class Window extends Container implements Runnable {
public Window(Window owner_) {
_owner = owner_;
init();
}
public Window(Frame owner_) {
_owner = owner_;
init();
}
private void init() {
_term = Toolkit.getDefaultToolkit();
super._layoutMgr = new BorderLayout();
_visible = false;
// The window inherits the colors of its parent if there is one,
// otherwise use the default colors as set in charva.awt.Toolkit.
if (_owner != null) {
super.setForeground(_owner.getForeground());
super.setBackground(_owner.getBackground());
} else {
super.setForeground(Toolkit.getDefaultForeground());
super.setBackground(Toolkit.getDefaultBackground());
}
}
/**
* Return the Window that is the "owner" of this Window.
*/
public Window getOwner() {
return _owner;
}
/**
* Register a WindowListener object for this window.
*/
public void addWindowListener(WindowListener listener_) {
if (_windowListeners == null) _windowListeners = new Vector<WindowListener>();
_windowListeners.add(listener_);
}
/**
* Process window events occurring on this window by dispatching them to
* any registered WindowListener objects.
*/
protected void processWindowEvent(WindowEvent evt_) {
if (_windowListeners == null) return;
Enumeration<WindowListener> e = _windowListeners.elements();
while (e.hasMoreElements()) {
WindowListener wl = (WindowListener) e.nextElement();
switch (evt_.getID()) {
case AWTEvent.WINDOW_CLOSING:
wl.windowClosing(evt_);
break;
case AWTEvent.WINDOW_OPENED:
wl.windowOpened(evt_);
break;
}
}
}
/**
* Returns true if this Window is currently displayed.
*/
public boolean isDisplayed() {
return _term.isWindowDisplayed(this);
}
/**
* Causes this Window to be sized to fit the preferred sizes and layouts of
* its contained components.
*/
public void pack() {
setSize(minimumSize());
super.doLayout(); // call method in Container superclass
}
/**
* Lay out the contained components, draw the window and its contained
* components, and then read input events off the EventQueue and send them
* to the component that has the input focus.
*/
public void show() {
if (_visible) return; // This window is already visible.
_visible = true;
_term.addWindow(this);
/*
* Start the keyboard-reader thread _after_ the first window has been
* displayed, otherwise we get a NoSuchElement exception if the user
* starts typing before the first window is displayed, because the
* _windowList is empty.
*/
_term.startKeyboardReader();
/*
* call doLayout in Container superclass. This will lay out all of the
* contained components (i.e. children) and their descendants, and in
* the process will set the valid flag of all descendants.
*/
super.doLayout(); // call method in Container superclass
this.adjustLocation(); // ensure it fits inside the screen
this.draw(Toolkit.getDefaultToolkit());
/*
* Rather than call Toolkit.sync() directly here, we put a SyncEvent
* onto the SyncQueue. The SyncThread will read it off the SyncQueue
* and then sleep for 50msec before putting the SyncEvent onto the
* EventQueue, from which it will be picked up by the active Window
* (i.e. an instance of this class). The active Window will then call
* Toolkit.sync() directly. This slight delay speeds up the case where
* a window opens and then immediately opens a new window (and
* another...).
*/
SyncQueue.getInstance().postEvent(new SyncEvent(this));
if (_dispatchThreadRunning)
run();
else {
_dispatchThreadRunning = true;
//System.err.println( "Created window dispatch thread." );
Thread dispatchThread = new Thread(this);
dispatchThread.setName("Window event dispatcher");
dispatchThread.start();
/*
* If "charva.script.playback" is defined, we start up a thread for
* playing back the script. Keys from both the script and the
* keyboard will cause "fireKeystroke()" to be invoked. The
* playback thread is started _after_ "addWindow()" is called for
* the first time, to make sure that _windowList is non-empty when
* the playback thread calls "fireKeystroke()".
*/
//startPlayback();
}
}
public void run() {
/*
* Loop and process input events until the window closes.
*/
try {
EventQueue evtQueue = _term.getSystemEventQueue();
for (_windowClosed = false; _windowClosed != true;) {
java.util.EventObject evt = evtQueue.getNextEvent();
/*
* The event object should always be an AWTEvent. If not, we
* will get a ClassCastException.
*/
processEvent((AWTEvent) evt);
} // end FOR loop
} catch (Exception e) {
System.err.println( "Exception in Window.run" );
e.printStackTrace();
// System.exit(1);
System.err.println( "Exiting method (not VM)" );
}
}
/**
* Process an event off the event queue. This method can be extended by
* subclasses of Window to deal with application-specific events.
*/
protected void processEvent(AWTEvent evt_) {
Object source = evt_.getSource();
if (evt_ instanceof AdjustmentEvent){
((Adjustable) source)
.processAdjustmentEvent((AdjustmentEvent) evt_);
}
else if (evt_ instanceof ScrollEvent) {
processScrollEvent((ScrollEvent) evt_);
}
else if (evt_ instanceof PaintEvent) {
processPaintEvent((PaintEvent) evt_);
}
else if (evt_ instanceof SyncEvent) {
_term.sync();
}
else if (evt_ instanceof WindowEvent) {
WindowEvent we = (WindowEvent) evt_;
we.getWindow().processWindowEvent(we);
/*
* Now, having given the WindowListener objects a chance to process
* the WindowEvent, we must check if it was a WINDOW_CLOSING event
* sent to this window.
*/
if (we.getID() == AWTEvent.WINDOW_CLOSING) {
we.getWindow()._windowClosed = true;
/*
* Remove this window from the list of those displayed, and
* blank out the screen area where the window was displayed.
*/
_term.removeWindow(we.getWindow());
_term.blankBox(_origin, _size);
/*
* Now redraw all of the windows, from the bottom to the top.
*/
Vector<Window> winlist = _term.getWindowList();
Window window = null;
synchronized (winlist) {
for (int i = 0; i < winlist.size(); i++) {
window = (Window) winlist.elementAt(i);
window.draw(Toolkit.getDefaultToolkit());
}
if (window != null) // (there may be no windows left)
window.requestFocus();
}
/*
* Put a SyncEvent onto the SyncQueue. The SyncThread will
* sleep for 50 msec before putting it onto the EventQueue,
* from which it will be picked up by the active Window (i.e.
* an instance of this class), which will then call
* Toolkit.sync() directly. This is done to avoid calling
* sync() after the close of a window and again after the
* display of a new window which might be displayed immediately
* afterwards.
*/
if(window != null)
SyncQueue.getInstance().postEvent(new SyncEvent(window));
}
} // end if WindowEvent
else if (evt_ instanceof GarbageCollectionEvent) {
SyncQueue.getInstance().postEvent(evt_);
}
else if (evt_ instanceof InvocationEvent) {
((InvocationEvent) evt_).dispatch();
}
else {
/*
* It is a KeyEvent, MouseEvent, ActionEvent, ItemEvent, FocusEvent
* or a custom type of event.
*/
//System.err.println(evt_);
((Component) source).processEvent(evt_);
}
}
protected void processScrollEvent(ScrollEvent event) {
Scrollable source = (Scrollable) event.getSource();
source.processScrollEvent(event);
requestFocus();
super.requestSync();
}
protected void processPaintEvent(PaintEvent event) {
Component source = (Component) event.getSource();
if (!source.isTotallyObscured()) {
processPaintEvent2(source);
}
}
private void processPaintEvent2(Component source) {
/*
* Unless the affected component is totally obscured by windows
* that are stacked above it, we must redraw its window and all the
* windows above it.
*/
if (!((Component) source).isTotallyObscured()) {
Vector<Window> windowlist = _term.getWindowList();
synchronized (windowlist) {
/*
* We have to draw the window rather than just the affected
* component, because setVisible(false) may have been set
* on the component.
*/
Window ancestor = ((Component) source).getAncestorWindow();
ancestor.draw(Toolkit.getDefaultToolkit());
/*
* Ignore windows that are underneath the window that
* contains the component that generated the PaintEvent.
*/
Window w = null;
int i;
for (i = 0; i < windowlist.size(); i++) {
w = (Window) windowlist.elementAt(i);
if (w == ancestor) break;
}
/*
* Redraw all the windows _above_ the one that generated
* the PaintEvent.
*/
for (; i < windowlist.size(); i++) {
w = (Window) windowlist.elementAt(i);
w.draw(Toolkit.getDefaultToolkit());
}
}
super.requestSync();
}
}
/**
* Hide this window and all of its contained components. This is done by
* putting a WINDOW_CLOSING event onto the queue.
*/
public void hide() {
if (!_visible) {
System.err.println("Trying to hide window " + this
+ " that is already hidden!");
return; // This window is already hidden.
}
_term.removeWindow( this );
_visible = false;
WindowEvent we = new WindowEvent(this, AWTEvent.WINDOW_CLOSING);
_term.getSystemEventQueue().postEvent(we);
}
/**
* Draw all the components in this window, and request the keyboard focus.
* @param toolkit
*/
public void draw(Toolkit toolkit) {
super.draw(toolkit);
requestFocus();
}
/**
* Overrides the method in the Component superclass, because a Window has
* no parent container. Note that we return a COPY of the origin, not a
* reference to it, so that the caller cannot modify our location via the
* returned value.
*/
public Point getLocationOnScreen() {
return new Point(_origin);
}
/**
* A Window component will not receive input focus during keyboard focus
* traversal using Tab and Shift-Tab.
*/
public boolean isFocusTraversable() {
return false;
}
/**
* Adjust the position of the window so that it fits inside the screen.
*/
public void adjustLocation() {
int bottom = _origin.y + getHeight();
if (bottom > _term.getScreenRows())
_origin.y -= bottom - _term.getScreenRows();
if (_origin.y < 0) _origin.y = 0;
int right = _origin.x + getWidth();
if (right > _term.getScreenColumns())
_origin.x -= right - _term.getScreenColumns();
if (_origin.x < 0) _origin.x = 0;
}
public void debug(int level_) {
System.err.println("Window origin=" + _origin + " size=" + _size);
super.debug(1);
}
@SuppressWarnings("unused")
private void startPlayback() {
// System.err.println("Playback disabled (awaiting security exception fix..)");
return;
// String scriptfilename = null;
// BufferedReader scriptReader = null;
// if ((scriptfilename = System.getProperty("charva.script.playback")) == null)
// return;
//
// try {
// scriptReader = new BufferedReader(new FileReader(scriptfilename));
// } catch (FileNotFoundException ef) {
// System.err.println("Cannot open script file \"" + scriptfilename
// + "\" for reading");
// return;
// }
//
// PlaybackThread thr = new PlaybackThread(scriptReader);
// thr.setDaemon(true);
// thr.setName("playback thread");
// thr.start();
}
//====================================================================
// INSTANCE VARIABLES
private Window _owner;
protected Toolkit _term;
private boolean _windowClosed = false;
private Vector<WindowListener> _windowListeners = null;
private static boolean _dispatchThreadRunning = false;
}