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; |
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 |