unit KBLVisualizerU; interface uses Windows, Forms, Graphics, SysUtils, StrUtils, Spin, ExtCtrls, Dialogs, StdCtrls, Controls, Classes, ComCtrls, Grids, Clipbrd, Messages; type TKBLEditForm = class(TForm) OpenDialog1: TOpenDialog; SaveDialog1: TSaveDialog; Px00Screen: TImage; Memo1: TMemo; ButtonOpen: TButton; ButtonSave: TButton; ButtonSaveAs: TButton; ButtonVisualize: TButton; Status: TListBox; SpinEdit1: TSpinEdit; LabelByMarkusBirth: TLabel; Unicodes: TStringGrid; ButtonNew: TButton; TrackR: TTrackBar; LabelR: TLabel; TrackG: TTrackBar; TrackB: TTrackBar; LabelG: TLabel; LabelB: TLabel; ButtonColorInsert: TButton; ButtonColorGet: TButton; ColorPanel: TPanel; LabelColorEditor: TLabel; LabelUnicodeTable: TLabel; LabelFile: TLabel; LabelSyntax: TLabel; TimerScroll: TTimer; procedure ButtonVisualizeClick(Sender: TObject); procedure ButtonOpenClick(Sender: TObject); procedure ButtonSaveAsClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure ButtonSaveClick(Sender: TObject); procedure UnicodesKeyPress(Sender: TObject; var Key: Char); procedure UnicodesDblClick(Sender: TObject); procedure ButtonNewClick(Sender: TObject); procedure TrackRChange(Sender: TObject); procedure TrackGChange(Sender: TObject); procedure TrackBChange(Sender: TObject); procedure ButtonColorInsertClick(Sender: TObject); procedure ButtonColorGetClick(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); procedure Memo1Change(Sender: TObject); procedure TimerScrollTimer(Sender: TObject); procedure Memo1Click(Sender: TObject); procedure Px00ScreenMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private { Private declarations } public { Public declarations } end; var KBLEditForm: TKBLEditForm; Screenshot: TBitmap; implementation {$R *.dfm} const Hexmap = '0123456789ABCDEF'; unicodefile = 'unicode-index.txt'; debug = false; CRLF = Chr(13)+Chr(10); type TArray = array of string; TSelection = record map: integer; row: integer; key: integer; end; TRowCol = record row, col: integer; end; TKey = record typ: char; value: string; width: integer; keycap: TColor; ink: TColor; legend: string; end; TRow = record keys: array of TKey; keycap: TColor; ink: TColor; end; TMap = record landscape: boolean; fullscreen: boolean; top, left, bottom, right: integer; rows, cols: integer; otop, oleft, obottom, oright: integer; keycap: TColor; ink: TColor; highlight: TColor; background: TColor; Data: array of TRow; end; var titlestore: AnsiString; exoc: boolean = false; procedure ClearScreen; var fn, t1, t2: string; tw, th: integer; hc, vc: integer; begin fn := ChangeFileExt(Application.ExeName,'.bmp'); with KBLEditForm.Px00Screen do begin if (FileExists(fn)) then begin Picture.LoadFromFile(fn); Picture.Bitmap.PixelFormat := pf24bit; end else begin t1 := ExtractFilename(fn); t2 := 'not found'; Canvas.Brush.Color := clWhite; Canvas.Brush.Style := bsSolid; Canvas.FillRect(Rect(0,0,Width,Height)); Canvas.Font.Color := clBlack; hc := width DIV 2; vc := height DIV 2; tw := Canvas.TextWidth(t1); th := Canvas.TextHeight(t1); Canvas.TextOut(hc-tw DIV 2,vc-th,t1); tw := Canvas.TextWidth(t2); Canvas.TextOut(hc-tw DIV 2,vc+2,t2); end; end; end; procedure Stat(s: string); begin if (debug) then begin with KBLEditForm.Status do begin Items.Add(s); TopIndex := Items.Count-3; end; end; end; function GetMemoRowCol(M: TMemo): TRowCol; begin Result.row := M.Perform(EM_LINEFROMCHAR, M.SelStart, 0); Result.col := M.SelStart - M.Perform(EM_LINEINDEX, Result.row, 0); end; procedure UpdateTitle; begin with KBLEditForm do begin if (Length(titlestore)=0) then titlestore := Caption; if (Length(LabelFile.Caption)>0) then Caption := titlestore + ' - [' + LabelFile.Caption + ']' else Caption := titlestore; end; end; function GetMap(x: integer): TArray; var i,j: integer; t: string; r: TArray; begin i := 0; SetLength(r, 200); for j:=0 to KBLEditForm.Memo1.Lines.Count do begin t := KBLEditForm.Memo1.Lines.Strings[j]; if (Length(t)>0) then begin if (UpCase(t[1])='M') AND (x>0) then Dec(x) else if (UpCase(t[1])='M') AND (x<=0) then Break; end; if (x=0) then begin r[i] := t; Inc(i); end; end; SetLength(r, i); Result := r; end; function Split(x, s: string): TArray; var i,p,o: integer; r: TArray; begin i := 0; o := 1; SetLength(r, 50); p := PosEx(s,x,o); while (p>0) do begin r[i] := Copy(x,o,p-o); Inc(i); o := p+1; p := PosEx(s,x,o); end; r[i] := Copy(x,o,Length(x)-o+1); Inc(i); SetLength(r, i); Result := r; end; function GetCursorPos: TSelection; var i: integer; test: char; m, r, k: integer; cpos: TRowCol; begin m := 0; r := 0; k := 0; cpos := GetMemoRowCol(KBLEditForm.Memo1); for i:=0 to cpos.row do begin if (Length(KBLEditForm.Memo1.Lines.Strings[i])>0) then begin test := UpCase(KBLEditForm.Memo1.Lines.Strings[i][1]); Stat('Memo1 #:'+IntToStr(i)+' - value: '+test); if (test='M') then begin Inc(m); r := 0; k := 0; end; if (test='R') then begin Inc(r); k := 0; end; if (test='K') OR (test='L') OR (test='S') then Inc(k); end; end; Result.map := m; Result.row := r; Result.key := k; end; function Hex2Col(h: string): TColor; var r,g,b: integer; begin h := UpperCase(h); r := (Pos(h[1],hexmap)-1)*16 + (Pos(h[2],hexmap)-1); g := (Pos(h[3],hexmap)-1)*16 + (Pos(h[4],hexmap)-1); b := (Pos(h[5],hexmap)-1)*16 + (Pos(h[6],hexmap)-1); Stat('In:'+h+' --- Out r:'+IntToStr(r)+' g:'+IntToStr(g)+' b:'+IntToStr(b)); Result := rgb(r, g, b); end; function Dec2Hex(i: integer): string; var s: string; begin s := ''; while i>15 do begin s := s + Hexmap[(i MOD 16)+1]; i := i DIV 16; end; s := Hexmap[i+1] + s; while Length(s) MOD 2>0 do s := '0' + s; Result := LowerCase(s); end; function Hex2Str(h: string): string; var c: integer; t: widestring; begin h := UpperCase(h); c := (Pos(h[1],hexmap)-1)*4096 + (Pos(h[2],hexmap)-1)*256 + (Pos(h[3],hexmap)-1)*16 + (Pos(h[4],hexmap)-1); t := Chr(c); Result := t; end; function ParseMap(m: TArray): TMap; var i, rows, keys: integer; l: TArray; r: TMap; begin SetLength(r.Data, 100); SetLength(l, 100); rows := 0; keys := 0; for i:=0 to Length(m)-1 do begin if (Length(m[i])>0) then begin l := Split(m[i], ','); if Length(l)>0 then begin if (l[0]='M') then begin if (Length(l)=17) then begin if (l[1]='0') then r.landscape := false else r.landscape := true; if (l[2]='0') then r.fullscreen := false else r.fullscreen := true; r.top := StrToInt(l[3]); r.left := StrToInt(l[4]); r.bottom := StrToInt(l[5]); r.right := StrToInt(l[6]); r.rows := StrToInt(l[7]); r.cols := StrToInt(l[8]); r.otop := StrToInt(l[9]); r.oleft := StrToInt(l[10]); r.obottom := StrToInt(l[11]); r.oright := StrToInt(l[12]); r.keycap := Hex2Col(l[13]); r.ink := Hex2Col(l[14]); r.highlight := Hex2Col(l[15]); r.background := Hex2Col(l[16]); end; end else if (l[0]='R') then begin if (rows>0) then SetLength(r.Data[rows-1].keys, keys); Inc(rows); with r.Data[rows-1] do begin SetLength(keys, 100); if (Length(l)>2) then keycap := Hex2Col(l[2]) else keycap := r.keycap; if (Length(l)>3) then ink := Hex2Col(l[3]) else ink := r.ink; end; keys := 0; end else if (l[0]='L') OR (l[0]='K') OR (l[0]='S') then begin with r.Data[rows-1].keys[keys] do begin typ := l[0][1]; value := l[1]; if (Length(l)>2) then width := StrToInt(l[2]) else width := 1; if (Length(l)>3) then keycap := Hex2Col(l[3]) else keycap := r.Data[rows-1].keycap; if (Length(l)>4) then ink := Hex2Col(l[4]) else ink := r.Data[rows-1].ink; if (Length(l)>5) then legend := l[5] else legend := Hex2Str(l[1]); end; Inc(keys); end; end; end; end; if (rows>0) then begin SetLength(r.Data[rows-1].keys, keys); SetLength(r.Data, rows); end; Result := r; end; function GetKeyRect(map: TMap; i,j: integer): TRect; const space = 1.5; var key: TKey; dt, dl, db, dr: double; // final rect as double mw, mh: double; // keymap width/height k, ki: integer; // key-index begin try ki := 1; for k:=0 to j-1 do ki := ki + map.Data[i].keys[k].width; key := map.Data[i].keys[j]; Stat('map: ('+IntToStr(map.left)+','+IntToStr(map.top)+'),('+IntToStr(map.right)+','+IntToStr(map.bottom)+')'); mw := map.right-map.left; mh := map.bottom-map.top; Stat('map: width:'+FloatToStr(mw)+' height:'+FloatToStr(mh)); dl := map.left + (mw/map.cols) * (ki-1) + space; dr := map.left + (mw/map.cols) * (ki-1+key.width) - space; dt := map.top + (mh/(map.rows)) * i + space; // ki=keyindex 1..x; i=row 0..x db := map.top + (mh/(map.rows)) * (i+1) - space; Result.left := Round(dl); Result.right := Round(dr)+1; Result.top := Round(dt); Result.bottom := Round(db)+1; except on e: Exception do begin ShowMessage('Exception: '+e.Message+CRLF+CRLF+'Something seems to be wrong with this map.'); exoc := true; Exit; end; end; end; procedure KeyRect(map: TMap; i,j,ki: integer; lbl: string; hicol: TColor); var top, left, bottom, right: integer; // final rect for key tw, th: integer; // Text width/height ow, oh: double; // output key width/height key: TKey; keyrect: TRect; begin try key := map.Data[i].keys[j]; keyrect := GetKeyRect(map, i, j); ow := keyrect.Right - keyrect.Left; oh := keyrect.Bottom - keyrect.Top; left := keyrect.Left; right := keyrect.Right; top := keyrect.Top; bottom := keyrect.Bottom; with KBLEditForm.Px00Screen.Canvas do begin Brush.Color := map.Data[i].keys[j].keycap; Font.Color := map.Data[i].keys[j].ink; Pen.Color := hicol; Pen.Style := psSolid; RoundRect(left, top, right, bottom,(right-left) DIV 4, (bottom-top) DIV 3); Brush.Style := bsClear; th := TextHeight(key.legend); tw := TextWidth(key.legend); TextRect(Rect(left,top,right,bottom),Round(left+(ow-tw)/2),Round(top+(oh-th)/2),key.legend); end; except on e: Exception do begin ShowMessage('Exception: '+e.Message+CRLF+CRLF+'Something seems to be wrong with this map.'); exoc := true; Exit; end; end; end; procedure VisMap(x: integer; highlight: TSelection); var m: TArray; map: TMap; i, j: integer; keyindex: integer; hicol: TColor; begin SetLength(m, 200); SetLength(map.Data, 200); m := GetMap(x); map := ParseMap(m); with KBLEditForm.Px00Screen.Canvas do begin Brush.Style := bsSolid; Brush.Color := map.background; Pen.Color := clBlack; Font.Name := 'Arial'; Font.Color := clBlack; Font.Height := ((map.bottom-map.top) DIV map.rows)-4; if (Font.Height>22) then Font.Height := 22; if (Font.Height<12) then Font.Height := 12; Rectangle(Rect(map.oleft, map.otop, map.oright+1, map.obottom+1)); FillRect(Rect(map.left, map.top, map.right+1, map.bottom+1)); for i:=0 to Length(map.Data)-1 do begin keyindex := 1; for j:=0 to Length(map.Data[i].keys)-1 do begin if (x=highlight.map) AND (i+1=highlight.row) AND (j+1=highlight.key) then begin hicol := clRed; Pen.Width := 2; end else begin hicol := clBlack; Pen.Width := 1; end; if (map.Data[i].keys[j].value<>'0000') then Keyrect(map, i, j, keyindex, map.Data[i].keys[j].legend, hicol); if (exoc) then begin exoc := false; Exit; end; Inc(keyindex, map.Data[i].keys[j].width); end; end; end; end; procedure TKBLEditForm.ButtonVisualizeClick(Sender: TObject); var pos: TSelection; begin ClearScreen; pos := GetCursorPos; VisMap(SpinEdit1.Value, pos); Memo1.SetFocus; end; procedure TKBLEditForm.ButtonOpenClick(Sender: TObject); var r: integer; begin if (Memo1.Modified) then begin r := Application.MessageBox('Do you want to save your work first?','Please confirm',MB_YESNO); if (r=IDYES) then begin if (ButtonSave.Enabled) then ButtonSaveClick(Sender) else ButtonSaveAsClick(Sender); end; end; if (OpenDialog1.Execute) then begin Memo1.Lines.LoadFromFile(OpenDialog1.FileName); Memo1.Modified := false; LabelFile.Caption := ExtractFileName(OpenDialog1.FileName); UpdateTitle; ButtonSave.Enabled := true; SpinEdit1.Value := 1; ClearScreen; end; end; procedure TKBLEditForm.ButtonSaveAsClick(Sender: TObject); begin SaveDialog1.FileName := OpenDialog1.FileName; if (SaveDialog1.Execute) then begin Memo1.Lines.SaveToFile(SaveDialog1.FileName); Memo1.Modified := false; end; end; procedure AddUnicode(code, desc: AnsiString); begin with KBLEditForm.Unicodes do begin RowCount := RowCount + 1; Cells[0, RowCount-1] := desc; Cells[1, RowCount-1] := code; end; end; procedure LoadUnicodes; var f: TextFile; t: AnsiString; d: integer; t2: TArray; begin d := 1; with KBLEditForm.Unicodes do begin SetLength(t2, 50); Cells[0,0] := 'Description'; Cells[1,0] := 'Unicode'; if (FileExists(unicodefile)) then begin try AssignFile(f, unicodefile); Reset(f); while Not Eof(f) do begin ReadLn(f, t); t2 := Split(t, Chr(9)); if (Length(t2[1])>2) then begin if (d>0) then Dec(d) else RowCount := RowCount + 1; Cells[0, RowCount-1] := Trim(t2[0]); Cells[1, RowCount-1] := Trim(t2[1]); end; end; CloseFile(f); except on e: Exception do ShowMessage('ERROR: Couldn''t load unicode list from '+unicodefile+'. ('+e.Message+')'); end; end; AddUnicode('', '-------------- useful PopOnTop codes follow here ---------------'); AddUnicode('0008', 'BS Backspace'); AddUnicode('000D', 'CR Carriage Return'); AddUnicode('F6DC', 'Next - Move cursor to next field'); AddUnicode('F6DD', 'Prev - Move cursor to previous field'); AddUnicode('F802', 'Home - Move cursor to start of field/line'); AddUnicode('F803', 'End - Move cursor to end of field/line'); AddUnicode('F807', 'Left - Move cursor left one character'); AddUnicode('F808', 'Right - Move cursor right one character'); AddUnicode('F809', 'Up - Move cursor up one character'); AddUnicode('F80A', 'Down - Move cursor down one character'); AddUnicode('F7FE', 'Horizontal - Move keyboard horizontally'); AddUnicode('F7FF', 'Vertical - Move keyboard vertically'); end; end; procedure TKBLEditForm.FormCreate(Sender: TObject); var ts: TSelection; begin ts.map := 0; ts.row := 0; ts.key := 0; if (debug) then Status.Visible := true; TimerScroll.Enabled := false; LabelFile.Caption := ''; LabelSyntax.Caption := ''; ClearScreen; VisMap(1, ts); LoadUnicodes; end; procedure TKBLEditForm.ButtonSaveClick(Sender: TObject); begin if (Length(OpenDialog1.FileName)>0) then begin Memo1.Lines.SaveToFile(OpenDialog1.FileName); Memo1.Modified := false; end; end; procedure TKBLEditForm.UnicodesKeyPress(Sender: TObject; var Key: Char); var i: integer; s: TGridRect; begin for i:=1 to Unicodes.RowCount-1 do begin if (UpCase(Unicodes.Cells[0,i][1])=UpCase(Key)) then begin s.Left := 0; s.Right := 1; s.Top := i; s.Bottom := i; Unicodes.Selection := s; Unicodes.TopRow := i; Break; end; end; end; procedure InsertIntoMemo(x: AnsiString); var m: AnsiString; ss, sl: longint; tmp: TClipboard; begin tmp := Clipboard; with KBLEditForm.Memo1 do begin m := Lines.Text; ss := SelStart; sl := SelLength; if (sl>0) then begin CutToClipboard; // Delete(m, ss+1, sl); end; { Insert(x, m, ss+1); Lines.Text := m; } Clipboard.SetTextBuf(PChar(x)); PasteFromClipboard; SelStart := ss; SelLength := Length(x); SetFocus; end; SetClipboard(tmp); end; procedure TKBLEditForm.UnicodesDblClick(Sender: TObject); var s: TGridRect; r: integer; c: string; begin s := Unicodes.Selection; r := s.Top; c := Unicodes.Cells[1,r]; InsertIntoMemo(c); end; procedure TKBLEditForm.ButtonNewClick(Sender: TObject); begin if (Memo1.Modified) then if (Application.MessageBox('Are you sure to clear everything?', 'Please confirm', MB_YESNO)=IDNO) then Exit; Memo1.Clear; ButtonSave.Enabled := false; Memo1.Modified := false; LabelFile.Caption := ''; UpdateTitle; ClearScreen; Memo1.SetFocus; end; procedure UpdateColor; var r,g,b: integer; begin with KBLEditForm do begin r := TrackR.Position; g := TrackG.Position; b := TrackB.Position; ColorPanel.Caption := Dec2Hex(r)+Dec2Hex(g)+Dec2Hex(b); ColorPanel.Color := rgb(r,g,b); ColorPanel.Font.Color := rgb(r XOR $FF, g XOR $FF, b XOR $FF); end; end; procedure UpdateColorFromHex(s: string); var col: TColor; r,g,b: integer; begin with KBLEditForm do begin col := Hex2Col(s); r := (col AND $0000FF); g := (col AND $00FF00) DIV $FF; b := (col AND $FF0000) DIV $FFFF; TrackR.Position := r; TrackG.Position := g; TrackB.Position := b; ColorPanel.Color := col; ColorPanel.Font.Color := col XOR $FFFFFF; end; end; procedure TKBLEditForm.TrackRChange(Sender: TObject); begin UpdateColor; end; procedure TKBLEditForm.TrackGChange(Sender: TObject); begin UpdateColor; end; procedure TKBLEditForm.TrackBChange(Sender: TObject); begin UpdateColor; end; procedure TKBLEditForm.ButtonColorInsertClick(Sender: TObject); begin InsertIntoMemo(ColorPanel.Caption); end; function StringIsNum(s: string): boolean; var i: integer; begin for i:=1 to Length(s) do begin if Pos(UpCase(s[i]),Hexmap)=0 then begin Result := false; Exit; end; end; Result := true; end; procedure TKBLEditForm.ButtonColorGetClick(Sender: TObject); var s: AnsiString; begin s := Copy(Memo1.Lines.Text, Memo1.SelStart+1, Memo1.SelLength); if (Length(s)=6) AND (StringIsNum(s)) then begin UpdateColorFromHex(s); end else begin ShowMessage('Please select a color value first. (6-digit hexadecimal number)'); end; end; procedure TKBLEditForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if (Memo1.Modified) then begin CanClose := (Application.MessageBox('You have not saved your work. Are you sure to quit?', 'Please confirm', MB_YESNO)=IDYES); end; end; function GetFirstCharInLine(m: TMemo): char; var p: TRowCol; begin Result := Chr(0); p := GetMemoRowCol(m); if (Length(m.Lines.Strings[p.row])>0) then begin Result := m.Lines.Strings[p.row][1]; end; end; procedure SetSyntax(s: AnsiString); var tw: integer; begin with KBLEditForm do begin if (s<>'') then s := 'Syntax: ' + s; LabelSyntax.Caption := s; Px00Screen.Canvas.Font := LabelSyntax.Font; tw := Px00Screen.Canvas.TextWidth(s); if (tw>LabelSyntax.Width) then TimerScroll.Enabled := true else TimerScroll.Enabled := false; end; end; function GetHelpByKey(x: char): AnsiString; begin case UpCase(x) of 'T': Result := 'T,Title Text'; 'M': Result := 'M,landscape,full,top,left,bottom,right,rows,cols,top,left,bottom,right,keycap,ink,highlight,background'; 'R': Result := 'R,keycap,ink'; 'K': Result := 'K,value,width,keycap,ink,legend'; 'L': Result := 'L,map,width,keycap,ink,legend'; 'S': Result := 'S,map,width,keycap,ink,legend'; '!': Result := '! Helpful Text'; else Result := ''; end; end; procedure TKBLEditForm.Memo1Change(Sender: TObject); var x: char; begin x := GetFirstCharInLine(Memo1); SetSyntax(GetHelpByKey(x)); end; procedure TKBLEditForm.TimerScrollTimer(Sender: TObject); var x: AnsiString; begin x := LabelSyntax.Caption; if (Pos(' +++ ',x)=0) then x := x + ' +++ '; LabelSyntax.Caption := Copy(x,2,Length(x)-1)+x[1]; end; procedure TKBLEditForm.Memo1Click(Sender: TObject); begin Memo1Change(Sender); end; function GetClickedKey(mapnum, x, y: integer): TSelection; var m: TArray; map: TMap; keyrect: TRect; i,j: integer; found: boolean; begin m := GetMap(mapnum); map := ParseMap(m); Result.map := 0; Result.row := 0; Result.key := 0; found := false; for i:=0 to Length(map.Data)-1 do begin for j:=0 to Length(map.Data[i].keys)-1 do begin keyrect := GetKeyRect(map, i, j); if (x>=keyrect.Left) AND (x<=keyrect.Right) AND (y>=keyrect.Top) AND (y<=keyrect.Bottom) then begin Result.map := mapnum; Result.row := i+1; Result.key := j+1; found := true; Break; end; end; if (found) then Break; end; end; procedure MemoJumpTo(map, row, key: integer); var t: AnsiString; i: integer; x: char; test: char; begin with KBLEditForm.Memo1 do begin t := Lines.Text; for i:=1 to Length(t) do begin test := UpCase(t[i]); if (i>1) AND (Ord(t[i-1])=10) then begin if (test='M') AND (map>0) then map := map - 1; if (test='R') AND (map=0) AND (row>0) then row := row - 1; if ((test='K') OR (test='L') OR (test='S')) AND (map=0) AND (row=0) AND (key>0) then key := key - 1; end; if (map=0) AND (row=0) AND (key=0) then begin SelStart := i-1; x := GetFirstCharInLine(KBLEditForm.Memo1); Perform(EM_SCROLLCARET, 0, 0); SetSyntax(GetHelpByKey(x)); SetFocus; Break; end; end; end; end; procedure TKBLEditForm.Px00ScreenMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var sel: TSelection; begin if (Button=mbLeft) AND (Shift=[ssLeft]) then begin sel := GetClickedKey(SpinEdit1.Value,x,y); if (sel.map>0) then begin MemoJumpTo(sel.map, sel.row, sel.key); VisMap(SpinEdit1.Value, sel); end; end; end; end.