// Copyright 1996, Marimba Inc. All Rights Reserved.


// Confidential and Proprietary Information of Marimba, Inc.


// @(#)Keymap.java, 1.10, 12/15/96





package marimba.text.keymap;





import java.util.*;


import java.awt.Event;





/**


 * This code implements the keymaps for the text widget.  It supports


 * key bindings of all flavors, including explicit support for


 * Shift/Meta/Control keys.  There's support for sparse maps as well


 * as full maps.  Typically the first level map is a full map and


 * everything else is sparse.


 *


 * @author	Jonathan Payne


 * @version 1.10, 12/15/96


 */


abstract public class Keymap extends KeymapItem {


    /* If the control key is pressed, it's converted to a prefix character


       257, and if meta is pressed, it's also a prefix character 256.





       So, Control-Meta-B is mainmap[257][256]['B'] where all but the


       mainmap are sparse. */


    public static int META = 256;


    public static int CONTROL = 257;


    public static final int NKEYS = 258;





    /*


     * If the control key is pressed, it's converted to a prefix


     * character 257, and if meta is pressed, it's also a prefix


     * character 256.


     *


     * So, Control-Meta-B is mainmap[257][256]['B'] where all but the


     * mainmap are sparse.


     */


    static Hashtable symbolicKeys = new Hashtable();


    static {


	symbolicKeys.put("home", new Integer(Event.HOME));


	symbolicKeys.put("end", new Integer(Event.END));


	symbolicKeys.put("pgup", new Integer(Event.PGUP));


	symbolicKeys.put("pgdn", new Integer(Event.PGDN));


	symbolicKeys.put("up", new Integer(Event.UP));


	symbolicKeys.put("down", new Integer(Event.DOWN));


	symbolicKeys.put("left", new Integer(Event.LEFT));


	symbolicKeys.put("right", new Integer(Event.RIGHT));


	symbolicKeys.put("f1", new Integer(Event.F1));


	symbolicKeys.put("f2", new Integer(Event.F2));


	symbolicKeys.put("f3", new Integer(Event.F3));


	symbolicKeys.put("f4", new Integer(Event.F4));


	symbolicKeys.put("f5", new Integer(Event.F5));


	symbolicKeys.put("f6", new Integer(Event.F6));


	symbolicKeys.put("f7", new Integer(Event.F7));


	symbolicKeys.put("f8", new Integer(Event.F8));


	symbolicKeys.put("f9", new Integer(Event.F9));


	symbolicKeys.put("f10", new Integer(Event.F10));


	symbolicKeys.put("f11", new Integer(Event.F11));


	symbolicKeys.put("f12", new Integer(Event.F12));


	symbolicKeys.put("tab", new Integer('\t'));


    }





    public static Keymap getSparseKeymap(Keymap parent) {


	return new SparseKeymap(parent);


    }





    public static Keymap getFullKeymap(Keymap parent) {


	return new FullKeymap(parent);


    }





    public static Keymap getCombinationKeymap(Keymap parent) {


	return new CombinationMap(parent);


    }





    /** Parent keymap, that we inherit from. */


    protected Keymap parent;





    public Keymap() {


    }





    public Keymap(Keymap parent) {


	this.parent = parent;


    }





    abstract public KeymapItem getKey(int key);





    abstract public void setKey(int key, KeymapItem item);





    /** Bind the specified key sequence in keymap MAP to the specified


      item. */


    public void bindSequence(String keys, KeymapItem item) {


	Keymap map = this;


	int nkeys = keys.length();





	for (int i = 0; i < nkeys - 1; i++) {


	    int ch = keys.charAt(i);





	    //System.out.println("\tCh: " + ch);





	    KeymapItem sub = map.getKey(ch);


	    boolean isNull = (sub == null);


	    if (isNull || !sub.isKeymap()) {


		Keymap keyParent;


		try {


		    keyParent = (Keymap) map.getKeyParent(ch);


		} catch (ClassCastException e) {


		    keyParent = null;


		}


		sub = new SparseKeymap(keyParent);


		map.setKey(ch, sub);


	    }


	    map = (Keymap) sub;


	}


	//System.out.println("\tCh: " + (int) keys.charAt(nkeys - 1));


	map.setKey(keys.charAt(nkeys - 1), item);


    }		





    public void bindKeys(String keys, KeymapItem item) {


	Keymap map = this;


	StringBuffer cookedKeys = new StringBuffer();


	int i = 0;


	int limit = keys.length();


	int c;


	boolean meta = false;


	boolean control = false;





	while (i < limit) {


	    boolean ascii;





	    c = keys.charAt(i++);


	    if (c == '\\') {


		boolean ends;





		int dash = keys.indexOf('-', i);


		if (ends = (dash == -1)) {


		    dash = keys.length();


		}


		String name = keys.substring(i, dash);


		name = name.toLowerCase();


		i = dash + 1;


		if ("meta".startsWith(name)) {


		    meta = true;


		    if (ends) {


			throw new RuntimeException("Bad key sequence: " +


						   keys);


		    }


		    continue;


		} else if ("control".startsWith(name)) {


		    control = true;


		    if (ends) {


			throw new RuntimeException("Bad key sequence: " +


						   keys);


		    }


		    continue;


		}


		Integer key = (Integer) symbolicKeys.get(name);


		if (key != null) {


		    c = key.intValue();


		    ascii = false;


		} else {


		    throw new RuntimeException("Unknown key: " + name);


		}


	    } else {


		ascii = true;


	    }


	    if (false) {


		if (meta) {


		    System.out.print("meta,");


		}


		if (control) {


		    System.out.print("control,");


		}


		if (ascii) {


		    System.out.print("ascii,");


		}


		if (Character.isLetter((char) c)) {


		    System.out.print("letter,");


		}


		System.out.println("'" + (char) c + "'" + " " + c);


	    }


	    if (meta) {


		cookedKeys.append((char) META);


	    }


	    if (control) {


		cookedKeys.append((char) CONTROL);


		if (ascii && (Character.isLetter((char) c) ||


			      c == '_' || c == '[' || c == '@')) {


		    /* kludge */


		    c &= 037;


		}


	    }


	    cookedKeys.append((char) c);


	    meta = control = false;


	}


	bindSequence(cookedKeys.toString(), item);


    }





    /** Get a keymap item, if null in this map, then get look in our


        parent. */


    public KeymapItem getKeyParent(int key) {


	KeymapItem item = getKey(key);





	if (item == null && parent != null)


	    return parent.getKeyParent(key);


	return item;


    }





    public boolean isKeymap() {


	return true;


    }


}





class FullKeymap extends Keymap {


    KeymapItem items[] = new KeymapItem[NKEYS];





    public FullKeymap() {


    }





    public FullKeymap(Keymap parent) {


	super(parent);


    }





    public KeymapItem getKey(int key) {


	try {


	    return items[key];


	} catch (ArrayIndexOutOfBoundsException e) {


	    return null;


	}


    }





    public void setKey(int key, KeymapItem item) {


	if (key >= items.length)


	    throw new ArrayIndexOutOfBoundsException(key + " out of range");


	items[key] = item;


    }


}





class SparseKeymap extends Keymap {


    static final int INIT_SIZE = 8;





    KeymapItem items[] = new KeymapItem[INIT_SIZE];


    int keys[] = new int[INIT_SIZE];


    int nkeys = 0;





    public SparseKeymap() {


    }





    public SparseKeymap(Keymap parent) {


	super(parent);


    }





    final int getKeyIndex(int key) {


	int keys[] = this.keys;





	for (int i = nkeys; --i >= 0; )


	    if (keys[i] == key)


		return i;


	return -1;


    }





    public KeymapItem getKey(int key) {


	int index = getKeyIndex(key);


	return (index == -1) ? null : items[index];


    }





    public void setKey(int key, KeymapItem item) {


	int index = getKeyIndex(key);


	if (index != -1)


	    items[index] = item;


	else {


	    if (nkeys == keys.length) {


		int nk[] = new int[nkeys + 8];


		KeymapItem ni[] = new KeymapItem[nkeys + 8];


		System.arraycopy(keys, 0, nk, 0, nkeys);


		System.arraycopy(items, 0, ni, 0, nkeys);


		keys = nk;


		items = ni;


	    }


	    items[nkeys] = item;


	    keys[nkeys++] = key;


	}


    }





    public String toString() {


	StringBuffer buf = new StringBuffer();


	buf.append(getClass().getName() + "[nkeys = " + nkeys + "\n");


	for (int i = 0; i < nkeys; i++)


	    buf.append((char) keys[i] + " = " + items[i] + "\n");


	buf.append("]");


	return buf.toString();


    }


}





class CombinationMap extends Keymap {


    FullKeymap full = new FullKeymap();


    SparseKeymap sparse = new SparseKeymap();





    public CombinationMap() {


    }





    public CombinationMap(Keymap parent) {


	super(parent);


    }





    public KeymapItem getKey(int key) {


	if (key < FullKeymap.NKEYS) {


	    return full.getKey(key);


	}


	return sparse.getKey(key);


    }





    public void setKey(int key, KeymapItem item) {


	if (key < FullKeymap.NKEYS) {


	    full.setKey(key, item);


	} else {


	    sparse.setKey(key, item);


	}


    }


}


