/* This file is part of
* ======================================================
* 
*           LyX, the High Level Word Processor
* 	 
*	    Copyright (C) 1995 1996 Matthias Ettrich
*           and the LyX Team.
*
*======================================================*/


/* ======================================================================= *\
    File   : kbmap.C, kbmap.C,v 1.1.1.1 1996/08/19 14:39:26 larsbj Exp
    Author : chb, 28.Oct.1995
    Docu   : classes: kb_sequence, kb_keymap, kb_key;
             howto: 1. define a kb_keymap as toplevel
                    2.   create a kb_sequence
                    3.   add keys to sequence (eg. using kb_sequence::parse)
                    4.   use kb_keymap::defkey to add sequence to map
                    5.   repeat 3-4. for all key sequences
                    6. create a static kb_sequence
                    7. set stdmap, curmap to toplevel map in sequence
                    8. add keys as they come to sequence and check retval.
             todo:  Add other modifiers to parse()
    Purpose: key maps for dispatching Key-X-Events
\* ======================================================================= */

#include "config.h"

#include <string.h>
#include <stdio.h>

#include "kbmap.h"
#include "file.h"
#ifdef DEBUG
#  define DBG(x) do{ printf x; fflush(stdout); } while(0)
#else
#  define DBG(x)
#endif


// === static functions ===================================================


/* ---F+------------------------------------------------------------------ *\
    Function  : printKeysym
    Called by : kb_sequence::print and printKeyMap. RVDK_PATCH_5
    Purpose   : prints a keysym, including modifiers.
    Parameters: key    - keysym
                mod    - modifiers
               buf    - string where the result goes
                maxlen - length of string (including '\0')
    Returns   : length of printed string if ok, 0 otherwise.
\* ---F------------------------------------------------------------------- */

static int printKeysym( KeySym key, unsigned mod, char *buf, int maxlen )
{
	int len;
	char *s;

	mod &= 0xffff;

	// calc required length;
	len = 0;
	if ( mod & ShiftMask )   len += 2;
	if ( mod & ControlMask ) len += 2;
	if ( mod & Mod1Mask )    len += 2;
	if ( mod & Mod2Mask )    len += 2;
	
	s = XKeysymToString( key );
	if ( s ) len += strlen( s );
	if ( len < maxlen ) {
		if ( mod & ShiftMask )
		{ *buf++ = 'S'; *buf++ = '-'; }
		if ( mod & ControlMask )
		{ *buf++ = 'C'; *buf++ = '-'; }
		if ( mod & Mod1Mask )
		{ *buf++ = 'M'; *buf++ = '-'; }
		if ( mod & Mod2Mask )
		{ *buf++ = 'W'; *buf++ = '-'; }
		if ( s ) strcpy( buf, s );
		return len;
	}
	else 
		return 0;
}


/* ---F+------------------------------------------------------------------ *\
   Function  : printKeyTab
   Called by : kb_keymap::print
   Purpose   : print the keysyms found in the given key table. RVDK_PATCH_5
   Parameters: tabPt  - keytable pointer
               buf    - string where the result goes
               maxLen - length of string (including '\0')
   Returns   : length of printed string.
\* ---F------------------------------------------------------------------- */

static int printKeyTab( kb_key *tabPt, char *buf, int maxLen )
{
	int len, doneLen = 0;
	unsigned ksym, mod;
	
	/* -------> Print each of the slots into buf. */
	for( ; (tabPt->code & 0xffff) != NoSymbol; tabPt++) {
		
		if ( maxLen <= 0 ) break;
		
		ksym =  tabPt->code;
		mod  =  tabPt->mod & 0xffff;
		
		len = printKeysym( ksym, mod, buf, maxLen );
		if ( len <= 0 ) break;
		buf     += len;
		maxLen  -= len;
		doneLen += len;
		
		/* -------> Add space when possible. */
		if ( maxLen > 0 ) {
			*buf++ = ' ';
			*buf = '\0';
			maxLen--;
			doneLen++;
		}
		
		/* if ( tabPt->table ) continue; prefix key stuff. */
	}
	return doneLen;
}



// === kb_sequence methods ================================================


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::addkey
    Called by : [user]
    Purpose   : add a key to the sequence, look up in map and return action
    Parameters: key  - keysym of key
                mod  - modifier mask
                nmod - modifier veto mask (unused now)
    Returns   : action or -1 if error (no map defined or key not found)
\* ---F------------------------------------------------------------------- */

int kb_sequence::addkey(KeySym key, unsigned mod, unsigned nmod /*=0*/)
{
   if(length<0) length=0;

   if(length+1 >= size)
   {
      unsigned *nseq = new unsigned[size+KB_PREALLOC];
      size += KB_PREALLOC;
      memcpy(nseq, sequence, length*sizeof(unsigned));
      if(sequence != staticseq) delete sequence;
      sequence = nseq;
      nseq = new unsigned[size];
      memcpy(nseq, modifiers, length*sizeof(unsigned));
      if(modifiers != staticmod) delete modifiers;
      modifiers = nseq;
   }

   modifiers[length]  = mod + (nmod<<16);
   sequence[length++] = key;
   
   if(curmap)
      return curmap->lookup(key, mod, this);
   
   return -1;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::parse
    Called by : [user]
    Purpose   : parse a string that holds a key sequence and add the keys
    Parameters: s - string holding the key sequence
    Returns   : 0 - if ok, error pos if error
    Note      : Keys must be separated with whitespace;
                Use the keysym names used by XStringToKeysym
                Prefixes are S-, C-, M- for shift, control, meta
\* ---F------------------------------------------------------------------- */

int kb_sequence::parse(char *s)
{
   int i = 0, j;
   unsigned mod = 0, nmod = 0;
   KeySym key;
   char tbuf[100];

   if(!s[0]) return 1;

   while(s[i])
   {
      if(s[i] && s[i] <= ' ') i++;
      if(!s[i]) break;

      if(s[i] && s[i+1]=='-')
      {
         switch(s[i])
         {
          case 's': case 'S':
            mod |= ShiftMask;
            i+=2;
            continue;
          case 'c': case 'C':
            mod |= ControlMask;
            i+=2;
            continue;
          case 'm': case 'M':
            mod |= Mod1Mask;
            i+=2;
            continue;
	  case 'w': case 'W':
	    mod |= Mod2Mask;
	    i+=2;
	    continue;
          default:
            return i+1;
         }
      }
      else if(s[i]=='~' && s[i+1] && s[i+2]=='-')
      {
         switch(s[i+1])
         {
          case 's': case 'S':
            nmod |= ShiftMask;
            i+=3;
            continue;
          case 'c': case 'C':
            nmod |= ControlMask;
            i+=3;
            continue;
          case 'm': case 'M':
            nmod |= Mod1Mask;
            i+=3;
            continue;
          case 'w': case 'W':
            nmod |= Mod2Mask;
            i+=3;
            continue;
          default:
            return i+2;
         }
      }
      else
      {
         for(j = i; s[j] && s[j]>' '; j++)
            tbuf[j-i] = s[j];               // (!!!check bounds :-)

         tbuf[j-i] = '\0';
         
         key = XStringToKeysym(tbuf);
         if(key == NoSymbol)
            return j;
         i = j;

         addkey(key, mod, nmod);
         mod = 0;
         nmod = 0;
      }
   }

   return 0;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::print
    Called by : [user]
    Purpose   : print the currently defined sequence into a string
    Parameters: buf           - string where the result goes
                maxlen        - length of string (including '\0')
                when_defined  - only  print when sequence is real: length > 0.
    Returns   : 0, if ok, -1 if string too long
\* ---F------------------------------------------------------------------- */

int kb_sequence::print(char *buf, int maxlen, bool when_defined) const
{
   KeySym key;
   unsigned mod;
   int len;
   int l = length;
   if ( l<0 && !when_defined ) l = -l;

   for(int i = 0; i < l; i++)
   {
      key = sequence[i];
      mod = modifiers[i] & 0xffff;

      len = printKeysym( key, mod, buf, maxlen );                  // RVDK_PATCH_5
      buf += len;
      maxlen -= len;

      if ( len == 0 ) {
                 *buf = '\0';
                 return -1;
       }

      if(i+1<l && maxlen>1) {  // append a blank
                 *buf++ = ' ';
                maxlen--;
      }
   }
   *buf = '\0';
   return 0;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::printOptions
    Called by : [user]
    Purpose   : print the available key options from the current state in the
                sequence. RVDK_PATCH_5
    Parameters: buf    - string where the result goes
                maxlen - length of string (including '\0')
    Returns   : 0, if ok, -1 if string too long
\* ---F------------------------------------------------------------------- */

int kb_sequence::printOptions(char *buf, int maxlen) const
{
    int len;

    print( buf, maxlen, true );
    len = strlen( buf );
    maxlen -= len;
    buf    += len;

    if ( maxlen < 20 || !curmap ) return -1;

    strcpy( buf, "   options: " );
    buf += 12;
    maxlen -= 12;
    curmap->print(buf, maxlen);
    return 0;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::delseq
    Called by : [user]
    Purpose   : mark the sequence as deleted
    Parameters: none
    Returns   : nothing
\* ---F------------------------------------------------------------------- */

void kb_sequence::delseq()
{
   // negative length marks sequence as deleted, but we can still
   // print() it or retrieve the last char using getiso()
   length = -length;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::getsym
    Called by : [user], getiso
    Purpose   : get the keysym of the last key in sequence
    Parameters: none
    Returns   : keysym
\* ---F------------------------------------------------------------------- */

KeySym kb_sequence::getsym()
{
   int l = length;
   if(l==0) return NoSymbol;
   if(l<0) l = -l;
   return sequence[l-1];
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::getiso
    Called by : [user]
    Purpose   : return iso character code of last key, if any
    Parameters: none
    Returns   : iso code or 0 if none
\* ---F------------------------------------------------------------------- */

char kb_sequence::getiso()
{
   int c = getsym();

   if(c > 0xff)
      return '\0';
   return (char)c;
}

/* ---F+------------------------------------------------------------------ *\
    Function  : kb_sequence::reset
    Called by : [user]
    Purpose   : reset sequence to initial state. RVDK_PATCH_5
    Parameters: none
    Returns   : void
\* ---F------------------------------------------------------------------- */

void kb_sequence::reset()
{
       delseq();
       curmap = stdmap;
       if ( length > 0 ) length = -length;
}


// === kb_keymap methods ==================================================


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_keymap::lookup
    Called by : [user], kb_sequence::add()
    Purpose   : look up a key press in a given keymap
    Parameters: key - the keysym of the key press
                mod - the modifier mask of the keypress
                seq - the key-sequence retrieved so far
    Returns   : user defined action; 0 for prefix key, -1 if key not found
\* ---F------------------------------------------------------------------- */

int kb_keymap::lookup(KeySym key, unsigned mod, kb_sequence *seq)
{
   unsigned hashval, ksym, msk1, msk0;
   kb_key *tab;

   if(!table)
   {
      // error - no keymap defined:
      seq->curmap = seq->stdmap;
      seq->delseq();
      return -1;
   }

   if(size < 0)               // --- if hash table ---
   {
      hashval = ((key&0xff) ^ ((key>>8)&0xff)) % KB_HASHSIZE;
      tab = htable[hashval];
      if(!tab)
      {
         seq->curmap = seq->stdmap;
         seq->delseq();
         return -1;
      }
   }
   else                       // --- else: linear list ---
      tab = table;

   // --- now search the list of keys ---

   for( ; (tab->code & 0xffff) != NoSymbol; tab++)
   {
      ksym =  tab->code;
      msk1 =  tab->mod      & 0xffff;
      msk0 = (tab->mod>>16) & 0xffff;

      if(ksym == key && (mod&~msk0) == msk1)
      {
         // match found:
         if(tab->table)
         {
            seq->curmap = tab->table; // this is a prefix key - set new map
            return 0;
         }
         else
         {
            seq->curmap = seq->stdmap;  // final key - reset map
            seq->delseq();
            return tab->action;         // ... and return action
         }
      }
   }

   // error - key not found:
   seq->curmap = seq->stdmap;
   seq->delseq();
   return -1;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_keymap::print
    Called by : [user]
    Purpose   : Prints all the available keysyms. RVDK_PATCH_5
    Parameters: buf    - string where output goes.
               maxLen - available length in string, including `\0'.
    Returns   : updated maxLen.
\* ---F------------------------------------------------------------------- */

int kb_keymap::print(char *buf, int maxLen) const
{
	// kb_key        *tabPt; // unused
   int                    len;

 /* -------> Return when running out of string space or when keymap has no table.
      Else, place a terminating newline in case no other output is generated. */

   if ( maxLen <= 3 || !buf ) return maxLen;
   if ( !table ) return maxLen;
   *buf   = '\0';
   
 /* -------> Hash table. Process each of its slots recursively and return. */
   if ( size < 0 ) {   
       for ( int ix = 0; (ix < KB_HASHSIZE) && (maxLen > 1); ix++ ) {
          if ( htable[ix] ) {
              len = printKeyTab( htable[ix], buf, maxLen );
              maxLen -= len;
              buf    += len;
          }
       }
   }
   else {
       /* -------> Normal table. */
       len = printKeyTab( table, buf, maxLen ); 
       maxLen -= len;
       buf    += len;
   }

   return maxLen;
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_keymap::defkey
    Called by : [user]
    Purpose   : define an action for a key sequence
    Parameters: seq    - the key sequence
                action - the action to be defined
                idx    - recursion depth
    Returns   : 0 if ok.
\* ---F------------------------------------------------------------------- */

int kb_keymap::defkey(kb_sequence *seq, int action, int idx /*=0*/)
{
   int      tsize;
   unsigned code, modmsk;
   kb_key  *tab, **ptab;


   code = seq->sequence[idx];
   modmsk = seq->modifiers[idx];
   if(code == NoSymbol) return -1;

   // --- get list --------------------------------------------------------
   if(!table)
   {
      table = new kb_key[KB_PREALLOC];
      table[0].code = NoSymbol;
      tab   =  table;
      ptab  = &table;
      size  = KB_PREALLOC;
   }
   else if(size<0)
   {
      int hashval = (code&0xffff);
      hashval = ((hashval&0xff) ^ ((hashval>>8)&0xff)) % KB_HASHSIZE;
      tab  = htable[hashval];
      ptab = htable+hashval;
      if(!tab)
      {
         tab = new kb_key[KB_PREALLOC];
         tab[0].code = NoSymbol;
         *ptab = tab;
      }
   }
   else
   {
      tab  =  table;
      ptab = &table;
   }

   // --- check if key is already there -----------------------------------

   kb_key *t;
   for(t = tab, tsize=1; t->code != NoSymbol; t++, tsize++)
   {
      if(code == t->code && modmsk == t->mod) // --- overwrite binding ---
      {
         if(idx+1 == seq->length)
         {
            if(t->table)
            {
               delete t->table;
	       t->table = 0;
            }
            t->action = action;
            return 0;
         }
         else
            return t->table->defkey(seq, action, idx+1);
      }
   }
   
   // --- extend list if necessary ----------------------------------------

   if(tsize % KB_PREALLOC == 0)
   {
      kb_key *nt = new kb_key[tsize+KB_PREALLOC];
      // Set to NULL as table is used uninitialised later (thornley)
      nt[tsize].table = NULL;
      memcpy(nt, tab, tsize*sizeof(kb_key));
      *ptab = nt;
      delete tab;
      tab = nt;
      if(size>=0) size = tsize+KB_PREALLOC;
   }

   // --- add action ------------------------------------------------------

   tab[tsize--].code = NoSymbol;
   tab[tsize].code = code;
   tab[tsize].mod  = modmsk;
   kb_key *newone = &tab[tsize];

   // --- convert list to hash table if necessary -------------------------

   if(size>=0 && tsize>=32)
   {
      kb_key *oldtab = tab;
      kb_key **nht = new kb_key*[KB_HASHSIZE];
      for(int i = 0; i < KB_HASHSIZE; i++)
         nht[i] = 0;
      htable = nht;
      size   = -KB_HASHSIZE;

      // --- copy old keys to new hash table ---
      int hashval;
      for(kb_key *tu = oldtab; tu->code != NoSymbol; tu++){
	      // copy values from oldtab to htable
	      hashval = (tu->code&0xffff);
	      hashval = ((hashval&0xff) ^ ((hashval>>8)&0xff)) % KB_HASHSIZE;
	      tab  = htable[hashval];
	      
	      if(!tab){
		      htable[hashval] = tab = new kb_key[KB_PREALLOC];
		      tab->code = NoSymbol;
	      }
	      int ts = 1;
	      for(kb_key *tt = tab; tt->code != NoSymbol; tt++)
		      ts++;
	      if(ts % KB_PREALLOC == 0){
		      // extend table
		      kb_key *nt = new kb_key[ts+KB_PREALLOC];
		      memcpy(nt, tab, ts*sizeof(kb_key));
		      htable[hashval] = nt;
		      delete tab;
		      tab = nt;
	      }
	      tab[ts--].code = NoSymbol;
	      tab[ts].code   = tu->code;
	      tab[ts].mod    = tu->mod;
	      tab[ts].action = tu->action;
	      tab[ts].table  = tu->table;
	      
	      if(tu == newone)
		      newone = &tab[ts];
      }

      delete oldtab;
   }

   // --- define rest of sequence -----------------------------------------

   if(idx+1 == seq->length)
   {
      newone->action = action;
      newone->table  = 0;
      return 0;
   }
   else
   {
      newone->table = new kb_keymap;
      int res = newone->table->defkey(seq, action, idx+1);
      return res;
   }
}


/* ---F+------------------------------------------------------------------ *\
    Function  : kb_keymap::~kb_keymap
    Called by : [destructor]
    Purpose   : free keymap and its descendents
    Parameters: none
    Returns   : nothing
\* ---F------------------------------------------------------------------- */

kb_keymap::~kb_keymap()
{
   if(!table) return;
   if(size<0)
   {
      for(int i=0; i < KB_HASHSIZE; i++)
      {
         if(htable[i])
         {
            for(kb_key *t = htable[i]; t->code != NoSymbol; t++)
               if(t->table)
                  delete t->table;
            delete htable[i];
         }
      }
      delete htable;
   }
   else
   {
      for(kb_key *t = table; t->code != NoSymbol; t++)
         if(t->table)
            delete t->table;
      delete table;
   }
}


/* ----------------------------------------------------------------------- */

#ifdef DEBUG_STANDALONE

#include "keybind.h"

kb_keymap *toplevel_keymap;

int main()
{
   kb_sequence k;

   // --- create a keymap ---

   toplevel_keymap = new kb_keymap;
   kb_keymap *map = toplevel_keymap;

   // --- do a few bindings ---

#  define BIND(seq, func) k.parse(seq); map->defkey(&k, func); k.delseq()

   BIND("C-x u", LFUN_MENULOADAUTO);
   BIND("C-x a", LFUN_AUTOSAVE);
   BIND("C-x f", LFUN_MENUOPEN);
   BIND("C-x d", LFUN_MENUNEW);
   BIND("C-x s", LFUN_MENUWRITE);
   BIND("C-x w", LFUN_MENUWRITEAS);
   BIND("C-x c", LFUN_MENUQUIT);
   BIND("C-x k", LFUN_CLOSEBUFFER);
   BIND("C-x r", LFUN_RUNLATEX);
   BIND("C-x t", LFUN_RUNLATEX);
   BIND("C-x g", LFUN_PREVIEWPS);
   BIND("C-x p", LFUN_PREVIEW);
   BIND("C-x b", LFUN_PREVBUFFER);

   BIND("F1", LFUN_LAYOUTNO);
   BIND("F35", LFUN_LAYOUTNO);
   BIND("Delete",    LFUN_DELETE);  // redef

   BIND("Right",   LFUN_RIGHT);
   BIND("C-f",     LFUN_RIGHT);
   BIND("S-Right", LFUN_RIGHTSEL);
   BIND("C-F",     LFUN_RIGHTSEL);
   BIND("Left",    LFUN_LEFT);
   BIND("C-b",     LFUN_LEFT);
   BIND("S-Left",  LFUN_LEFTSEL);
   BIND("S-C-B",   LFUN_LEFTSEL);

   BIND("Up",      LFUN_UP);
   BIND("C-Up",    LFUN_UP);
   BIND("C-p",     LFUN_UP);
   BIND("S-Up",    LFUN_UPSEL);
   BIND("S-C-P",   LFUN_UPSEL);

   BIND("Down",    LFUN_DOWN);
   BIND("C-Down",  LFUN_DOWN);
   BIND("C-n",     LFUN_DOWN);
   BIND("S-Down",  LFUN_DOWNSEL);
   BIND("S-C-N",   LFUN_DOWNSEL);

   BIND("C-Right", LFUN_WORDRIGHT);
   BIND("M-f",     LFUN_WORDRIGHT);
   BIND("S-C-Right", LFUN_WORDRIGHTSEL);
   BIND("S-M-F",   LFUN_WORDRIGHTSEL);
   BIND("C-Left",  LFUN_WORDLEFT);
   BIND("M-b",     LFUN_WORDLEFT);
   BIND("S-C-Left",LFUN_WORDLEFTSEL);
   BIND("S-M-B",   LFUN_WORDLEFTSEL);

   BIND("Home",    LFUN_HOME);
   BIND("C-a",     LFUN_HOME);
   BIND("S-Home",  LFUN_HOMESEL);
   BIND("S-C-A",   LFUN_HOMESEL);

   BIND("End",     LFUN_END);
   BIND("C-e",     LFUN_END);
   BIND("S-End",   LFUN_ENDSEL);
   BIND("S-C-E",   LFUN_ENDSEL);

   BIND("Prior",   LFUN_PRIOR);
   BIND("C-Prior", LFUN_PRIOR);
   BIND("C-v",     LFUN_PRIOR);  // !!! emacs does it the other way round!
   BIND("S-Prior", LFUN_PRIORSEL);
   BIND("S-C-V",   LFUN_PRIORSEL);

   BIND("Delete",    LFUN_BACKSPACE);  // redef

   // --- check the keymap ---

   k.stdmap = k.curmap = toplevel_keymap;

   int mod, sym, res;
   char buf[100];

   mod = ControlMask;
   sym = 0x78;                    /* C-x */
   res = k.addkey(sym,mod);
   k.print(buf,100);
   DBG(("RES = %d (l=%d) [%s]\n", res, k.length, buf));

   mod = 0;
   sym = 0x6b;                    /* k */
   res = k.addkey(sym,mod);
   k.print(buf,100);
   DBG(("RES = %d (l=%d) [%s]\n", res, k.length, buf));

   mod = ControlMask;
   sym = 0x78;                    /* C-x */
   res = k.addkey(sym,mod);
   DBG(("RES = %d (l=%d)\n", res, k.length));

   mod = ShiftMask;
   sym = 0x41;                    /* A */
   res = k.addkey(sym,mod);
   DBG(("RES = %d (l=%d)\n", res, k.length));

   mod = ShiftMask;
   sym = 0xff53;                    /* Right */
   res = k.addkey(sym,mod);
   DBG(("RES = %d (l=%d)\n", res, k.length));

   return 0;
}

#endif

/* === End of File: kbmap.C ============================================== */
