spiegel-keyman/developer/src/tike/debug/Keyman.UI.Debug.CharacterGridRenderer.pas
Marc Durdin 7e41ecb358 feat(developer): LDML keyboard test window marker support
Fixes #10458.

Refactors the LDML keyboard test window significantly, in order to be
able to support markers. Cleans up the state machine for the debug
window in the process, as that was complicating the changes. Removes
a lot of extraneous code in the debug window.
2024-01-23 12:42:28 +07:00

290 lines
7.6 KiB
ObjectPascal

unit Keyman.UI.Debug.CharacterGridRenderer;
interface
uses
debugdeadkeys,
System.Generics.Collections,
System.SysUtils,
System.Types,
Vcl.Graphics,
Vcl.Grids;
type
TCharacterGridRenderer = class sealed
public
const
CURSOR_COL_WIDTH = 5;
CELL_DEADKEY = 1;
CELL_CURSOR = 2;
CELL_SELECTED = 4;
class procedure Fill(grid: TStringGrid; const text: string;
deadkeys: TDebugDeadkeyInfoList;
SelStart, SelLength, SelAnchor: Integer;
DeadkeysAreCalledMarkers: Boolean = False); static;
class procedure Render(grid: TStringGrid; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState; CharFont: TFont); static;
class procedure Size(grid: TStringGrid; CharFont: TFont); static;
end;
implementation
uses
Unicode;
class procedure TCharacterGridRenderer.Fill(grid: TStringGrid;
const text: string; deadkeys: TDebugDeadkeyInfoList;
SelStart, SelLength, SelAnchor: Integer;
DeadkeysAreCalledMarkers: Boolean);
type
TCellType = (ctChar, ctDeadkey);
TCell = record
case CellType: TCellType of
ctChar: (ch: Integer); // UTF-32
ctDeadkey: (dk: TDeadKeyInfo);
end;
function Cut(Start, Finish: Integer): TArray<TCell>;
var
x, n: Integer;
begin
SetLength(Result, Finish-Start);
x := Start; n := 0;
while x < Finish do
begin
if (x < Finish - 1) and Uni_IsSurrogate1(text[x+1]) and Uni_IsSurrogate2(text[x+2]) then
begin
Result[n].CellType := ctChar;
Result[n].ch := Uni_SurrogateToUTF32(text[x+1], text[x+2]);
Inc(x);
end
else if text[x+1] = #$FFFC then
begin
Result[n].CellType := ctDeadkey;
Result[n].dk := deadkeys.GetFromPosition(x);
end
else
begin
Result[n].CellType := ctChar;
Result[n].ch := Ord(text[x+1]);
end;
Inc(n);
Inc(x);
end;
SetLength(Result, n);
end;
procedure FillGrid(const cells: TArray<TCell>; flags: Integer; var x: Integer);
var
cell: TCell;
begin
for cell in cells do
begin
if cell.CellType = ctDeadkey then
begin
grid.Objects[x, 0] := Pointer(flags or CELL_DEADKEY);
//Assert(Assigned(cell.dk));
if not Assigned(cell.dk)
then grid.Cells[x, 0] := '???'
else grid.Cells[x, 0] := cell.dk.Deadkey.Name;
if DeadkeysAreCalledMarkers
then grid.Cells[x, 1] := 'Marker'
else grid.Cells[x, 1] := 'Deadkey';
end
else
begin
grid.Objects[x, 0] := Pointer(flags);
grid.Cells[x, 0] := Uni_UTF32CharToUTF16(cell.ch);
grid.Cells[x, 1] := 'U+'+IntToHex(cell.ch, 4);
end;
grid.ColWidths[x] := grid.DefaultColWidth;
Inc(x);
end;
end;
procedure FillGridCursor(var x: Integer);
begin
grid.Objects[x,0] := Pointer(CELL_CURSOR);
grid.Cells[x,0] := '|';
grid.Cells[x,1] := '|';
grid.ColWidths[x] := CURSOR_COL_WIDTH;
Inc(x);
end;
var
MaxCols: Integer;
FirstSel, LastSel: Integer;
Before, Selection, After: TArray<TCell>;
X: Integer;
LB: Integer;
LS: Integer;
LA: Integer;
begin
MaxCols := (grid.ClientWidth - CURSOR_COL_WIDTH - 1) div (grid.DefaultColWidth + 1);
FirstSel := SelStart;
LastSel := SelStart + SelLength;
// We have three runs: before selection, in selection, after selection
// There are three possible modes:
// * No selection
// * Selection, cursor at beginning
// * Selection, cursor at end
//
// We also have a cursor cell (|), which is between two of the runs
// When there are too many cells to fit, we want to show:
// * No selection:
// <before[n..before.length]>|<after[1]>
// * With selection, cursor at beginning:
// <before[n..before.length]>|<selection[1]>
// * With selection, cursor at end:
// <before[n..before.length]><selection[m..selection.length]>|<after[1]>
Before := Cut(0, FirstSel);
Selection := Cut(FirstSel, LastSel);
After := Cut(LastSel, Text.Length);
// Trim arrays
LB := Length(Before);
LS := Length(Selection);
LA := Length(After);
while LB + LS + LA > MaxCols do
begin
if LA > 1 then
Dec(LA)
else if LB > 0 then
Dec(LB)
else if LS > 1 then
Dec(LS);
end;
if LB < Length(Before) then Before := Copy(Before, Length(Before)-LB, LB);
if LS < Length(Selection) then Selection := Copy(Selection, Length(Selection)-LS, LS);
SetLength(After, LA);
// Fill the grid
grid.ColCount := LB + LS + LA + 1; // include cursor cell
X := 0;
FillGrid(Before, 0, X);
if SelAnchor = SelStart then
FillGridCursor(X);
FillGrid(Selection, CELL_SELECTED, X);
if SelAnchor <> SelStart then
FillGridCursor(X);
FillGrid(After, 0, X);
(*
J := 0; I := SelStart + SelLength; // I is 1-based Delphi string index
while (J < MaxCols) and (I > SelStart) do
begin
if (I > 1) and Uni_IsSurrogate2(s[I]) and Uni_IsSurrogate1(s[I-1]) then
Dec(I);
Inc(J); Dec(I);
end;
if J = 0 then
begin
sgChars.ColCount := 1;
sgChars.Objects[0,0] := Pointer(0);
sgChars.Cells[0,0] := '';
sgChars.Cells[0,1] := '';
Exit;
end
else
sgChars.ColCount := J + 1;
Inc(I);
J := 0;
while J < sgChars.ColCount do
begin
if I = memo.SelStart + 1 then
begin
sgChars.Objects[J, 0] := Pointer(2);
sgChars.Cells[J, 0] := '|';
sgChars.Cells[J, 1] := 'Cursor';
Inc(J);
end;
if Ord(S[I]) = $FFFC then
begin
sgChars.Objects[J, 0] := Pointer(1);
sgChars.Cells[J, 0] := '???';
for K := 0 to FDeadkeys.Count-1 do
if FDeadkeys[K].Position = I-1 then
begin
sgChars.Cells[J, 0] := FDeadkeys[K].Deadkey.Name;
break;
end;
sgChars.Cells[J, 1] := 'Deadkey';
end
else if Uni_IsSurrogate1(s[I]) and (I < Length(s)) and Uni_IsSurrogate2(s[I+1]) then
begin
sgChars.Objects[J, 0] := Pointer(0);
sgChars.Cells[J, 0] := Copy(s, I ,2);
sgChars.Cells[J, 1] := 'U+'+IntToHex(Uni_SurrogateToUTF32(s[I], s[I+1]), 5);
Inc(I);
end
else
begin
sgChars.Objects[J, 0] := Pointer(0);
sgChars.Cells[J, 0] := s[I];
sgChars.Cells[J, 1] := 'U+'+IntToHex(Ord(s[i]), 4);
end;
Inc(J); Inc(I);
end;
*)
end;
class procedure TCharacterGridRenderer.Render(grid: TStringGrid; ACol,
ARow: Integer; Rect: TRect; State: TGridDrawState; CharFont: TFont);
var
flags: Integer;
begin
flags := Integer(grid.Objects[ACol, 0]);
if (ARow = 0) and ((flags and CELL_DEADKEY) = 0)
then grid.Canvas.Font := CharFont
else grid.Canvas.Font := grid.Font;
grid.Canvas.Font.Color := clWindowText;
if (flags and CELL_SELECTED) <> 0 then
begin
grid.Canvas.Brush.Color := clHighlight;
if (flags and CELL_DEADKEY) <> 0
then grid.Canvas.Font.Color := clYellow
else grid.Canvas.Font.Color := clHighlightText;
end
else
begin
grid.Canvas.Brush.Color := clWindow;
if (flags and CELL_DEADKEY) <> 0 then
begin
grid.Canvas.Font.Color := clRed;
end
else if (flags and CELL_CURSOR) <> 0 then
begin
grid.Canvas.Font.Color := clGreen;
grid.Canvas.Brush.Color := clGreen;
end;
end;
grid.Canvas.TextRect(Rect,
(Rect.Right + Rect.Left - grid.Canvas.TextWidth(grid.Cells[ACol, ARow])) div 2,
(Rect.Top + Rect.Bottom - grid.Canvas.TextHeight(grid.Cells[ACol, ARow])) div 2,
grid.Cells[ACol, ARow]);
end;
class procedure TCharacterGridRenderer.Size(grid: TStringGrid; CharFont: TFont);
begin
grid.Canvas.Font := CharFont;
grid.RowHeights[0] := grid.Canvas.TextHeight('A') + 4;
grid.Canvas.Font := grid.Font;
grid.RowHeights[1] := grid.Canvas.TextHeight('A') + 4;
grid.Height := grid.RowHeights[0] + grid.RowHeights[1] + 4;
end;
end.