package jcurses.widgets; import jcurses.event.WindowEvent; import jcurses.event.WindowListener; import jcurses.event.WindowListenerManager; import jcurses.system.CharColor; import jcurses.system.InputChar; import jcurses.system.Toolkit; import jcurses.util.Rectangle; import java.util.Collections; import java.util.Comparator; import java.util.Hashtable; import java.util.Vector; /** * This class is a jcurses implementation of an text based window. An window * under jcurses is, differnt from other GUI libraries, not a widget, this * contains a panel ( a the called root panel ), that contains all widgets. * A window can, but doesn't must, have a border and a title. * All windows under jcurses are managed in a stack, the topmost visible window * window on the stack gets all input chars for handling, this is so called focus * window. If an window are created, it gets automatically to the top of the stack * and leaves the until an other window is created or explicitly brought to the top. * */ public class Window { private Panel _root = null; private Vector<Widget> _focusableChilds = null; private int _currentIndex = -1; private boolean _visible = false; private Rectangle _rect = null; private boolean _border = false; private String _title = null; private boolean _hasShadow = true; private Vector<InputChar> _shortCutsList = new Vector<InputChar>(); private Hashtable<InputChar, Widget> _shortCutsTable = new Hashtable<InputChar, Widget>(); boolean _closed = false; /** * The constructor * * @param x the the top left corner's x coordinate * @param y the top left corner's y coordinate * @param width window's width * @param height window's height * @param border true, if the window has a border, false in other case */ public Window (int x, int y, int width, int height, boolean border, String title) { _border = border; _title = title; _rect = new Rectangle(width, height); _rect.setLocation(x, y); int x1 = border?x+1:x; int y1 = border?y+1:y; int w = border?width-2:width; int h = border?height-2:height; _root = new Panel(w, h); _root.setSize(new Rectangle(w, h)); _root.setX(x1); _root.setY(y1); _root.setWindow(this); //WindowManager.createWindow(this); } /** * The constructor. A window created with this constructor is centered on the screen. * * @param width window's width * @param height window's height * @param border true, if the window has a border, false in other case */ public Window (int width, int height, boolean border, String title) { this((Toolkit.getScreenWidth()-width)/2,(Toolkit.getScreenHeight()-height)/2, width, height, border, title); } private void configureRootPanel() { if (_root == null) { _root = new Panel(); } int x = _rect.getX(); int y = _rect.getY(); int width = _rect.getWidth(); int height = _rect.getHeight(); int x1 = _border?x+1:x; int y1 = _border?y+1:y; int w = _border?width-2:width; int h = _border?height-2:height; _root.setSize(new Rectangle(w, h)); _root.setX(x1); _root.setY(y1); } /** * The method shows the window */ public void show() { setVisible(true); } /** * The method hides the window */ public void hide() { setVisible(false); } /** * The method changes the window's visibility status * * @param value true, if the window becomes visible, false in other case */ public void setVisible(boolean value){ Window oldTop = WindowManager.getTopWindow(); WindowManager.createWindow(this); _visible = value; if (value) { pack(); WindowManager.makeWindowVisible(this, oldTop); } else { WindowManager.makeWindowInvisible(this, oldTop); } } /** * The method returns the window's visibility status * @return true, if the window becomes visible, false in other case */ public boolean isVisible() { return _visible; } /** * The method paint's the window */ protected void paint() { drawThingsIfNeeded(); _root.paint(); } /** * Currently the method makes the same as repaint, in next versions the method * will repaint only the part of the window, that was hided. */ protected void repaint() { drawThingsIfNeeded(); _root.repaint(); } /** * @return the rectangle occupied by the window */ protected Rectangle getRectangle() { return _rect; } /** * The method closed the window, that is removes it from window stack an * evantually from screen, if it was visible. */ public void close() { WindowManager.removeWindow(this); } /** * The method moves the window to the top of the stack */ public void moveToTheTop() { WindowManager.moveToTop(this); } /** * Folgende Methoden bestimmen das zeichen das benutzt wird, um das Fenster zu schlie�en */ private static InputChar __defaultClosingChar = new InputChar(27);//escape character private InputChar _closingChar = getDefaultClosingChar(); private InputChar getDefaultClosingChar() { return __defaultClosingChar; } /** * The method returns the character to close window. * As default is escape characted defined * * @return window's closing character */ public InputChar getClosingChar() { return _closingChar; } /** * The method defines a new window's closing character. Default is escape. * @param character new window's closing character */ public void setClosingChar(InputChar character) { _closingChar = character; } private static InputChar __defaultFocusChangeChar = new InputChar('\t');//tab character private InputChar _focusChangeChar = getDefaultFocusChangeChar(); private InputChar getDefaultFocusChangeChar() { return __defaultFocusChangeChar; } /** * The method returns the charater used to navigate (change the focus) between widgets * within the window. Default is 'tab' * * @return window's focus changing charater */ public InputChar getFocusChangeChar() { return _focusChangeChar; } /** * The method defined the charater used to navigate (change the focus) between widgets * within the window. Default is 'tab' * * @param character new window's focus changing charater */ public void setFocusChangeChar(InputChar character) { _focusChangeChar = character; } /** * Behandlung der Eingabe. * Vier m�gliche F�lle: * 1. Fenster schliessen. * 2. Zum n�chsten Widget springen. * 3. Shortcut bearbeiten. * 3. Eingabe vom aktuell Fokus habenden Kind bearbeiten lassen. */ private boolean isShortCut(InputChar inp) { return (_shortCutsList.indexOf(inp)!=-1); } private Widget getWidgetByShortCut(InputChar inp) { return (Widget)_shortCutsTable.get(inp); } private static InputChar __upChar = new InputChar(InputChar.KEY_UP); private static InputChar __downChar = new InputChar(InputChar.KEY_DOWN); private static InputChar __leftChar = new InputChar(InputChar.KEY_LEFT); private static InputChar __rightChar = new InputChar(InputChar.KEY_RIGHT); /** * The method tries to close the window, after the user has typed 'escape' * or an other closing character. The procedure is as following: * If the window has listeners, than an event is sent to the listeners. The window * can be closed bei listeners. Did'nt listeners close the window, in leaves open. * Has the window no listeners, than the method closes it. * */ public boolean tryToClose() { if (_listenerManager.countListeners() > 0) { _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.CLOSING)); return isClosed(); } else { close(); return true; } } /** * @return true, if the window is already closed, false in othe case. */ public boolean isClosed() { return _closed; } /** * The method is called by the libray to handle an input character, if the window has the focus. */ protected void handleInput(InputChar inp) { if (inp.equals(getClosingChar())) { tryToClose(); } else if (inp.equals(getFocusChangeChar())) { changeFocus(); } else if (inp.equals(__upChar)) { Widget cur = getCurrentWidget(); if (cur != null) { boolean result = cur.handleInput(inp); if (!result) { changeFocus(2); } } } else if (inp.equals(__downChar)) { Widget cur = getCurrentWidget(); if (cur != null) { boolean result = cur.handleInput(inp); if (!result) { changeFocus(3); } } } else if (inp.equals(__leftChar)) { Widget cur = getCurrentWidget(); if (cur != null) { boolean result = cur.handleInput(inp); if (!result) { changeFocus(0); } } } else if (inp.equals(__rightChar)) { Widget cur = getCurrentWidget(); if (cur != null) { boolean result = cur.handleInput(inp); if (!result) { changeFocus(1); } } } else if (isShortCut(inp)) { Widget cur = getCurrentWidget(); if (cur!=null) { boolean result = cur.handleInput(inp); if (!result) { getWidgetByShortCut(inp).handleInput(inp); } } } else { if (!handleInputByCurrentChild(inp)) { onChar(inp); } } } /** * The method is called by <code>handleInput</code>, if no widget has handled * the input. Derived classes can override the method to define additional shortcuts. */ protected void onChar(InputChar inp) { //default nothing } private void changeFocus() { if (_currentIndex != -1) { int newIndex = (_currentIndex+1)%_focusableChilds.size(); while (newIndex!=_currentIndex){ Widget newWidget = _focusableChilds.elementAt(newIndex); if (newWidget.isFocusable()) break; newIndex=(newIndex+1)%_focusableChilds.size(); } if (newIndex!=_currentIndex) { ((Widget)_focusableChilds.elementAt(_currentIndex)).setFocus(false); ((Widget)_focusableChilds.elementAt(newIndex)).setFocus(true); _currentIndex = newIndex; } } } private void changeFocus(int direction) { if (_currentIndex != -1) { changeFocus(getNextWidget(direction)); } } /** * Get the next widget by direction * 0 - Left * 1 - Right * 2 - Up * 3 - Down * @param direction * @return */ private Widget getNextWidget(int direction) { Widget result = getCurrentWidget(); int searchDirection = ((direction == 0) || (direction == 2))?-1:1; Vector<Widget> widgets = _focusableChilds; int index = widgets.indexOf(result); if (index < 0) { throw new RuntimeException("widget in the sorted queue not found!!"); } index += searchDirection; while (index<widgets.size() && index>=0){ if (widgets.get(index).isFocusable()) break; index += searchDirection; } if (index>=0 && index<widgets.size()){ result = widgets.get(index); } return result; } void changeFocus(Widget widget) { int newIndex = _focusableChilds.indexOf(widget); if (newIndex!=-1) { if (_currentIndex == -1) { widget.setFocus(true); _currentIndex = newIndex; } else if (_currentIndex != newIndex) { widget.setFocus(true); ((Widget)_focusableChilds.elementAt(_currentIndex)).setFocus(false); _currentIndex = newIndex; } } } private Widget getCurrentWidget() { if (_currentIndex != -1) { return ((Widget)_focusableChilds.elementAt(_currentIndex)); } else { return null; } } private boolean handleInputByCurrentChild(InputChar inp) { if (_currentIndex != -1) { return ((Widget)_focusableChilds.elementAt(_currentIndex)).handleInput(inp); } return false; } private void loadShortcuts() { _shortCutsList.clear(); _shortCutsTable.clear(); Vector<Widget> list = _root.getListOfWidgetsWithShortCuts(); for (int i=0; i<list.size(); i++) { Widget widget = (Widget)list.elementAt(i); Vector<InputChar> shortCuts = widget.getShortCutsList(); _shortCutsList.addAll(shortCuts); for (int j=0; j< shortCuts.size(); j++) { _shortCutsTable.put(shortCuts.elementAt(j), widget); } } } private void loadFocusableChilds() { _focusableChilds = _root.getListOfFocusables(); if (_focusableChilds.size() == 0) { _currentIndex = -1; } else { Collections.sort(_focusableChilds, new WindowWidgetComparator()); _currentIndex = 0; ((Widget)_focusableChilds.elementAt(0)).setFocus(true); for (int i=1;i<_focusableChilds.size();i++){ ((Widget)_focusableChilds.elementAt(i)).setFocus(false); } } } /** * The method computes new window's layout. * The method must already be called, if anything on the window building * is changed, for example, an widget is removed or isn't more focusable * ( because not visible or other ). */ public void pack() { cutIfNeeded(); configureRootPanel(); _root.pack(); loadFocusableChilds(); loadShortcuts(); } private void cutIfNeeded() { int maxWidth = Toolkit.getScreenWidth()-_rect.getX()-(_hasShadow?1:0); int maxHeight = Toolkit.getScreenHeight()-_rect.getY()-(_hasShadow?1:0); if (_rect.getWidth() > maxWidth) { _rect.setWidth(maxWidth); } if (_rect.getHeight() > maxHeight) { _rect.setHeight(maxHeight); } } /** * @return the root panel of the window */ public Panel getRootPanel() { //Ein kommentar return _root; } /** * Sets the root panel of the window. This is the top most widget container in the * window's widget hierarchy. It occupies the entire window out of the border (if exists ). */ public void setRootPanel(Panel root) { _root = root; _root.setWindow(this); } private void drawThingsIfNeeded() { if (_border) { Toolkit.drawBorder(_rect, getBorderColors()); } paintTitle(); if (hasShadow()) { Toolkit.drawRectangle(_rect.getX()+_rect.getWidth(), _rect.getY()+1, 1, _rect.getHeight(), getShadowColors()); Toolkit.drawRectangle(_rect.getX()+1, _rect.getY()+_rect.getHeight(), _rect.getWidth(), 1, getShadowColors()); } } private void paintTitle() { if (_title!=null) { CharColor color = getTitleColors(); Toolkit.printString( _title, _rect.getX()+(_rect.getWidth()-_title.length())/2,_rect.getY(), color); } } private static CharColor __defaultBorderColors = new CharColor(CharColor.WHITE, CharColor.BLACK); private CharColor _borderColors = getDefaultBorderColors(); public CharColor getDefaultBorderColors() { return __defaultBorderColors; } public CharColor getBorderColors() { return _borderColors; } public void setBorderColors(CharColor colors) { _borderColors = colors; } /** * Normaler title */ private static CharColor __defaultTitleColors = new CharColor(CharColor.WHITE, CharColor.RED); private CharColor _titleColors = getDefaultTitleColors(); public CharColor getDefaultTitleColors() { return __defaultTitleColors; } public CharColor getTitleColors() { return _titleColors; } public void setTitleColors(CharColor colors) { _titleColors = colors; } /** * The method defines, whether the window is to paint with a shadow * * @param value true if a shadow is to paint, false in othe case */ public void setShadow(boolean value) { _hasShadow=value; } boolean hasShadow() { return _hasShadow; } private static CharColor __shadowColors = new CharColor(CharColor.BLACK, CharColor.BLACK); private CharColor getShadowColors() { return __shadowColors; } //Listener-Zeugs private WindowListenerManager _listenerManager = new WindowListenerManager(); /** * The method adds a listener to the window * * @param listener the listener to add */ public void addListener(WindowListener listener) { _listenerManager.addListener(listener); } /** * The method remove a listener from the window * * @param listener the listener to remove */ public void removeListener(WindowListener listener) { _listenerManager.removeListener(listener); } /** * The method is called, if the window gets focus. */ protected void activate() { _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.ACTIVATED)); } /** * The method is called, if the window loses focus. */ protected void deactivate() { _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.DEACTIVATED)); } /** * The method is called, if the window is closed. */ protected void closed() { _closed = true; _listenerManager.handleEvent(new WindowEvent(this, WindowEvent.CLOSED)); } protected void resize(int width, int height) { _rect.setWidth(width); _rect.setHeight(height); } } class WindowWidgetComparator implements Comparator<Widget> { public int compare(Widget widget1, Widget widget2) { Rectangle rect1 = widget1.getRectangle(); Rectangle rect2 = widget2.getRectangle(); int x1 = rect1.getX()+(rect1.getWidth()/2); int y1 = rect1.getY()+(rect1.getHeight()/2); int x2 = rect2.getX()+(rect2.getWidth()/2); int y2 = rect2.getY()+(rect2.getHeight()/2); if (y1<y2 || (y1==y2 && x1<x2)) return -1; else return 1; } }