spiegel-keyman/developer/src/tike/debug/Keyman.UI.Debug.CharacterGridRenderer.pas
Marc Durdin 2add4d354d fix(developer): use U+0001 for deadkey markers in the debugger
Previously, we used U+FFFC as a marker in the debugger (for deadkeys and
LDML keyboard markers), which displayed as the letters OBJ in a small
dotted square. However, we switched the underlying component to
RichEdit, in order to better support rendering of many scripts (EDIT has
some problems). The RichEdit control silently converts U+FFFC to U+0020.
I switched to U+0001 as this is handled better, but has an unfortunate
missing-glyph marker.

In the future, it would be better to use a non-character marker
(leveraging RichEdit), but this is a much more complex change, and this
fix at least matches previous functionality.

Fixes: #13293
2025-02-24 10:38:33 +01:00

230 lines
6.1 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] = DeadKey_Marker 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);
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.