hedgewars/uChat.pas
changeset 10920 11a28d22985f
parent 10919 8aed2bfc43c5
child 10921 05e6f3b02612
equal deleted inserted replaced
10919:8aed2bfc43c5 10920:11a28d22985f
    45     s: shortstring;
    45     s: shortstring;
    46     Color: TSDL_Color;
    46     Color: TSDL_Color;
    47     end;
    47     end;
    48     TChatCmd = (ccQuit, ccPause, ccFinish, ccShowHistory, ccFullScreen);
    48     TChatCmd = (ccQuit, ccPause, ccFinish, ccShowHistory, ccFullScreen);
    49 
    49 
    50 type TInputStrL = array[0..260] of byte;
       
    51 
       
    52 var Strs: array[0 .. MaxStrIndex] of TChatLine;
    50 var Strs: array[0 .. MaxStrIndex] of TChatLine;
    53     MStrs: array[0 .. MaxStrIndex] of shortstring;
    51     MStrs: array[0 .. MaxStrIndex] of shortstring;
    54     LocalStrs: array[0 .. MaxStrIndex] of shortstring;
    52     LocalStrs: array[0 .. MaxStrIndex] of shortstring;
    55     missedCount: LongWord;
    53     missedCount: LongWord;
    56     lastStr: LongWord;
    54     lastStr: LongWord;
    57     localLastStr: LongInt;
    55     localLastStr: LongInt;
    58     history: LongInt;
    56     history: LongInt;
    59     visibleCount: LongWord;
    57     visibleCount: LongWord;
    60     InputStr: TChatLine;
    58     InputStr: TChatLine;
    61     InputStrL: TInputStrL; // for full str + 4-byte utf-8 char
       
    62     ChatReady: boolean;
    59     ChatReady: boolean;
    63     showAll: boolean;
    60     showAll: boolean;
    64     liveLua: boolean;
    61     liveLua: boolean;
    65     ChatHidden: boolean;
    62     ChatHidden: boolean;
    66     firstDraw: boolean;
    63     firstDraw: boolean;
    95 
    92 
    96 
    93 
    97 const Padding  = 2;
    94 const Padding  = 2;
    98       ClHeight = 2 * Padding + 16; // font height
    95       ClHeight = 2 * Padding + 16; // font height
    99 
    96 
       
    97 // relevant for UTF-8 handling
       
    98 function IsFirstCharByte(b: byte): boolean; inline;
       
    99 begin
       
   100     // based on https://en.wikipedia.org/wiki/UTF-8#Description
       
   101     IsFirstCharByte:= (b and $C0) <> $80;
       
   102 end;
       
   103 
   100 function charIsForHogSpeech(c: char): boolean;
   104 function charIsForHogSpeech(c: char): boolean;
   101 begin
   105 begin
   102 exit((c = '"') or (c = '''') or (c = '-'));
   106 exit((c = '"') or (c = '''') or (c = '-'));
   103 end;
   107 end;
   104 
   108 
   572     GameState:= gsGame;
   576     GameState:= gsGame;
   573     ResetKbd;
   577     ResetKbd;
   574 end;
   578 end;
   575 
   579 
   576 procedure DelBytesFromInputStrBack(endIdx: integer; count: byte);
   580 procedure DelBytesFromInputStrBack(endIdx: integer; count: byte);
   577 var i, startIdx: integer;
   581 var startIdx: integer;
   578 begin
   582 begin
   579     // nothing to do if count is 0
   583     // nothing to do if count is 0
   580     if count = 0 then
   584     if count = 0 then
   581         exit;
   585         exit;
   582 
   586 
   584     startIdx:= endIdx - (count - 1);
   588     startIdx:= endIdx - (count - 1);
   585 
   589 
   586     // delete bytes from string
   590     // delete bytes from string
   587     Delete(InputStr.s, startIdx, count);
   591     Delete(InputStr.s, startIdx, count);
   588 
   592 
   589     // wipe utf8 info for deleted char
       
   590     InputStrL[endIdx]:= InputStrLNoPred;
       
   591 
       
   592     // shift utf8 char info to reflect new string
       
   593     for i:= endIdx + 1 to Length(InputStr.s) + count do
       
   594         begin
       
   595         if InputStrL[i] <> InputStrLNoPred then
       
   596             begin
       
   597             InputStrL[i-count]:= InputStrL[i] - count;
       
   598             InputStrL[i]:= InputStrLNoPred;
       
   599             end;
       
   600         end;
       
   601 
       
   602     SetLine(InputStr, InputStr.s, true);
   593     SetLine(InputStr, InputStr.s, true);
   603 end;
   594 end;
   604 
   595 
   605 // returns count of removed bytes
   596 procedure MoveCursorToPreviousChar();
   606 function DelCharFromInputStr(idx: integer): integer;
   597 begin
   607 var btw: byte;
   598     if cursorPos > 0 then
   608 begin
   599         begin
   609     // note: idx is always at last byte of utf8 chars. cuz relevant for InputStrL
   600         while (not IsFirstCharByte(byte(InputStr.s[cursorPos]))) do
   610 
   601             begin
   611     if (Length(InputStr.s) < 1) or (idx < 1) or (idx > Length(InputStr.s)) then
   602             dec(cursorPos);
   612         exit(0);
   603             end;
   613 
   604         dec(cursorPos);
   614     btw:= byte(idx) - InputStrL[idx];
   605         end;
   615 
   606 end;
   616     DelCharFromInputStr:= btw;
   607 
   617 
   608 procedure MoveCursorToNextChar();
   618     DelBytesFromInputStrBack(idx, btw);
   609 begin
   619 end;
   610     if cursorPos <  Length(InputStr.s) then
   620 
   611         begin
   621 // unchecked
   612         inc(cursorPos, 2);
   622 procedure DoCursorStepForward();
   613         while (cursorPos <  Length(InputStr.s)) and (not IsFirstCharByte(byte(InputStr.s[cursorPos]))) do
   623 begin
   614             begin
   624     // go to end of next utf8-char
   615             inc(cursorPos);
   625     repeat
   616             end;
   626         inc(cursorPos);
   617         dec(cursorPos);
   627     until InputStrL[cursorPos] <> InputStrLNoPred;
   618         end;
   628 end;
   619 end;
   629 
   620 
   630 procedure DeleteSelected();
   621 procedure DeleteSelected();
   631 begin
   622 begin
   632     if (selectedPos >= 0) and (cursorPos <> selectedPos) then
   623     if (selectedPos >= 0) and (cursorPos <> selectedPos) then
   633         begin
   624         begin
   634         DelBytesFromInputStrBack(max(cursorPos, selectedPos), abs(selectedPos-cursorPos));
   625         DelBytesFromInputStrBack(max(cursorPos, selectedPos), abs(selectedPos-cursorPos));
   635         cursorPos:= min(cursorPos, selectedPos);
   626         cursorPos:= min(cursorPos, selectedPos);
   636         ResetSelection();
   627         end;
   637         end;
   628     ResetSelection();
   638     UpdateCursorCoords();
   629     UpdateCursorCoords();
   639 end;
   630 end;
   640 
   631 
   641 procedure HandleSelection(enabled: boolean);
   632 procedure HandleSelection(enabled: boolean);
   642 begin
   633 begin
   652 type TCharSkip = ( none, wspace, numalpha, special );
   643 type TCharSkip = ( none, wspace, numalpha, special );
   653 
   644 
   654 function GetInputCharSkipClass(index: LongInt): TCharSkip;
   645 function GetInputCharSkipClass(index: LongInt): TCharSkip;
   655 var  c: char;
   646 var  c: char;
   656 begin
   647 begin
   657     // multi-byte chars counts as letter
       
   658     if (index > 1) and (InputStrL[index] <> index - 1) then
       
   659         exit(numalpha);
       
   660 
       
   661     c:= InputStr.s[index];
   648     c:= InputStr.s[index];
   662 
   649 
   663     // non-ascii counts as letter
   650     // non-ascii counts as letter
   664     if c > #127 then
   651     if c > #127 then
   665         exit(numalpha);
   652         exit(numalpha);
   696     // skip trailing whitespace, similar to Qt
   683     // skip trailing whitespace, similar to Qt
   697     while (skip = wspace) and (cursorPos > 0) do
   684     while (skip = wspace) and (cursorPos > 0) do
   698         begin
   685         begin
   699         skip:= GetInputCharSkipClass(cursorPos);
   686         skip:= GetInputCharSkipClass(cursorPos);
   700         if skip = wspace then
   687         if skip = wspace then
   701             cursorPos:= InputStrL[cursorPos];
   688             MoveCursorToPreviousChar();
   702         end;
   689         end;
   703     // skip same-type chars
   690     // skip same-type chars
   704     while (cursorPos > 0) and (GetInputCharSkipClass(cursorPos) = skip) do
   691     while (cursorPos > 0) and (GetInputCharSkipClass(cursorPos) = skip) do
   705         cursorPos:= InputStrL[cursorPos];
   692         MoveCursorToPreviousChar();
   706     end
   693     end
   707 else
   694 else
   708     begin
   695     begin
   709     // skip same-type chars
   696     // skip same-type chars
   710     while cursorPos < Length(InputStr.s) do
   697     while cursorPos < Length(InputStr.s) do
   711         begin
   698         begin
   712         DoCursorStepForward();
   699         MoveCursorToNextChar();
   713         if (GetInputCharSkipClass(cursorPos) <> skip) then
   700         if (GetInputCharSkipClass(cursorPos) <> skip) then
   714             begin
   701             begin
   715             // go back 1 char
   702             MoveCursorToPreviousChar();
   716             cursorPos:= InputStrL[cursorPos];
       
   717             break;
   703             break;
   718             end;
   704             end;
   719         end;
   705         end;
   720     // skip trailing whitespace, similar to Qt
   706     // skip trailing whitespace, similar to Qt
   721     while cursorPos < Length(InputStr.s) do
   707     while cursorPos < Length(InputStr.s) do
   722         begin
   708         begin
   723         DoCursorStepForward();
   709         MoveCursorToNextChar();
   724         if (GetInputCharSkipClass(cursorPos) <> wspace) then
   710         if (GetInputCharSkipClass(cursorPos) <> wspace) then
   725             begin
   711             begin
   726             // go back 1 char
   712             MoveCursorToPreviousChar();
   727             cursorPos:= InputStrL[cursorPos];
       
   728             break;
   713             break;
   729             end;
   714             end;
   730         end;
   715         end;
   731     end;
   716     end;
   732 end;
   717 end;
   744         selection:= copy(InputStr.s, min(CursorPos, selectedPos) + 1, abs(CursorPos - selectedPos));
   729         selection:= copy(InputStr.s, min(CursorPos, selectedPos) + 1, abs(CursorPos - selectedPos));
   745         CopyToClipboard(selection);
   730         CopyToClipboard(selection);
   746         end;
   731         end;
   747 end;
   732 end;
   748 
   733 
   749 // regenerate UTF-8 info for current input string
       
   750 procedure RegenInputStrL();
       
   751 var i, n, lastL: integer;
       
   752     b: byte;
       
   753 begin
       
   754     lastL:= InputStrLNoPred;
       
   755     i:= 0;
       
   756     n:= Length(InputStr.s);
       
   757     while i <= n do
       
   758         begin
       
   759 
       
   760         // also save lastL if we reached the end
       
   761         if i = n then
       
   762             b:= 0
       
   763         else
       
   764             b:= byte(InputStr.s[i+1]);
       
   765 
       
   766         // start of char, based on https://en.wikipedia.org/wiki/UTF-8#Description
       
   767         if (b and $C0) <> $80 then
       
   768             begin
       
   769             InputStrL[i]:= lastL;
       
   770             lastL:= i;
       
   771             end
       
   772         else
       
   773             InputStrL[i]:= InputStrLNoPred;
       
   774 
       
   775         inc(i);
       
   776         end;
       
   777 end;
       
   778 
       
   779 procedure InsertIntoInputStr(s: shortstring);
   734 procedure InsertIntoInputStr(s: shortstring);
   780 var i, l, il, lastc: integer;
   735 var l, lastc: integer;
   781 begin
   736 begin
   782     // safe length for string
   737     // safe length for string
   783     l:= min(MaxInputStrLen-cursorPos, Length(s));
   738     l:= min(MaxInputStrLen-cursorPos, Length(s));
   784     s:= copy(s,1,l);
   739     // SetLength(s, l);
       
   740     s[0]:= char(l);
   785 
   741 
   786     // insert string truncated to safe length
   742     // insert string truncated to safe length
       
   743     Insert(s, InputStr.s, cursorPos + 1);
   787     // TODO: honor utf8, don't break utf8 chars when shifting chars beyond limit
   744     // TODO: honor utf8, don't break utf8 chars when shifting chars beyond limit
   788     Insert(s, InputStr.s, cursorPos + 1);
       
   789     if Length(InputStr.s) > MaxInputStrLen then
   745     if Length(InputStr.s) > MaxInputStrLen then
   790         InputStr.s[0]:= char(MaxInputStrLen);
   746         InputStr.s[0]:= char(MaxInputStrLen);
   791 
   747 
   792     SetLine(InputStr, InputStr.s, true);
   748     SetLine(InputStr, InputStr.s, true);
   793 
       
   794     // update InputStrL to also reflect whatever was inserted
       
   795     RegenInputStrL();
       
   796 
   749 
   797     // move cursor to end of inserted string
   750     // move cursor to end of inserted string
   798     lastc:= MaxInputStrLen;
   751     lastc:= MaxInputStrLen;
   799     cursorPos:= min(lastc, cursorPos + l);
   752     cursorPos:= min(lastc, cursorPos + l);
   800     UpdateCursorCoords();
   753     UpdateCursorCoords();
   833     case Sym of
   786     case Sym of
   834         SDLK_BACKSPACE:
   787         SDLK_BACKSPACE:
   835             begin
   788             begin
   836             if selectedPos < 0 then
   789             if selectedPos < 0 then
   837                 begin
   790                 begin
       
   791                 HandleSelection(true);
       
   792 
       
   793                 // delete more if ctrl is held
   838                 if ctrl then
   794                 if ctrl then
   839                     skip:= GetInputCharSkipClass(cursorPos);
   795                     SkipInputChars(GetInputCharSkipClass(cursorPos), true)
   840 
   796                 else
   841                 // remove char before cursor
   797                     MoveCursorToPreviousChar();
   842                 dec(cursorPos, DelCharFromInputStr(cursorPos));
   798 
       
   799                 end;
       
   800 
       
   801             DeleteSelected();
       
   802             UpdateCursorCoords();
       
   803             end;
       
   804         SDLK_DELETE:
       
   805             begin
       
   806             if selectedPos < 0 then
       
   807                 begin
       
   808                 HandleSelection(true);
   843 
   809 
   844                 // delete more if ctrl is held
   810                 // delete more if ctrl is held
   845                 if ctrl and (selectedPos < 0) then
   811                 if ctrl then
   846                     begin
   812                     SkipInputChars(GetInputCharSkipClass(cursorPos), false)
   847                     HandleSelection(true);
       
   848                     SkipInputChars(skip, true);
       
   849                     DeleteSelected();
       
   850                     end
       
   851                 else
   813                 else
   852                     UpdateCursorCoords();
   814                     MoveCursorToNextChar();
   853 
   815 
   854                 end
   816                 end;
   855             else
   817 
   856                 DeleteSelected();
   818             DeleteSelected();
   857             end;
   819             UpdateCursorCoords();
   858         SDLK_DELETE:
       
   859             begin
       
   860             if selectedPos < 0 then
       
   861                 begin
       
   862                 // remove char after cursor
       
   863                 if cursorPos < Length(InputStr.s) then
       
   864                     begin
       
   865                     DoCursorStepForward();
       
   866                     if ctrl then
       
   867                         skip:= GetInputCharSkipClass(cursorPos);
       
   868 
       
   869                     // delete char
       
   870                     dec(cursorPos, DelCharFromInputStr(cursorPos));
       
   871 
       
   872                     // delete more if ctrl is held
       
   873                     if ctrl and (cursorPos < Length(InputStr.s)) then
       
   874                         begin
       
   875                         HandleSelection(true);
       
   876                         SkipInputChars(skip, false);
       
   877                         DeleteSelected();
       
   878                         end;
       
   879                     end
       
   880                 else
       
   881                     UpdateCursorCoords();
       
   882                 end
       
   883             else
       
   884                 DeleteSelected();
       
   885             end;
   820             end;
   886         SDLK_ESCAPE:
   821         SDLK_ESCAPE:
   887             begin
   822             begin
   888             if Length(InputStr.s) > 0 then
   823             if Length(InputStr.s) > 0 then
   889                 begin
   824                 begin
   890                 SetLine(InputStr, '', true);
   825                 SetLine(InputStr, '', true);
   891                 FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
       
   892                 ResetCursor();
   826                 ResetCursor();
   893                 end
   827                 end
   894             else CleanupInput
   828             else CleanupInput
   895             end;
   829             end;
   896         SDLK_RETURN, SDLK_KP_ENTER:
   830         SDLK_RETURN, SDLK_KP_ENTER:
   897             begin
   831             begin
   898             if Length(InputStr.s) > 0 then
   832             if Length(InputStr.s) > 0 then
   899                 begin
   833                 begin
   900                 AcceptChatString(InputStr.s);
   834                 AcceptChatString(InputStr.s);
   901                 SetLine(InputStr, '', false);
   835                 SetLine(InputStr, '', false);
   902                 FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
       
   903                 ResetCursor();
   836                 ResetCursor();
   904                 end;
   837                 end;
   905             CleanupInput
   838             CleanupInput
   906             end;
   839             end;
   907         SDLK_UP, SDLK_DOWN:
   840         SDLK_UP, SDLK_DOWN:
   910             if (Sym = SDLK_DOWN) and (history > 0) then dec(history);
   843             if (Sym = SDLK_DOWN) and (history > 0) then dec(history);
   911             index:= localLastStr - history + 1;
   844             index:= localLastStr - history + 1;
   912             if (index > localLastStr) then
   845             if (index > localLastStr) then
   913                 begin
   846                 begin
   914                 SetLine(InputStr, '', true);
   847                 SetLine(InputStr, '', true);
   915                 FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
       
   916                 end
   848                 end
   917             else
   849             else
   918                 begin
   850                 begin
   919                 SetLine(InputStr, LocalStrs[index], true);
   851                 SetLine(InputStr, LocalStrs[index], true);
   920                 RegenInputStrL();
       
   921                 end;
   852                 end;
   922             cursorPos:= Length(InputStr.s);
   853             cursorPos:= Length(InputStr.s);
   923             ResetSelection();
   854             ResetSelection();
   924             UpdateCursorCoords();
   855             UpdateCursorCoords();
   925             end;
   856             end;
   958 
   889 
   959                 if selMode or (selectedPos < 0) then
   890                 if selMode or (selectedPos < 0) then
   960                     begin
   891                     begin
   961                     HandleSelection(selMode);
   892                     HandleSelection(selMode);
   962                     // go to end of previous utf8-char
   893                     // go to end of previous utf8-char
   963                     cursorPos:= InputStrL[cursorPos];
   894                     MoveCursorToPreviousChar();
   964                     end
   895                     end
   965                 else // if we're leaving selection mode, jump to its left end
   896                 else // if we're leaving selection mode, jump to its left end
   966                     begin
   897                     begin
   967                     cursorPos:= min(cursorPos, selectedPos);
   898                     cursorPos:= min(cursorPos, selectedPos);
   968                     ResetSelection();
   899                     ResetSelection();
   983                 begin
   914                 begin
   984 
   915 
   985                 if selMode or (selectedPos < 0) then
   916                 if selMode or (selectedPos < 0) then
   986                     begin
   917                     begin
   987                     HandleSelection(selMode);
   918                     HandleSelection(selMode);
   988                     DoCursorStepForward();
   919                     MoveCursorToNextChar();
   989                     end
   920                     end
   990                 else // if we're leaving selection mode, jump to its right end
   921                 else // if we're leaving selection mode, jump to its right end
   991                     begin
   922                     begin
   992                     cursorPos:= max(cursorPos, selectedPos);
   923                     cursorPos:= max(cursorPos, selectedPos);
   993                     ResetSelection();
   924                     ResetSelection();
  1140     if length(s) = 0 then
  1071     if length(s) = 0 then
  1141         SetLine(InputStr, '', true)
  1072         SetLine(InputStr, '', true)
  1142     else
  1073     else
  1143         begin
  1074         begin
  1144         SetLine(InputStr, '/team ', true);
  1075         SetLine(InputStr, '/team ', true);
  1145         // update InputStrL and cursor accordingly
       
  1146         // this allows cursor-jumping over '/team ' as if it was a single char
       
  1147         InputStrL[6]:= 0;
       
  1148         cursorPos:= 6;
  1076         cursorPos:= 6;
  1149         UpdateCursorCoords();
  1077         UpdateCursorCoords();
  1150         end;
  1078         end;
  1151 end;
  1079 end;
  1152 
  1080 
  1175     inputStr.s:= '';
  1103     inputStr.s:= '';
  1176     inputStr.Tex := nil;
  1104     inputStr.Tex := nil;
  1177     for i:= 0 to MaxStrIndex do
  1105     for i:= 0 to MaxStrIndex do
  1178         Strs[i].Tex := nil;
  1106         Strs[i].Tex := nil;
  1179 
  1107 
  1180     FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred);
       
  1181 
       
  1182     LastKeyPressTick:= 0;
  1108     LastKeyPressTick:= 0;
  1183     ResetCursor();
  1109     ResetCursor();
  1184 end;
  1110 end;
  1185 
  1111 
  1186 procedure freeModule;
  1112 procedure freeModule;