All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ascert.open.term.gui.AbstractKeyHandler Maven / Gradle / Ivy

Go to download

An open source emulator supporting 3270 and potentially later 5250 terminal types.

The newest version!
/*
 * Copyright (c) 2016, 2017 Ascert, LLC.
 * www.ascert.com
 *
 * 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 com.ascert.open.term.gui;

import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ComponentInputMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.KeyStroke;

import com.ascert.open.ohio.Ohio;
import com.ascert.open.term.core.InputCharHandler;
import com.ascert.open.term.core.IsProtectedException;
import com.ascert.open.term.core.TermChar;
import com.ascert.open.term.core.TermField;
import com.ascert.open.term.core.Terminal;

/**
 *
 * @version 1,0 21-Apr-2017
 * @author rhw

 */
public abstract class AbstractKeyHandler implements KeyHandler
{
    //////////////////////////////////////////////////
    // INSTANCE VARIABLES
    //////////////////////////////////////////////////

    private static final Logger log = Logger.getLogger(AbstractKeyHandler.class.getName());

    protected ActionMap actMap = new ActionMap();
    protected InputMap inpMap = new InputMap();
    protected Terminal term;

    private final MyKeyListener keyListener;

    protected InputCharHandler charHandler;

    //////////////////////////////////////////////////
    // CONSTRUCTORS
    //////////////////////////////////////////////////
    public AbstractKeyHandler(Terminal term)
    {
        this.term = term;
        this.keyListener = new MyKeyListener();
        this.charHandler = new MyCharHandler();
        addCommonKeys();
    }

    //////////////////////////////////////////////////
    // PUBLIC INSTANCE METHODS
    //////////////////////////////////////////////////
    public void addCommonKeys()
    {
        // These are fairly standard bindings. They can be replaced or removed as needed in specific implementations
        makeKeyAction("UP", new UpAction());
        makeKeyAction("DOWN", new DownAction());
        makeKeyAction("LEFT", new LeftAction());
        makeKeyAction("RIGHT", new RightAction());
        makeKeyAction("HOME", new HomeAction());
        makeKeyAction("END", new EndAction());

        makeKeyAction("DELETE", new DeleteAction());
        makeKeyAction("TAB", new TabAction());
        makeKeyAction("shift TAB", new BacktabAction());
        makeKeyAction("BACK_SPACE", new BackspaceAction());
    }

    public Action getAction(final Ohio.OHIO_AID aid)
    {
        final String name = aid.getAidMnemonic().toUpperCase();
        Action act = actMap.get(name);
        if (act == null)
        {
            act = new AbstractAction(name)
            {
                public void actionPerformed(ActionEvent arg0)
                {
                    log.finest("AID/F-key pressed: " + aid);
                    term.Fkey(aid);
                    // Need to refresh in case lock changed
                    term.getClient().refresh();
                }
            };
            getActionMap().put(name, act);
        }
        return act;
    }

    public Action makeFkeyAction(KeyStroke kStroke, final Ohio.OHIO_AID aid)
    {
        Action act = getAction(aid);
        getInputMap().put(kStroke, act.getValue(Action.NAME));
        return act;
    }

    public Action makeKeyAction(String keyName, Action act)
    {
        act.putValue(Action.NAME, keyName);
        getActionMap().put(keyName, act);
        KeyStroke kStroke = KeyStroke.getKeyStroke(keyName);
        getInputMap().put(kStroke, keyName);
        return act;
    }

    //TODO - there's a focus issue with these buttons. Once clicked, keyboard entry is no longer handled properly
    public JButton makeButton(final Ohio.OHIO_AID aid, String text)
    {
        JButton button = makeButton(aid);
        button.setText(text);
        return button;
    }

    public JButton makeButton(Ohio.OHIO_AID aid)
    {
        JButton btn = new JButton(actMap.get(aid.getAidMnemonic().toUpperCase()));
        // Probably not ideal, but if button gets focus then keys seem to stop working (and SPACE key then triggers the focused
        // button action).
        btn.setFocusable(false);
        return btn;
    }

    //////////////////////////////////////////////////
    // INTERFACE METHODS - KeyHandler
    //////////////////////////////////////////////////
    public InputMap getInputMap(InputMap parent)
    {
        if (parent != null)
        {
            if (parent instanceof ComponentInputMap && !(this.inpMap instanceof ComponentInputMap))
            {
                // We can't simply return our map, we need to create a cloned copy of the correct type
                ComponentInputMap newMap = new ComponentInputMap(((ComponentInputMap) parent).getComponent());

                if (inpMap.size() > 0)
                {
                    for (KeyStroke key : inpMap.keys())
                    {
                        newMap.put(key, inpMap.get(key));
                    }
                }

                inpMap = newMap;
            }

            inpMap.setParent(parent);
        }

        return inpMap;
    }

    public InputMap getInputMap()
    {
        return getInputMap(null);
    }

    public ActionMap getActionMap(ActionMap parent)
    {
        if (parent != null)
        {
            actMap.setParent(parent);
        }
        return actMap;
    }

    public ActionMap getActionMap()
    {
        return getActionMap(null);
    }

    public KeyListener getKeyListener()
    {
        return this.keyListener;
    }

    public void doKeyAction(String keyName, boolean clientRefresh, boolean observeKbdLock)
    {
        doKeyAction(KeyStroke.getKeyStroke(keyName), clientRefresh, observeKbdLock);
    }
    
    public void doKeyAction(KeyStroke keyStroke, boolean clientRefresh, boolean observeKbdLock)
    {
        Object kObj = inpMap.get(keyStroke);
        
        if (keyStroke == null || kObj == null)
        {
            return;
        }
        
        Action act = actMap.get(kObj);
        
        if (act == null)
        {
            return;
        }

        if (act instanceof KeyAction)
        {
            ((KeyAction) act).doAction(clientRefresh, observeKbdLock);
            return;
        }

        ActionEvent actEvt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, keyStroke.toString());
        act.actionPerformed(actEvt);
    }
    

    /**
     * @return the charHandler
     */
    public InputCharHandler getCharHandler()
    {
        return charHandler;
    }

    //////////////////////////////////////////////////
    // PRIVATE INSTANCE METHODS
    //////////////////////////////////////////////////
    //////////////////////////////////////////////////
    // STATIC INNER CLASSES
    //////////////////////////////////////////////////
    //////////////////////////////////////////////////
    // NON-STATIC INNER CLASSES
    //////////////////////////////////////////////////
    public class MyKeyListener extends KeyAdapter
    {

        public void keyTyped(KeyEvent evt)
        {
            char typedChar = evt.getKeyChar();

            if (term.getClient().clearStatus())
            {
                evt.consume();
            }

            // Not sure if there is a better way to skip non-character keys
            if ((typedChar != KeyEvent.CHAR_UNDEFINED) && (!evt.isControlDown() && !evt.isAltDown() && !evt.isMetaDown()) && !(typedChar
                                                                                                                               == KeyEvent.VK_TAB)
                && !(typedChar == KeyEvent.VK_BACK_SPACE) && !(typedChar == KeyEvent.VK_DELETE) && !(typedChar == KeyEvent.VK_ENTER)
                && !(typedChar == KeyEvent.VK_ESCAPE))
            {
                // the typed key generated some character
                try
                {
                    log.finest("typed char: " + typedChar);
                    if (getCharHandler().type(typedChar));
                    {
                        term.getClient().refresh();
                    }
                    evt.consume();
                }
                catch (IsProtectedException e)
                {
                    log.warning(e.toString());
                }
                catch (Exception e)
                {
                    log.log(Level.WARNING, "Error in char handler", e);
                }
            }
            else
            {
                log.finest("unhandled key: " + evt);
            }
        }
    }

    public class MyCharHandler implements InputCharHandler
    {

        /**
         * Inserts the specified ASCII character at the current cursor position if the current field is unprotected, and advances the cursor
         * position by one. This is useful for implementations that accept keyboard input directly. For implementations that don't require
         * character-by-character input, use RW3270Field.setData(String data) instead.
         *
         * @param key keyboard/ASCII character corresponding to the key pressed.
         *
         * @throws IsProtectedException if the current field is protected.
         *
         * @see RW3270Field
         */
        public boolean type(char key, boolean updateDisplay) throws IsProtectedException, IOException
        {
            if (term.isKeyboardLocked())
            {
                return false;
            }

            int oldPos = term.getCursorPosition();
            int newPos = oldPos + 1;
            TermChar ch = term.getChar(oldPos);

            if (term.allowDirectScreenEditing())
            {
                ch.setChar(key);
                term.setCursorPosition(newPos);
            }
            else
            {
                TermField f = term.getField(oldPos);

                if (ch.isStartField() || f == null || f.isProtected())
                {
                    throw new IsProtectedException();
                }

                if (!f.isValidInput(key))
                {
                    term.getClient().beep();
                    return false;
                }

                ch.setChar(f.applyTransform(key));
                f.setModified(true);

                int bufsize = term.getCharBuffer().length;
                if (f.getEndBA() < f.getBeginBA() && newPos >= bufsize)
                {
                    newPos -= bufsize;
                }
                if (newPos > f.getEndBA())
                {
                    if (f.isAutoTab())
                    {
                        term.setCursorPosition(term.getNextUnprotectedField(newPos));
                    }
                    else
                    {
                        term.getClient().beep();
                    }
                    return true;
                }

                term.setCursorPosition(newPos);
            }
            return true;
        }

    }

    public class KeyAction extends AbstractAction
    {

        boolean refresh;
        boolean observeKbdLock;

        public KeyAction()
        {
            this(true, true);
        }

        public KeyAction(boolean refresh, boolean observeKbdLock)
        {
            this.refresh = refresh;
            this.observeKbdLock = observeKbdLock;
        }

        @Override
        public void actionPerformed(ActionEvent evt)
        {
            log.finest("key action: " + evt + ", at pos:  " + term.getCursorPosition());
            doAction(refresh, observeKbdLock);
        }

        public void doAction(boolean withRefresh, boolean observeKbdLock)
        {
            try
            {
                if (observeKbdLock && term.isKeyboardLocked())
                {
                    //TODO - one possible central place to add type-ahead handling for bound keys when locked
                    return;
                }

                if (handle())
                {
                    if (withRefresh)
                    {
                        term.getClient().refresh();
                    }
                }
            }
            catch (Exception ex)
            {
                log.warning("Exception handling action: " + ex);
                log.log(Level.FINE, "", ex);
            }
        }

        public boolean handle() throws Exception
        {
            return false;
        }
    }

    /**
     * Moves the cursor position 'up' one row from it's current position.
     * 

* For example, in an 80-column screen, calling the up() mehtod will decrease the cursor position by 80. This method will * 'wrap' from the first row to the last row *

*/ public class UpAction extends KeyAction { public boolean handle() { term.setCursorPosition(term.getCursorPosition() - term.getCols()); return true; } } /** * Moves the cursor position 'down' one row from it's current position. * *

* For example, in an 80-column screen, calling the down() mehtod will increase the cursor position by 80. This method will * 'wrap' from the last row to the first row *

*/ public class DownAction extends KeyAction { public boolean handle() { term.setCursorPosition(term.getCursorPosition() + term.getCols()); return true; } } /** * Moves the cursor position one character to the left, wrapping when necessary. Returns without moving the cursor if the terminal is * currently locked. */ public class LeftAction extends KeyAction { public boolean handle() { term.setCursorPosition(term.getCursorPosition() - 1); return true; } } /** * Moves the cursor position one character to the right, wrapping when necessary. * *

* Returns without moving the cursor if the terminal is currently locked. *

*/ public class RightAction extends KeyAction { public boolean handle() { term.setCursorPosition(term.getCursorPosition() + 1); return true; } } /** * This method moves the cursor to the first character of the first unprotected field in the data buffer. */ public class HomeAction extends KeyAction { public boolean handle() { //TODO - check handling when no fields int pos = term.getNextUnprotectedField(0); term.setCursorPosition(pos); return true; } } /** * This method moves the cursor to the first character of the last unprotected field in the data buffer. */ public class EndAction extends KeyAction { public boolean handle() { //TODO - check handling when no fields int pos = term.getPreviousUnprotectedField(0); term.setCursorPosition(pos); return true; } } /** * Advances the cursor position to the first character position of the next unprotected field. */ public class TabAction extends KeyAction { public boolean handle() { term.setCursorPosition(term.getNextUnprotectedField(term.getCursorPosition())); return true; } } /** * This method sets the cursor position to the first character position of the last unprotected field. */ public class BacktabAction extends KeyAction { public boolean handle() { term.setCursorPosition(term.getPreviousUnprotectedField(term.getCursorPosition())); return true; } } /** * Deletes the current character (by setting it to empty) and decrements the cursor position by one. * * @throws IsProtectedException if the current field is protected. */ public class BackspaceAction extends KeyAction { public boolean handle() throws IsProtectedException { int newPos = term.getCursorPosition() - 1; int bufsize = term.getCharBuffer().length; int len; if (term.allowDirectScreenEditing()) { if (newPos < 0) { return true; } len = bufsize - 1 - newPos; } else { if (newPos < 0) { if (term.getChar(0).getField() == term.getChar(newPos + bufsize).getField()) { newPos += bufsize; } else { return true; } } TermChar ch = term.getChar(newPos); TermField fld = ch.getField(); if (fld == null || fld.isProtected()) { throw new IsProtectedException(); } if (ch.isStartField()) { return true; } fld.setModified(true); len = fld.getEndBA() - newPos; // Must allow for possible field wrapping if (len < 0) { len += bufsize; } } //TODO - wonder if we should use clear to reset any video attributes?? int pos = newPos; for (int ix = 0; ix < len; ix++) { int nextpos = pos + 1; if (nextpos >= bufsize) { nextpos = 0; } term.getChar(pos).setChar(term.getChar(nextpos).getChar()); pos = nextpos; } term.getChar(pos).setChar(term.getEmptyChar()); term.setCursorPosition(newPos); return true; } } /** * Deletes the current character (by setting it to empty). * * @throws IsProtectedException if the current field is protected. */ public class DeleteAction extends KeyAction { public boolean handle() throws IsProtectedException { int pos = term.getCursorPosition(); int bufsize = term.getCharBuffer().length; int len; if (term.allowDirectScreenEditing()) { len = bufsize - 1 - pos; } else { TermChar ch = term.getChar(pos); TermField fld = ch.getField(); if (fld == null || fld.isProtected() || ch.isStartField()) { throw new IsProtectedException(); } fld.setModified(true); len = fld.getEndBA() - pos; // Must allow for possible field wrapping if (len < 0) { len += bufsize; } } for (int ix = 0; ix < len; ix++) { int nextpos = pos + 1; if (nextpos >= bufsize) { nextpos = 0; } term.getChar(pos).setChar(term.getChar(nextpos).getChar()); pos = nextpos; } term.getChar(pos).setChar(term.getEmptyChar()); return true; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy