!===============================================================================
!     FKeys.h                         An Inform 6 library extension
!                                     Copyright 2000-2014 by David Fillmore
!     Release 1.1
! This work is licensed under the Creative Commons Attribution-NonCommercial
! 4.0 International License.
! To view a copy of this license, visit
! http://creativecommons.org/licenses/by-nc/4.0/
! or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain
! View, California, 94041, USA.
!===============================================================================
! This library file should enable the use of Function Keys as command shortcuts
! in your games.
! To make it work, Replace Keyboard and KeyboardPrimitive before inclusion of
! Parser.h, include this file after Parser.h, and put the line 'SetupFKeys();'
! somewhere in the initialise routine.
!
! The arrays containing the commands that will be used when various shortcut
! keys are pressed are set up in the SetupFKeys routine. There is a default
! SetupFKeys routine, but you can provide your own if you define it before
! including this file.
!
! It is highly recommended that you use exclusively lowercase characters
! if you define your own arrays, as some interpreters will break slightly when
! uppercase characters are used.
!
! If you have any problems, questions, or suggesctions, please e-mail me at
! [email protected]
!
!===============================================================================

#Ifndef TARGET_ZCODE;
Message fatalerror "*** The FKeys library extension only works with Z-Machine games ***";
#Endif;

#Iffalse (LIBRARY_VERSION == 612);
#Iffalse (LIBRARY_VERSION == 611);
Message fatalerror "*** The FKeys library extension requires version 6/11 or version 6/12 of the standard Inform library ***";
#Endif;
#Endif;

Array F1 -> 36;
Array F2 -> 36;
Array F3 -> 36;
Array F4 -> 36;
Array F5 -> 36;
Array F6 -> 36;
Array F7 -> 36;
Array F8 -> 36;
Array F9 -> 36;
Array F10 -> 36;
Array F11 -> 36;
Array F12 -> 36;

Array NP1 -> 36;
Array NP2 -> 36;
Array NP3 -> 36;
Array NP4 -> 36;
Array NP6 -> 36;
Array NP7 -> 36;
Array NP8 -> 36;
Array NP9 -> 36;


#ifndef WORDSIZE;
Constant TARGET_ZCODE;
Constant WORDSIZE 2;
#endif;

ZCharacter terminating 133 134 135 136 137 138 139 140 141 142 143 144 146 147 148 149 151 152 153 154;

Global termchar;

#ifndef SetupFKeys;
[ SetupFKeys;
 F1->0=4;
 F1->1='l';
 F1->2='o';
 F1->3='o';
 F1->4='k';
 F1->5=124;

 F2->0=9;
 F2->1='i';
 F2->2='n';
 F2->3='v';
 F2->4='e';
 F2->5='n';
 F2->6='t';
 F2->7='o';
 F2->8='r';
 F2->9='y';
 F2->10=124;

 F3->0=5;
 F3->1='o';
 F3->2='p';
 F3->3='e';
 F3->4='n';
 F3->5=' ';

 F4->0=8;
 F4->1='e';
 F4->2='x';
 F4->3='a';
 F4->4='m';
 F4->5='i';
 F4->6='n';
 F4->7='e';
 F4->8=' ';

 F5->0=5;
 F5->1='t';
 F5->2='a';
 F5->3='k';
 F5->4='e';
 F5->5=' ';

 F6->0=5;
 F6->1='d';
 F6->2='r';
 F6->3='o';
 F6->4='p';
 F6->5=' ';

 F7->0=7;
 F7->1='a';
 F7->2='t';
 F7->3='t';
 F7->4='a';
 F7->5='c';
 F7->6='k';
 F7->7=' ';

 F8->0=5;
 F8->1='a';
 F8->2='g';
 F8->3='a';
 F8->4='i';
 F8->5='n';
 F8->6=124;

 F9->0=4;
 F9->1='u';
 F9->2='n';
 F9->3='d';
 F9->4='o';
 F9->5=124;

 F10->0=5;
 F10->1='o';
 F10->2='o';
 F10->3='p';
 F10->4='s';
 F10->5=' ';

 F11->0=4;
 F11->1='a';
 F11->2='s';
 F11->3='k';
 F11->4=' ';

 F12->0=5;
 F12->1='t';
 F12->2='e';
 F12->3='l';
 F12->4='l';
 F12->5=' ';

 NP1->0=9;
 NP1->1='s';
 NP1->2='o';
 NP1->3='u';
 NP1->4='t';
 NP1->5='h';
 NP1->6='w';
 NP1->7='e';
 NP1->8='s';
 NP1->9='t';
 NP1->10=124;

 NP2->0=5;
 NP2->1='s';
 NP2->2='o';
 NP2->3='u';
 NP2->4='t';
 NP2->5='h';
 NP2->6=124;

 NP3->0=9;
 NP3->1='s';
 NP3->2='o';
 NP3->3='u';
 NP3->4='t';
 NP3->5='h';
 NP3->6='e';
 NP3->7='a';
 NP3->8='s';
 NP3->9='t';
 NP3->10=124;

 NP4->0=4;
 NP4->1='w';
 NP4->2='e';
 NP4->3='s';
 NP4->4='t';
 NP4->5=124;

 NP6->0=4;
 NP6->1='e';
 NP6->2='a';
 NP6->3='s';
 NP6->4='t';
 NP6->5=124;

 NP7->0=9;
 NP7->1='n';
 NP7->2='o';
 NP7->3='r';
 NP7->4='t';
 NP7->5='h';
 NP7->6='w';
 NP7->7='e';
 NP7->8='s';
 NP7->9='t';
 NP7->10=124;

 NP8->0=5;
 NP8->1='n';
 NP8->2='o';
 NP8->3='r';
 NP8->4='t';
 NP8->5='h';
 NP8->6=124;

 NP9->0=9;
 NP9->1='n';
 NP9->2='o';
 NP9->3='r';
 NP9->4='t';
 NP9->5='h';
 NP9->6='e';
 NP9->7='a';
 NP9->8='s';
 NP9->9='t';
 NP9->10=124;

];
#endif;

[ PressedKey a_buffer charnum x y z fkey max_chars current_chars total_chars;
 switch(termchar)
 { 133 to 154: switch(termchar)
               { 133: fkey=F1;
                 134: fkey=F2;
                 135: fkey=F3;
                 136: fkey=F4;
                 137: fkey=F5;
                 138: fkey=F6;
                 139: fkey=F7;
                 140: fkey=F8;
                 141: fkey=F9;
                 142: fkey=F10;
                 143: fkey=F11;
                 144: fkey=F12;
                 146: fkey=NP1;
                 147: fkey=NP2;
                 148: fkey=NP3;
                 149: fkey=NP4;
                 151: fkey=NP6;
                 152: fkey=NP7;
                 153: fkey=NP8;
                 154: fkey=NP9;
               }

               max_chars = a_buffer->0;
               current_chars = a_buffer->1;
               total_chars = (fkey->0) + current_chars;
               if (total_chars > max_chars-1) ! the new characters will not fit on the line
               { @sound_effect 2;
                 rtrue;
               }
               a_buffer->1=total_chars;

               x=(fkey->0)+1;
               @buffer_mode false;
               for (y=1:y<x:y++)
               { a_buffer->(y+1+current_chars)=(fkey->y);
                 print (char) a_buffer->(y+1+current_chars);
                 if (a_buffer->(y+1+current_chars) > $40 && a_buffer->(y+1+current_chars) < $5b)
                 { a_buffer->(y+1+current_chars)=(a_buffer->(y+1+current_chars)+$20);
                 }
               }


               if (fkey->y==124)
               { print "^";
                 z=0;
               }
               else
               z=1;
               @buffer_mode true;
 }
 charnum=a_buffer->1;
 return z;
];


[ KeyboardPrimitive  a_buffer a_table;
   a_buffer->0 = 120;
   @aread a_buffer a_table -> termchar;

   #Iftrue (#version_number == 6);
   @output_stream -1;
   @loadb a_buffer 1 -> sp;
   @add a_buffer 2 -> sp;
   @print_table sp sp;
   new_line;
   @output_stream 1;
   #Endif;
];

#Iftrue (LIBRARY_VERSION == 612);
[ Keyboard  a_buffer a_table  nw i w w2 x1 x2 a;
 DisplayStatus();
 .FreshInput;

 ! Save the start of the buffer, in case "oops" needs to restore it
 ! to the previous time's buffer

 for (i=0 : i<OOPS_WORKSPACE_LEN : i++) oops_workspace->i = a_buffer->i;

 ! In case of an array entry corruption that shouldn't happen, but would be
 ! disastrous if it did:

 #Ifdef TARGET_ZCODE;
 a_buffer->0 = INPUT_BUFFER_LEN - WORDSIZE;
 a_table->0  = MAX_BUFFER_WORDS; ! Allow to split input into this many words
 #Endif; ! TARGET_

 ! Print the prompt, and read in the words and dictionary addresses

 L__M(##Prompt);
 if (AfterPrompt() == 0) LibraryExtensions.RunAll(ext_afterprompt);
 a_buffer->1=0;
 .xyzzy;
 #IFV5; DrawStatusLine(); #ENDIF;
 KeyboardPrimitive(a_buffer, a_table);
 if (termchar~=13 or 10)
 { if (PressedKey(a_buffer)==1)
   { jump xyzzy;
   }

   @tokenise a_buffer a_table;
 }

  nw = NumberWords(a_table);

 ! If the line was blank, get a fresh line
 if (nw == 0) {
     L__M(##Miscellany, 10);
     jump FreshInput;
 }

 ! Unless the opening word was "oops", return
 ! Conveniently, a_table-->1 is the first word in both ZCODE and GLULX.

 w = a_table-->1;
 if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) jump DoOops;

 if (a_buffer->WORDSIZE == COMMENT_CHARACTER) {
     #Ifdef TARGET_ZCODE;
     if ((HDR_GAMEFLAGS-->0) & $0001 || xcommsdir)
                                        L__M(##Miscellany, 54);
     else                               L__M(##Miscellany, 55);
     #Ifnot; ! TARGET_GLULX
     if (gg_scriptstr || gg_commandstr) L__M(##Miscellany, 54);
     else                               L__M(##Miscellany, 55);
     #Endif; ! TARGET_

     jump FreshInput;
 }

 #IfV5;
 ! Undo handling

 if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
     if (turns == START_MOVE) {
         L__M(##Miscellany, 11);
         jump FreshInput;
     }
     if (undo_flag == 0) {
         L__M(##Miscellany, 6);
         jump FreshInput;
     }
     if (undo_flag == 1) jump UndoFailed;
     #Ifdef TARGET_ZCODE;
     @restore_undo i;
     #Ifnot; ! TARGET_GLULX
     @restoreundo i;
     i = (~~i);
     #Endif; ! TARGET_
     if (i == 0) {
       .UndoFailed;
         L__M(##Miscellany, 7);
     }
     jump FreshInput;
  }
   #Ifdef TARGET_ZCODE;
   @save_undo i;
   #Ifnot; ! TARGET_GLULX
   @saveundo i;
   if (i == -1) {
       GGRecoverObjects();
       i = 2;
   }
   else  i = (~~i);
   #Endif; ! TARGET_
   just_undone = 0;
   undo_flag = 2;
   if (i == -1) undo_flag = 0;
   if (i == 0) undo_flag = 1;
   if (i == 2) {
       RestoreColours();
       #Ifdef TARGET_ZCODE;
       style bold;
       #Ifnot; ! TARGET_GLULX
       glk($0086, 4); ! set subheader style
       #Endif; ! TARGET_
       print (name) location, "^";
       #Ifdef TARGET_ZCODE;
       style roman;
       #Ifnot; ! TARGET_GLULX
       glk($0086, 0); ! set normal style
       #Endif; ! TARGET_
       L__M(##Miscellany, 13);
       just_undone = 1;
       jump FreshInput;
   }
   #Endif; ! V5

   return nw;

 .DoOops;
   if (oops_from == 0) {
       L__M(##Miscellany, 14);
       jump FreshInput;
   }
   if (nw == 1) {
       L__M(##Miscellany, 15);
       jump FreshInput;
   }
   if (nw > 2) {
       L__M(##Miscellany, 16);
       jump FreshInput;
   }

   ! So now we know: there was a previous mistake, and the player has
   ! attempted to correct a single word of it.

   for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
   #Ifdef TARGET_ZCODE;
   x1 = a_table->9;  ! Start of word following "oops"
   x2 = a_table->8;  ! Length of word following "oops"
   #Ifnot; ! TARGET_GLULX
   x1 = a_table-->6; ! Start of word following "oops"
   x2 = a_table-->5; ! Length of word following "oops"
   #Endif; ! TARGET_

   ! Repair the buffer to the text that was in it before the "oops"
   ! was typed:

   for (i=0 : i < OOPS_WORKSPACE_LEN : i++) a_buffer->i = oops_workspace->i;
   Tokenise__(a_buffer, a_table);

   ! Work out the position in the buffer of the word to be corrected:

   #Ifdef TARGET_ZCODE;
   w = a_table->(4*oops_from + 1); ! Start of word to go
   w2 = a_table->(4*oops_from);    ! Length of word to go
   #Ifnot; ! TARGET_GLULX
   w = a_table-->(3*oops_from);      ! Start of word to go
   w2 = a_table-->(3*oops_from - 1); ! Length of word to go
   #Endif; ! TARGET_

   ! Write spaces over the word to be corrected:

   for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';

   if (w2 < x2) {
       ! If the replacement is longer than the original, move up...
       for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
           a_buffer->i = a_buffer->(i-x2+w2);

       ! ...increasing buffer size accordingly.
       SetKeyBufLength(GetKeyBufLength(a_buffer) + (x2-w2), a_buffer);
   }

   ! Write the correction in:

   for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);

   Tokenise__(a_buffer, a_table);
   nw=NumberWords(a_table);

   return nw;
]; ! end of Keyboard
#Endif;

#Iftrue (LIBRARY_VERSION == 611);
[ Keyboard  a_buffer a_table  nw i w w2 x1 x2;
   DisplayStatus();

 .FreshInput;

   ! Save the start of the buffer, in case "oops" needs to restore it
   ! to the previous time's buffer

   for (i=0 : i<64 : i++) oops_workspace->i = a_buffer->i;

   ! In case of an array entry corruption that shouldn't happen, but would be
   ! disastrous if it did:

   #Ifdef TARGET_ZCODE;
   a_buffer->0 = INPUT_BUFFER_LEN;
   a_table->0 = 15;  ! Allow to split input into this many words
   #Endif; ! TARGET_

   ! Print the prompt, and read in the words and dictionary addresses

   L__M(##Prompt);
   AfterPrompt();
   a_buffer->1=0;
   .xyzzy;
   #IFV5; DrawStatusLine(); #ENDIF;
   KeyboardPrimitive(a_buffer, a_table);
   if (termchar~=13 or 10)
   { if (PressedKey(a_buffer)==1)
     { jump xyzzy;
     }

     @tokenise a_buffer a_table;
   }

   #Ifdef TARGET_ZCODE;
   nw = a_table->1;
   #Ifnot; ! TARGET_GLULX
   nw = a_table-->0;
   #Endif; ! TARGET_

   ! If the line was blank, get a fresh line
   if (nw == 0) {
       L__M(##Miscellany, 10);
       jump FreshInput;
   }

   ! Unless the opening word was "oops", return
   ! Conveniently, a_table-->1 is the first word in both ZCODE and GLULX.

   w = a_table-->1;
   if (w == OOPS1__WD or OOPS2__WD or OOPS3__WD) jump DoOops;

   if (a_buffer->WORDSIZE == COMMENT_CHARACTER) {
       #Ifdef TARGET_ZCODE;
       if ((HDR_GAMEFLAGS-->0) & 1 || xcommsdir)
                                          L__M(##Miscellany, 54);
       else                               L__M(##Miscellany, 55);
       #Ifnot; ! TARGET_GLULX
       if (gg_scriptstr || gg_commandstr) L__M(##Miscellany, 54);
       else                               L__M(##Miscellany, 55);
       #Endif; ! TARGET_

       jump FreshInput;
   }

   #IfV5;
   ! Undo handling

   if ((w == UNDO1__WD or UNDO2__WD or UNDO3__WD) && (nw==1)) {
       if (turns == START_MOVE) {
           L__M(##Miscellany, 11);
           jump FreshInput;
       }
       if (undo_flag == 0) {
           L__M(##Miscellany, 6);
           jump FreshInput;
       }
       if (undo_flag == 1) jump UndoFailed;
       #Ifdef TARGET_ZCODE;
       @restore_undo i;
       #Ifnot; ! TARGET_GLULX
       @restoreundo i;
       i = (~~i);
       #Endif; ! TARGET_
       if (i == 0) {
         .UndoFailed;
           L__M(##Miscellany, 7);
       }
       jump FreshInput;
   }
   #Ifdef TARGET_ZCODE;
   @save_undo i;
   #Ifnot; ! TARGET_GLULX
   @saveundo i;
   if (i == -1) {
       GGRecoverObjects();
       i = 2;
   }
   else  i = (~~i);
   #Endif; ! TARGET_
   just_undone = 0;
   undo_flag = 2;
   if (i == -1) undo_flag = 0;
   if (i == 0) undo_flag = 1;
   if (i == 2) {
       RestoreColours();
       #Ifdef TARGET_ZCODE;
       style bold;
       #Ifnot; ! TARGET_GLULX
       glk($0086, 4); ! set subheader style
       #Endif; ! TARGET_
       print (name) location, "^";
       #Ifdef TARGET_ZCODE;
       style roman;
       #Ifnot; ! TARGET_GLULX
       glk($0086, 0); ! set normal style
       #Endif; ! TARGET_
       L__M(##Miscellany, 13);
       just_undone = 1;
       jump FreshInput;
   }
   #Endif; ! V5

   return nw;

 .DoOops;
   if (oops_from == 0) {
       L__M(##Miscellany, 14);
       jump FreshInput;
   }
   if (nw == 1) {
       L__M(##Miscellany, 15);
       jump FreshInput;
   }
   if (nw > 2) {
       L__M(##Miscellany, 16);
       jump FreshInput;
   }

   ! So now we know: there was a previous mistake, and the player has
   ! attempted to correct a single word of it.

   for (i=0 : i<INPUT_BUFFER_LEN : i++) buffer2->i = a_buffer->i;
   #Ifdef TARGET_ZCODE;
   x1 = a_table->9;  ! Start of word following "oops"
   x2 = a_table->8;  ! Length of word following "oops"
   #Ifnot; ! TARGET_GLULX
   x1 = a_table-->6; ! Start of word following "oops"
   x2 = a_table-->5; ! Length of word following "oops"
   #Endif; ! TARGET_

   ! Repair the buffer to the text that was in it before the "oops"
   ! was typed:

   for (i=0 : i<64 : i++) a_buffer->i = oops_workspace->i;
   Tokenise__(a_buffer,a_table);

   ! Work out the position in the buffer of the word to be corrected:

   #Ifdef TARGET_ZCODE;
   w = a_table->(4*oops_from + 1); ! Start of word to go
   w2 = a_table->(4*oops_from);    ! Length of word to go
   #Ifnot; ! TARGET_GLULX
   w = a_table-->(3*oops_from);      ! Start of word to go
   w2 = a_table-->(3*oops_from - 1); ! Length of word to go
   #Endif; ! TARGET_

   ! Write spaces over the word to be corrected:

   for (i=0 : i<w2 : i++) a_buffer->(i+w) = ' ';

   if (w2 < x2) {
       ! If the replacement is longer than the original, move up...
       for (i=INPUT_BUFFER_LEN-1 : i>=w+x2 : i--)
           a_buffer->i = a_buffer->(i-x2+w2);

       ! ...increasing buffer size accordingly.
       #Ifdef TARGET_ZCODE;
       a_buffer->1 = (a_buffer->1) + (x2-w2);
       #Ifnot; ! TARGET_GLULX
       a_buffer-->0 = (a_buffer-->0) + (x2-w2);
       #Endif; ! TARGET_
   }

   ! Write the correction in:

   for (i=0 : i<x2 : i++) a_buffer->(i+w) = buffer2->(i+x1);

   Tokenise__(a_buffer, a_table);
   #Ifdef TARGET_ZCODE;
   nw = a_table->1;
   #Ifnot; ! TARGET_GLULX
   nw = a_table-->0;
   #Endif; ! TARGET_

   return nw;
]; ! end of Keyboard
#Endif;