/* class Toolkit * * Copyright (C) 2001, 2002, 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 */ /* * Modified Jul 14, 2003 by Tadpole Computer, Inc. * Modifications Copyright 2003 by Tadpole Computer, Inc. * * Modifications are hereby licensed to all parties at no charge under * the same terms as the original. * * Modifications include minor bug fixes, and moving the handling of * endwin() into an atexit(3) function call. */ package charva.awt; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.Vector; import charva.awt.event.AWTEvent; import charva.awt.event.KeyEvent; import charva.awt.event.MouseEvent; /** * The Toolkit class provides the interface to the "ncurses" library * functions. Exceptions and error messages are reported to the * logfile $HOME/charva.log.<p> * * The default colors are white foreground and black background, but * these defaults can be modified by changing the static variables * _defaultForeground and _defaultBackground. * @author * @author Levente S\u00e1ntha */ public abstract class AbstractToolkit implements Runnable { private boolean keyboardReaderRunning = false; private Thread keyboardReader; public EventQueue getSystemEventQueue() { return _evtQueue; } /** * Get the top window of the window stack. */ public Window getTopWindow() { return (Window) _windowList.lastElement(); } /** * Returns true if the specified window is currently displayed. */ public boolean isWindowDisplayed(Window window_) { boolean answer = false; synchronized (_windowList) { for (int i = 0; i < _windowList.size(); i++) { Window w = (Window) _windowList.elementAt(i); if (w == window_) { answer = true; break; } } } return answer; } /** * Processes a keystroke that was pressed in the currently displayed * window. * @param key_ the keystroke that was pressed. If it is less than * 256, it is a ASCII or ISO8859-1 code; otherwise it is a function * key as defined in the "VK_*" values. */ public void fireKeystroke(int key_) { Component currentFocus; synchronized (_windowList) { Window sourcewin = getTopWindow(); /* Get the (non-Container) component within the source window * that generated the keystroke. */ currentFocus = sourcewin.getCurrentFocus(); while (currentFocus instanceof Container) { currentFocus = ((Container) currentFocus).getCurrentFocus(); } } fireKeystroke(key_, currentFocus); } /** * Process a keystroke as if it was pressed while the focus was in the * specified component. * @param key_ the keystroke that was pressed. If it is less than * 256, it is an ASCII or ISO8859-1 code; otherwise it is a function * key as defined in the "VK_*" values. */ public void fireKeystroke(int key_, Component source_) { int id; if (key_ > 255 || key_ < ' ') id = AWTEvent.KEY_PRESSED; else id = AWTEvent.KEY_TYPED; _evtQueue.postEvent(new KeyEvent(key_, id, source_)); } public Component getComponentAt(int x, int y) { Window top_window = getTopWindow(); Point origin = top_window.getLocation(); Component component = null; if (top_window.contains(x, y)) { component = top_window.getComponentAt(x - origin.x, y - origin.y); } return component; } /** * Processes the mouse-click specified by mouse_info. * Note that we disable mouse-click resolution in the ncurses library * by setting "mouse_interval" to 0, because it doesn't work properly. * Instead we do it in this method. */ public void fireMouseEvent(MouseEvent event) { int modifiers = event.getModifiers(); int x = event.getX(); int y = event.getY(); int button = event.getButton(); if (modifiers == MouseEvent.MOUSE_PRESSED) _lastMousePressTime = System.currentTimeMillis(); _evtQueue.postEvent(event); // If this is a button-release within 400 msec of the corresponding // button-press. long current_time = System.currentTimeMillis(); Component component = (Component) event.getSource(); if (modifiers == MouseEvent.MOUSE_RELEASED && current_time - _lastMousePressTime < 400L) { _evtQueue.postEvent(new MouseEvent( component, MouseEvent.MOUSE_CLICKED, x, y, 1, button)); // Check for a double-click. if (current_time - _lastMouseClickTime < 500L) { _evtQueue.postEvent(new MouseEvent( component, MouseEvent.MOUSE_CLICKED, x, y, 2, button)); } _lastMouseClickTime = current_time; } } /** Start a thread that reads characters from the keyboard and * writes them to the event-queue. Make it a daemon thread * so that the program will exit when the main thread ends. */ public void startKeyboardReader() { if (keyboardReader == null) { keyboardReader = new Thread(this); keyboardReader.setDaemon(true); keyboardReader.setName("keyboard reader"); keyboardReader.start(); } } public synchronized boolean isKeyboardReaderRunning() { return keyboardReaderRunning; } public void setKeyboardReaderRunning(boolean keyboardReaderRunning) { this.keyboardReaderRunning = keyboardReaderRunning; } /** * The keyboard-reading thread just reads keystrokes, converts * them to KeyEvent objects, and posts the events onto the EventQueue. */ public void run() { keyboardReaderRunning = true; StringBuffer scriptbuf = new StringBuffer(); try { while (isKeyboardReaderRunning()) { Object event = readKey(); // identify the kind of event (key, mouse) int key = -1; MouseEvent mouseEvent = null; if(event == null) break; else if(event instanceof Integer) key = ((Integer) event).intValue(); else if(event instanceof MouseEvent) mouseEvent = (MouseEvent) event; if(mouseEvent != null) { fireMouseEvent(mouseEvent); } /* Note that if the "kent" key is defined, ncurses returns * VK_ENTER when the ENTER key is pressed; but some terminfo * files don't define the "kent" capability. */ else if (key == '\n' || key == '\r') key = KeyEvent.VK_ENTER; /* Likewise, some versions of curses don't map '\b' to * VK_BACK_SPACE (Solaris at least); this works around * that. (I can't imagine anyone wanting \b to be mapped * to anything else. If they do, then they should set it * up that way in the terminfo database.) */ else if (key == '\b') { key = KeyEvent.VK_BACK_SPACE; } else { fireKeystroke(key); } /* If script recording is on. */ if (_scriptPrintStream != null) { scriptbuf.setLength(0); /* Compute the elapsed time since the last keystroke. */ long current = System.currentTimeMillis(); long elapsed = 1000; // initial delay of 1 sec if (_prevTimeMillis != 0) elapsed = current - _prevTimeMillis; _prevTimeMillis = current; scriptbuf.append(elapsed).append(" "); if (mouseEvent != null) { scriptbuf.append("MOUSE "). append(mouseEvent.getX()). append(" "). append(mouseEvent.getY()); } else { scriptbuf.append("KEY "); scriptbuf.append(Integer.toHexString(key)); scriptbuf.append(" "); scriptbuf.append(key2ASCII(key)); } _scriptPrintStream.println(scriptbuf.toString()); } // if (_scriptPrintStream != null) } // for (;;) } catch (Exception e) { e.printStackTrace(); } Object obj = Toolkit.getDefaultToolkit(); synchronized(obj){ obj.notifyAll(); } keyboardReader = null; } /** * Convert the integer representation of a keystroke to an ASCII string. */ public static String key2ASCII(int key_) { StringBuffer buf = new StringBuffer(); if (key_ < 0x20) { buf.append("^"); buf.append((char) (key_ + 0x40)); } else if (key_ == 0x20) { buf.append("SPACE"); } else if (key_ < 0x7f) { buf.append((char) key_); } else { switch (key_) { case KeyEvent.VK_DOWN: buf.append("VK_DOWN"); break; case KeyEvent.VK_UP: buf.append("VK_UP"); break; case KeyEvent.VK_LEFT: buf.append("VK_LEFT"); break; case KeyEvent.VK_RIGHT: buf.append("VK_RIGHT"); break; case KeyEvent.VK_HOME: buf.append("VK_HOME"); break; case KeyEvent.VK_BACK_SPACE: buf.append("VK_BACK_SPACE"); break; case KeyEvent.VK_F1: case KeyEvent.VK_F2: case KeyEvent.VK_F3: case KeyEvent.VK_F4: case KeyEvent.VK_F5: case KeyEvent.VK_F6: case KeyEvent.VK_F7: case KeyEvent.VK_F8: case KeyEvent.VK_F9: case KeyEvent.VK_F10: case KeyEvent.VK_F11: case KeyEvent.VK_F12: case KeyEvent.VK_F13: case KeyEvent.VK_F14: case KeyEvent.VK_F15: case KeyEvent.VK_F16: case KeyEvent.VK_F17: case KeyEvent.VK_F18: case KeyEvent.VK_F19: case KeyEvent.VK_F20: buf.append("VK_F"); int c = 1 + key_ - KeyEvent.VK_F1; buf.append(c); break; case KeyEvent.VK_DELETE: buf.append("VK_DELETE"); break; case KeyEvent.VK_INSERT: buf.append("VK_INSERT"); break; case KeyEvent.VK_PAGE_DOWN: buf.append("VK_PAGE_DOWN"); break; case KeyEvent.VK_PAGE_UP: buf.append("VK_PAGE_UP"); break; case KeyEvent.VK_ENTER: buf.append("VK_ENTER"); break; case KeyEvent.VK_BACK_TAB: buf.append("VK_BACK_TAB"); break; case KeyEvent.VK_END: buf.append("VK_END"); break; default: buf.append("UNKNOWN"); } } return buf.toString(); } /** Close the terminal window and restore terminal settings * (calls the curses endwin() function). */ public abstract void close(); /** Clears the screen (calls the curses clear() function). */ public abstract void clear(); /** * Set absolute cursor position */ public void setCursor(Point p) { setCursor(p.x, p.y); } /** * Set absolute cursor position */ public abstract void setCursor(int x_, int y_); /** * Get absolute cursor position */ public Point getCursor() { int x = getx(); int y = gety(); return new Point(x, y); } /** * Get absolute cursor position. Use this overloaded version to * avoid allocating a new Point on the heap. */ public Point getCursor(Point p_) { p_.x = getx(); p_.y = gety(); return p_; } /** * Add a character to the virtual terminal at the current cursor position. */ public abstract void addChar(int chr, int attrib, int colorpair_); //NOT USED /** * Draw a horizontal line of the specified length starting at the current * cursor position. */ // public abstract void addHorizontalLine(int length_, int attrib_, // int colorpair); /** * Draw a vertical line of the specified length starting at the current * cursor position. */ public abstract void addVerticalLine(int length_, int attrib_, int colorpair_); /** * Add a string to the virtual terminal at the current cursor position. */ public abstract void addString(String str_, int attrib_, int colorpair_); /** * Draw a box. */ public void drawBox(Point origin_, Dimension size_) { drawBoxNative(origin_.x, origin_.y, origin_.x + size_.width - 1, origin_.y + size_.height - 1, 0); } /** * Draw a box using the specified color pair. */ public void drawBox(Point origin_, Dimension size_, int colorpair_) { drawBoxNative(origin_.x, origin_.y, origin_.x + size_.width - 1, origin_.y + size_.height - 1, colorpair_); } public abstract void drawBoxNative(int left_, int top_, int right_, int bottom_, int colorpair_); /** */ public void blankBox(Point origin_, Dimension size_) { blankBoxNative(origin_.x, origin_.y, origin_.x + size_.width - 1, origin_.y + size_.height - 1, 0); } /** Blank out a box using the specified color pair. * @param origin_ The top left corner of the rectangle. * @param size_ The dimensions of the rectangle to blank out. * @param colorpair_ The number of the color-pair (foreground+background) * to use for blanking the rectangle. */ public void blankBox(Point origin_, Dimension size_, int colorpair_) { blankBoxNative(origin_.x, origin_.y, origin_.x + size_.width - 1, origin_.y + size_.height - 1, colorpair_); } /** * Blank out the specified rectangle. */ public abstract void blankBoxNative(int left_, int top_, int right_, int bottom_, int colorpair_); /** * Set a clipping rectangle. */ public void setClipRect(Rectangle clip_) { setClipRectNative(clip_.getLeft(), clip_.getTop(), clip_.getRight(), clip_.getBottom()); } /** * Set a clipping rectangle. */ public abstract void setClipRectNative(int left_, int top_, int right_, int bottom_); /** * Reset the clipping rectangle to the screen size. */ public abstract void resetClipRect(); /** * Ring the terminal's bell. */ public abstract void beep(); public abstract int getScreenRows(); public abstract int getScreenColumns(); public Dimension getScreenSize() { return new Dimension(getScreenColumns(), getScreenRows()); } /** Returns true if the terminal is capable of displaying colours. */ public abstract boolean hasColors(); /** * Returns the maximum number of color-pairs (provides an interface * to the ncurses COLOR_PAIRS global variable). */ public abstract int getMaxColorPairs(); /** An interface to the terminfo "start_colors()" function. */ public abstract void startColors(); /** * Returns the color-pair index corresponding to the specified * color-pair. If the color pair does not exist yet, it is created * using the ncurses "init_pair" function. If all the available * color-pairs are already in use, a TerminfoCapabilityException * is thrown. */ public int getColorPairIndex(ColorPair pair_) throws TerminfoCapabilityException { int index = _colorPairs.indexOf(pair_); if (index != -1) return index; if (_colorPairs.size() == getMaxColorPairs()) { throw new TerminfoCapabilityException( "max number of color pairs (" + getMaxColorPairs() + ") exceeded"); } index = _colorPairs.size(); _colorPairs.add(pair_); initColorPair(index, pair_.getForeground(), pair_.getBackground()); return index; } /** * Synchronize the state of the physical terminal with the state of * the virtual terminal maintained by curses (ie provides an interface * to the curses refresh() function). */ public abstract void sync(); //NOT USED /** * Indicate to ncurses that all the lines on the screen have changed * and need to be redrawn the next time Toolkit.sync() is called. This * just calls the ncurses function "redrawwin(stdscr)". */ // public abstract void redrawWin(); /** * Provides an interface to the ncurses "tigetstr()" function. */ // public abstract String getStringCapability(String capname_) // throws TerminfoCapabilityException; /** * Provides an interface to the ncurses "tigetnum()" function. */ // public abstract int getNumericCapability(String capname_) // throws TerminfoCapabilityException; /** * Provides an interface to the ncurses "tigetflag()" function. */ // public abstract boolean getBooleanCapability(String capname_) // throws TerminfoCapabilityException; //NOT USED /** * Provides an interface to the ncurses "putp()" function, to write a * string to the terminal; the string must be a return value from * getStringCapability(). */ // public abstract void putp(String str_); //NOT USED /** * Provides an interface to the ncurses "mcprint()" function to ship * the specified string to a printer attached to the terminal. Makes * use of the "mc4" and "mc5" capabilities of the terminal (see * "man curs_print"). */ // public abstract void print(String str_) throws TerminfoCapabilityException; /** Provides an interface to the terminfo "init_pair()" function. */ public abstract void initColorPair(int pair_, int fgnd_, int bgnd_) throws TerminfoCapabilityException; /** Emulates the terminfo COLOR_PAIR macro. */ public static int COLOR_PAIR_ATTRIBUTE(int pair_) { return (pair_ << 8); } /** Returns the tty device name (provides an interface to the * Unix C function "ttyname()"). */ public abstract String getTtyName(); //==================================================================== // PACKAGE-PRIVATE METHODS /** * Add a window to the list of displayed windows. * Intended to be called by the Window class only. */ void addWindow(Window window_) { synchronized (_windowList) { _windowList.add(window_); } } // public static void pause(){ // try { // Thread.sleep(500); // } // catch( InterruptedException e ) { // e.printStackTrace(); // } // } /** * Remove a window from the list of displayed windows. * This is intended to be called by the Window object when it hides itself. */ void removeWindow(Window window_) { // System.err.println( "Removing window" +window_.getName() ); // pause(); synchronized (_windowList) { if (_windowList.remove(window_) == false) { // throw new RuntimeException( // "trying to remove window not in windowlist"); //todo why does it happen this case? // System.err.println( "Trying to remove window not in list" ); } // throw new RuntimeException( // "trying to remove window not in windowlist"); } } /** * Returns a Vector of all the currently-displayed Windows. Note that the * calling thread must synchronize on the returned Vector before using or * modifying it, because the window list is accessed by the * keyboard-reading thread as well as by the event-dispatching thread. */ Vector<Window> getWindowList() { return _windowList; } private static int[] COLOR_TABLE = { 0, //black 0 4, //red 0 2, //green 0 14, //yellow 0 1, //blue 0 5, //magenta 0 3, //cyan 0 7, //white 0 }; /** This method is used for initializing the color constants in the * Color class. */ protected static int getColor(int index) { return COLOR_TABLE[index]; } /** Changes the default foreground color. */ public static void setDefaultForeground(Color color_) { _defaultForeground = color_; } /** Returns the default foreground color. */ public static Color getDefaultForeground() { return _defaultForeground; } /** Changes the default background color. */ public static void setDefaultBackground(Color color_) { _defaultBackground = color_; } /** Returns the default background color. */ public static Color getDefaultBackground() { return _defaultBackground; } /** Trigger garbage collection. This method can be called inside an * event handler, but the call to <code>System.gc()</code> will be * made after the event handler has completed (i.e. after drawing is * completed). This is a convenient, but OPTIONAL, way of ensuring * that the heap does not grow too large. Some experts say that it is * better not to call System.gc() explicitly from inside an application; * you take your pick. * * @param source_ the component that triggered the garbage collection * (not important). */ public void triggerGarbageCollection(Component source_) { //LS _evtQueue.postEvent( new GarbageCollectionEvent(source_)); } //==================================================================== // PRIVATE METHODS /** * This method is intended to be called by the keyboard-reading thread * only. */ protected abstract Object readKey(); /** Get current X position of cursor. */ protected abstract int getx(); /** Get current Y position of cursor. */ protected abstract int gety(); private static int[] ATTRIBUTE_TABLE = { 0, //A_NORMAL 0 0, //A_STANDOUT 1 1, //A_UNDERLINE 2 2, //A_REVERSE 3 0, //A_BLINK 4 0, //A_DIM 5 4, //A_BOLD 6 0, //A_ALTCHARSET 7 0, //A_INVIS 8 }; /** This method is used for initializing the curses / ncurses video * attributes. */ protected static int getAttribute(int offset_) { return ATTRIBUTE_TABLE[offset_]; } private static int[] ACS_TABLE = new int[] { 218, //ACS_ULCORNER 0 192, //ACS_LLCORNER 1 191, //ACS_URCORNER 2 217, //ACS_LRCORNER 3 195, //ACS_LTEE 4 180, //ACS_RTEE 5 193, //ACS_BTEE 6 194, //ACS_TTEE 7 196, //ACS_HLINE 8 179, //ACS_VLINE 9 197, //ACS_PLUS 10 0, //ACS_S1 11 0, //ACS_S9 12 254, //ACS_DIAMOND 13 //TODO check it 178, //ACS_CKBOARD 14 //TODO check it 0, //ACS_DEGREE 15 0, //ACS_PLMINUS 16 0, //ACS_BULLET 17 }; /** This method is used for initializing the line-drawing * characters using curses/ncurses. */ private static int getACSchar(int offset_) { return ACS_TABLE[offset_]; } //==================================================================== // INSTANCE VARIABLES //private int _rows; //private int _columns; /** * A list of visible Windows. The first in the list is at the bottom, the * last is on top. */ private Vector<Window> _windowList = new Vector<Window>(); /** * A list of color-pairs. */ protected Vector<ColorPair> _colorPairs = new Vector<ColorPair>(); protected EventQueue _evtQueue; /** Used to record keystrokes if "charva.script.record" is defined. */ private static PrintStream _scriptPrintStream = null; /** Used to save the time the previous key was pressed. */ private long _prevTimeMillis = 0; private long _lastMousePressTime; private long _lastMouseClickTime; // Definition as for ISO8859-1 (Latin-1) characters public static final char Auml = (char) 0xc4; public static final char Ccedil = (char) 0xc7; public static final char Eacute = (char) 0xc9; public static final char Euml = (char) 0xcb; public static final char Ouml = (char) 0xd6; public static final char Uuml = (char) 0xdc; /* This is a static initializer block. It is executed once, * when the class is initialized. */ static { /* Check if the user wants to record or play back a script. */ String scriptfilename=null; //SRR turning off scripting, because of security exception. // String scriptfilename = System.getProperty("charva.script.record"); if (scriptfilename != null) { try { _scriptPrintStream = new PrintStream( new FileOutputStream(scriptfilename)); } catch (FileNotFoundException ef) { System.err.println("Cannot open script file \"" + scriptfilename + "\" for writing"); System.exit(1); } } // String home = System.getProperty("user.home"); // String logfilename = home + "/charva.log"; // System.loadLibrary("Terminal"); } /** This flag is true is the system property "charva.color" has been set. */ public static final boolean isColorEnabled = true; //(System.getProperty("charva.color") != null); /* These are set to match the equivalent definitions in <ncurses.h> * or <curses.h> (they have to be set dynamically because the values * differ between curses, ncurses and PDCurses). */ public static final int A_NORMAL = getAttribute(0); public static final int A_STANDOUT = getAttribute(1); public static final int A_UNDERLINE = getAttribute(2); public static final int A_REVERSE = getAttribute(3); public static final int A_BLINK = getAttribute(4); public static final int A_DIM = getAttribute(5); public static final int A_BOLD = getAttribute(6); public static final int A_ALTCHARSET = getAttribute(7); public static final int A_INVIS = getAttribute(8); // graphical symbols that must be initialized from <ncurses.h> or // <curses.h> public static final int ACS_ULCORNER = getACSchar(0); public static final int ACS_LLCORNER = getACSchar(1); public static final int ACS_URCORNER = getACSchar(2); public static final int ACS_LRCORNER = getACSchar(3); public static final int ACS_LTEE = getACSchar(4); public static final int ACS_RTEE = getACSchar(5); public static final int ACS_BTEE = getACSchar(6); public static final int ACS_TTEE = getACSchar(7); public static final int ACS_HLINE = getACSchar(8); public static final int ACS_VLINE = getACSchar(9); public static final int ACS_PLUS = getACSchar(10); public static final int ACS_S1 = getACSchar(11); public static final int ACS_S9 = getACSchar(12); public static final int ACS_DIAMOND = getACSchar(13); public static final int ACS_CKBOARD = getACSchar(14); public static final int ACS_DEGREE = getACSchar(15); public static final int ACS_PLMINUS = getACSchar(16); public static final int ACS_BULLET = getACSchar(17); // These constants must be the same as in ncurses.h public static final int BLACK = getColor(0); public static final int RED = getColor(1); public static final int GREEN = getColor(2); public static final int YELLOW = getColor(3); public static final int BLUE = getColor(4); public static final int MAGENTA = getColor(5); public static final int CYAN = getColor(6); public static final int WHITE = getColor(7); public static Color _defaultForeground = Color.white; public static Color _defaultBackground = Color.black; // NOT USED // public static final int KEY_MOUSE = 0631; // // public static final int BUTTON1_RELEASED = 000001; // public static final int BUTTON1_PRESSED = 000002; // public static final int BUTTON1_CLICKED = 000004; // public static final int BUTTON2_RELEASED = 000100; // public static final int BUTTON2_PRESSED = 000200; // public static final int BUTTON2_CLICKED = 000400; // public static final int BUTTON3_RELEASED = 010000; // public static final int BUTTON3_PRESSED = 020000; // public static final int BUTTON3_CLICKED = 040000; }