From d8fcccecb5fdb4c891fd28b595bf03e13678530f Mon Sep 17 00:00:00 2001 From: John Sennesael Date: Sun, 29 Nov 2020 19:13:00 -0600 Subject: [PATCH] Ansi stuff works, some unicode translation implemented - baud.baby renders correctly! --- src/BrowserWidget.pas | 559 +++++++++++++++++++--- src/{MainWindow.pas => BrowserWindow.pas} | 34 +- src/DrawUtils.pas | 46 ++ src/GopherClient.pas | 41 +- src/LogWindow.pas | 52 +- src/TurboGopherApplication.pas | 10 +- src/TurboGopherWindow.pas | 19 + 7 files changed, 662 insertions(+), 99 deletions(-) rename src/{MainWindow.pas => BrowserWindow.pas} (76%) create mode 100644 src/DrawUtils.pas create mode 100644 src/TurboGopherWindow.pas diff --git a/src/BrowserWidget.pas b/src/BrowserWidget.pas index 15eebb7..131f84b 100644 --- a/src/BrowserWidget.pas +++ b/src/BrowserWidget.pas @@ -5,20 +5,28 @@ unit BrowserWidget; interface uses + DrawUtils, Logger, TurboGopherApplication, Classes, + CustApp, Drivers, Objects, + Regexpr, + StrUtils, SysUtils, Views; - type + TCsiParseResult = record + attributes: byte; + tokensConsumed: SizeInt; + end; + TBrowserCharacter = record character: byte; - attributtes: byte; + attributes: byte; end; TBrowserString = array of TBrowserCharacter; @@ -32,90 +40,507 @@ type procedure Add(text: string); procedure Draw; virtual; private - var App: TTurboGopherApplication; + App: TTurboGopherApplication; Lines: array of TBrowserString; CurrentForegroundColor: byte; CurrentBackgroundColor: byte; - const defaultAttrs = $10; + const defaultAttrs = $1f; end; PBrowserWidget = ^TBrowserWidget; implementation +var + sgrTokenRe: TRegExpr; + { some static helper funcs } +(* ANSI SGR / CSI parsing helper *) function IsCsiParam(character: Char): Boolean; begin Result := false; if (ord(character) >= $30) and (ord(character) <= $3f) then Result := true; end; +(* ANSI SGR / CSI parsing helper *) function IsCsiIntermediate(character: Char): Boolean; begin Result := false; if (ord(character) >= $20) and (ord(character) <= $2f) then Result := true; end; +(* ANSI SGR / CSI parsing helper *) function IsCsiFinal(character: Char): Boolean; begin Result := false; if (ord(character) >= $40) and (ord(character) <= $7e) then Result := true; end; +(* ANSI SGR / CSI parsing helper *) function ParseCsiToken( - Logger: PLogger; - token: string; - currentAttrs, defaultAttrs: Byte): Byte; + csi: AnsiString; + currentAttrs, defaultAttrs: Byte): TCsiParseResult; +var + code: Integer; + token: AnsiString; + fg, bg, sepPos: Integer; begin + { default result if we can't parse anything is to preseve current attrs. } + Result.tokensConsumed := 0; + Result.attributes := currentAttrs; + { get position of first token end. (either up to the first ; or m) } + sepPos := Pos(';', csi); + if sepPos = 0 then sepPos := Pos('m', csi); + { if we couldn't get a position, we can't parse anything. } + if sepPos = 0 then Exit; + { use position to get first token } + token := Copy(csi, 1, sepPos - 1); + { CSI Reset } if token = '0' then begin - Result := defaultAttrs; + Result.attributes := defaultAttrs; + Result.tokensConsumed := 1; Exit; end; + { split current attrs into a bg and fg. } + bg := Hi(currentAttrs) * 16; + fg := Lo(currentAttrs); + { parse token } case token of - '30': Result := (Hi(currentAttrs) * $10) + $00; { fg = black } - '31': Result := (Hi(currentAttrs) * $10) + $04; { fg = red } - '32': Result := (Hi(currentAttrs) * $10) + $02; { fg = green } - '33': Result := (Hi(currentAttrs) * $10) + $06; { fg = yellow } - '34': Result := (Hi(currentAttrs) * $10) + $01; { fg = blue } - '35': Result := (Hi(currentAttrs) * $10) + $05; { fg = magenta } - '36': Result := (Hi(currentAttrs) * $10) + $03; { fg = cyan } - '37': Result := (Hi(currentAttrs) * $10) + $07; { fg = white } - '90': Result := (Hi(currentAttrs) * $10) + $08; { fg = bright black } - '91': Result := (Hi(currentAttrs) * $10) + $0c; { fg = bright red } - '92': Result := (Hi(currentAttrs) * $10) + $0a; { fg = bright green } - '93': Result := (Hi(currentAttrs) * $10) + $0e; { fg = bright yellow } - '94': Result := (Hi(currentAttrs) * $10) + $09; { fg = bright blue } - '95': Result := (Hi(currentAttrs) * $10) + $0d; { fg = bright magenta } - '96': Result := (Hi(currentAttrs) * $10) + $0b; { fg = bright cyan } - '97': Result := (Hi(currentAttrs) * $10) + $0f; { fg = bright white } - '40': Result := $00 + Lo(currentAttrs); { bg = black } - '41': Result := $40 + Lo(currentAttrs); { bg = red } - '42': Result := $20 + Lo(currentAttrs); { bg = green } - '43': Result := $60 + Lo(currentAttrs); { bg = yellow } - '44': Result := $10 + Lo(currentAttrs); { bg = blue } - '45': Result := $50 + Lo(currentAttrs); { bg = magenta } - '46': Result := $30 + Lo(currentAttrs); { bg = cyan } - '47': Result := $70 + Lo(currentAttrs); { bg = white } - '100': Result := $80 + Lo(currentAttrs); { bg = bright black } - '101': Result := $c0 + Lo(currentAttrs); { bg = bright red } - '102': Result := $a0 + Lo(currentAttrs); { bg = bright green } - '103': Result := $e0 + Lo(currentAttrs); { bg = bright yellow } - '104': Result := $90 + Lo(currentAttrs); { bg = bright blue } - '105': Result := $d0 + Lo(currentAttrs); { bg = bright magenta } - '106': Result := $b0 + Lo(currentAttrs); { bg = bright cyan } - '107': Result := $f0 + Lo(currentAttrs); { bg = bright white } + '30': + begin { fg = black } + Result.attributes := bg + $00; + Result.tokensConsumed := 1; + Exit; + end; + '31': + begin { fg = red } + Result.attributes := bg + $04; + Result.tokensConsumed := 1; + Exit; + end; + '32': + begin { fg = green } + Result.attributes := bg + $02; + Result.tokensConsumed := 1; + Exit; + end; + '33': + begin { fg = yellow } + Result.attributes := bg + $06; + Result.tokensConsumed := 1; + Exit; + end; + '34': + begin { fg = blue } + Result.attributes := bg + $01; + Result.tokensConsumed := 1; + Exit; + end; + '35': + begin { fg = magenta } + Result.attributes := bg + $05; + Result.tokensConsumed := 1; + Exit; + end; + '36': + begin { fg = cyan } + Result.attributes := bg + $03; + Result.tokensConsumed := 1; + Exit; + end; + '37': + begin { fg = white } + Result.attributes := bg + $07; + Result.tokensConsumed := 1; + Exit; + end; + '90': + begin { fg = bright black } + Result.attributes := bg + $08; + Result.tokensConsumed := 1; + Exit; + end; + '91': + begin { fg = bright red } + Result.attributes := bg + $0c; + Result.tokensConsumed := 1; + Exit; + end; + '92': + begin { fg = bright green } + Result.attributes := bg + $0a; + Result.tokensConsumed := 1; + Exit; + end; + '93': + begin { fg = bright yellow } + Result.attributes := bg + $0e; + Result.tokensConsumed := 1; + Exit; + end; + '94': + begin { fg = bright blue } + Result.attributes := bg + $09; + Result.tokensConsumed := 1; + Exit; + end; + '95': { fg = bright magenta } + begin + Result.attributes := bg + $0d; + Result.tokensConsumed := 1; + Exit; + end; + '96': + begin { fg = bright cyan } + Result.attributes := bg + $0b; + Result.tokensConsumed := 1; + Exit; + end; + '97': + begin { fg = bright white } + Result.attributes := bg + $0f; + Result.tokensConsumed := 1; + Exit; + end; + '40': + begin { bg = black } + Result.attributes := $00 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '41': + begin { bg = red } + Result.attributes := $40 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '42': + begin { bg = green } + Result.attributes := $20 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '43': + begin { bg = yellow } + Result.attributes := $60 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '44': + begin { bg = blue } + Result.attributes := $10 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '45': + begin { bg = magenta } + Result.attributes := $50 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '46': + begin { bg = cyan } + Result.attributes := $30 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '47': { bg = white } + begin + Result.attributes := $70 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '100': + begin { bg = bright black } + Result.attributes := $80 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '101': + begin { bg = bright red } + Result.attributes := $c0 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '102': + begin { bg = bright green } + Result.attributes := $a0 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '103': + begin { bg = bright yellow } + Result.attributes := $e0 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '104': + begin { bg = bright blue } + Result.attributes := $90 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '105': + begin { bg = bright magenta } + Result.attributes := $d0 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '106': + begin { bg = bright cyan } + Result.attributes := $b0 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '107': { bg = bright white } + begin + Result.attributes := $f0 + fg; + Result.tokensConsumed := 1; + Exit; + end; + '38': { indexed fg color } + begin + if sgrTokenRe.Exec(csi) then + begin + if sgrTokenRe.Match[2] <> '5' then + begin + Result.tokensConsumed := 0; + Exit; + end; + code := StrToInt(sgrTokenRe.Match[3]); + if code = 233 then + begin + Result.tokensConsumed := 0; + end; + case code of + 0, 16, 232..237: + begin { black } + Result.attributes := bg + $00; + Result.tokensConsumed := 3; + Exit; + end; + 1, 52, 88, 124, 202..205: + begin { red } + Result.attributes := bg + $04; + Result.tokensConsumed := 3; + Exit; + end; + 2, 22, 23, 58, 94, 130, 166, 28, 64, 65, 100, 34..36, 70..72, 106..108: + begin { green } + Result.attributes := bg + $02; + Result.tokensConsumed := 3; + Exit; + end; + 3, 136, 172, 208, 142..145, 178..181, 214..216: + begin { yellow } + Result.attributes := bg + $06; + Result.tokensConsumed := 3; + Exit; + end; + 4, 17..21, 24..27: + begin { blue } + Result.attributes := bg + $01; + Result.tokensConsumed := 3; + Exit; + end; + 5, 53..57, 89..93, 125..129, 59..63, 95..99, 131..135, 109..110, 146..147, 182, 183: + begin { magenta } + Result.attributes := bg + $05; + Result.tokensConsumed := 3; + Exit; + end; + 6, 29..31, 66..67, 37..39, 73..75: + begin { cyan } + Result.attributes := bg + $03; + Result.tokensConsumed := 3; + Exit; + end; + 7, 231, 101, 102, 244..251: + begin { white } + Result.attributes := bg + $07; + Result.tokensConsumed := 3; + Exit; + end; + 8, 238..243: + begin { bright black } + Result.attributes := bg + $08; + Result.tokensConsumed := 3; + Exit; + end; + 9, 160, 196, 217..218: + begin { bright red } + Result.attributes := bg + $0c; + Result.tokensConsumed := 3; + Exit; + end; + 10, 40..43, 76..79, 112..115, 148..151, 46..49, 82..84, 118..121, 154..157, 190..193: + begin { bright green } + Result.attributes := bg + $0a; + Result.tokensConsumed := 3; + Exit; + end; + 11, 184..188, 220..223, 226..230: + begin { bright yellow } + Result.attributes := bg + $0e; + Result.tokensConsumed := 3; + Exit; + end; + 12, 32..33, 68..69, 103..105, 111, 152..153, 87, 123, 159, 195: + begin { bright blue } + Result.attributes := bg + $09; + Result.tokensConsumed := 3; + Exit; + end; + 13, 161..165, 197..201, 167..171, 206..207, 137..141, 173..177, 209..213, 219, 189, 224, 225: + begin { bright magenta } + Result.attributes := bg + $0d; + Result.tokensConsumed := 3; + Exit; + end; + 14, 44..45, 80..81, 116..117, 50..51, 85..86, 122, 158, 194: + begin { bright cyan } + Result.attributes := bg + $0b; + Result.tokensConsumed := 3; + Exit; + end; + 15, 252..255: + begin { bright white } + Result.attributes := bg + $0f; + Result.tokensConsumed := 3; + Exit; + end; + end; + end; + end; + '48': { indexed bg color } + begin + if sgrTokenRe.Exec(csi) then + begin + code := StrToInt(sgrTokenRe.Match[3]); + case code of + 0, 16, 232..237: + begin { black } + Result.attributes := $00 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 1, 52, 88, 124, 202..205: + begin { red } + Result.attributes := $40 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 2, 22, 23, 58, 94, 130, 166, 28, 64, 65, 100, 34..36, 70..72, 106..108: + begin { green } + Result.attributes := $20 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 3, 136, 172, 208, 142..145, 178..181, 214..216: + begin { yellow } + Result.attributes := $60 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 4, 17..21, 24..27: + begin { blue } + Result.attributes := $10 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 5, 53..57, 89..93, 125..129, 59..63, 95..99, 131..135, 109..110, 146..147, 182, 183: + begin { magenta } + Result.attributes := $50 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 6, 29..31, 66..67, 37..39, 73..75: + begin { cyan } + Result.attributes := $30 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 7, 231, 101, 102, 244..251: + begin { white } + Result.attributes := $70 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 8, 238..243: + begin { bright black } + Result.attributes := $80 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 9, 160, 196, 217..218: + begin { bright red } + Result.attributes := $c0 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 10, 40..43, 76..79, 112..115, 148..151, 46..49, 82..84, 118..121, 154..157, 190..193: + begin { bright green } + Result.attributes := $a0 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 11, 184..188, 220..223, 226..230: + begin { bright yellow } + Result.attributes := $e0 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 12, 32..33, 68..69, 103..105, 111, 152..153, 87, 123, 159, 195: + begin { bright blue } + Result.attributes := $90 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 13, 161..165, 197..201, 167..171, 206..207, 137..141, 173..177, 209..213, 219, 189, 224, 225: + begin { bright magenta } + Result.attributes := $d0 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 14, 44..45, 80..81, 116..117, 50..51, 85..86, 122, 158, 194: + begin { bright cyan } + Result.attributes := $b0 + fg; + Result.tokensConsumed := 3; + Exit; + end; + 15, 252..255: + begin { bright white } + Result.attributes := $f0 + fg; + Result.tokensConsumed := 3; + Exit; + end; + end; + end; + end; end; end; -function ParseCsi(Logger: PLogger; csi: string; defaultAttrs: Byte): Byte; +(* ANSI SGR / CSI parsing helper *) +function ParseCsi( + Logger: PLogger; + csi: string; + currentAttrs, defaultAttrs: Byte +): Byte; var - I: Integer; + prevPos, nextPos, I: Integer; sgrBuffer: AnsiString; + parsedToken: TCsiParseResult; + success: Boolean; begin + if csi = '38;5;233m' then + begin + sgrBuffer := 'a'; + end; sgrBuffer := ''; - Result := defaultAttrs; + Result := currentAttrs; + success := False; if Length(csi) < 2 then begin Logger^.Info('Ignoring unsupported CSI sequence: ' + csi); @@ -126,20 +551,30 @@ begin Logger^.Info('Ignoring unsupported CSI sequence: ' + csi); Exit; end; - for I := 1 to Length(csi) do + I := 1; + prevPos := 0; + while I <= Length(csi) do begin - if csi[I] = 'm' then + { read up to the next token or terminator } + nextPos := NPos(';', csi, I); + if nextPos = 0 then nextPos := Pos('m', csi); + if nextPos = 0 then break; + sgrBuffer := Copy(csi, prevPos + 1, nextPos); + { attempt to parse the tokens so far } + parsedToken := ParseCsiToken(sgrBuffer, Result, defaultAttrs); + if parsedToken.tokensConsumed > 0 then begin - Result := ParseCsiToken(Logger, sgrBuffer, Result, defaultAttrs); - Exit; + { parse success, write attrs, advance counter #tokens consumed. } + Result := parsedToken.attributes; + prevPos := nextPos; + success := True; end; - if csi[I] = ';' then - begin - Result := ParseCsiToken(Logger, sgrBuffer, Result, defaultAttrs); - sgrBuffer := ''; - continue; - end; - sgrBuffer += csi[I]; + { parse fail, try next token. } + I += 1; + end; + if success <> True then + begin + Logger^.Warning('Unable to parse csi token: ' + csi); end; end; @@ -157,6 +592,7 @@ begin CurrentBackgroundColor := Lo(defaultAttrs); GrowMode := gfGrowHiX + gfGrowHiY; SetLimit(128, 10); + sgrTokenRe := TRegExpr.Create('^(\d+);(\d+);(\d+)'); end; procedure TBrowserWidget.Add(text: string); @@ -250,7 +686,7 @@ begin end; if CsiStage = TCsiParseStage.Final then begin - Attrs := ParseCsi(Logger, CsiBuffer, defaultAttrs); + Attrs := ParseCsi(Logger, CsiBuffer, Attrs, defaultAttrs); CsiStage := TCsiParseStage.None; CsiBuffer := ''; end; @@ -275,7 +711,7 @@ begin continue; end; NewChar.character := ord(text[I + 1]); - NewChar.attributtes := Attrs; + NewChar.attributes := Attrs; SetLength(Line, Length(Line) + 1); Line[Length(Line) - 1] := NewChar; PC := CC; @@ -292,14 +728,15 @@ procedure TBrowserWidget.Draw; var I: Integer; DrawBuffer: TDrawBuffer; - X, Y, LineCount, LongestLine: SizeInt; + DrawBufferIndex, X, Y, LineCount, LongestLine: SizeInt; C: TBrowserCharacter; begin DrawBuffer := default(TDrawBuffer); + DrawBufferIndex := 0; LongestLine := 0; LineCount := Length(Lines); { clear the screen } - MoveChar(DrawBuffer, ' ', defaultAttrs, Size.X); + AddToDrawBuf(DrawBuffer, 32, defaultAttrs, Size.X); WriteLine(0, 0, Size.X, Size.Y, DrawBuffer); DrawBuffer := default(TDrawBuffer); { render characters } @@ -313,9 +750,11 @@ begin begin if (Length(Lines[I]) - 1 < X) then continue; C := Lines[I][X]; - MoveChar(DrawBuffer, chr(C.character), C.attributtes, 1); - WriteBuf(x - Delta.X, Y - Delta.Y, 1, 1, DrawBuffer); + SetInDrawBuf(DrawBuffer, C.character, C.attributes, DrawBufferIndex); + DrawBufferIndex += 1; end; + WriteBuf(0, Y - Delta.Y, DrawBufferIndex, 1, DrawBuffer); + DrawBufferIndex := 0; end; end; SetLimit(LongestLine, LineCount); diff --git a/src/MainWindow.pas b/src/BrowserWindow.pas similarity index 76% rename from src/MainWindow.pas rename to src/BrowserWindow.pas index 6b2d7ea..8e39e26 100644 --- a/src/MainWindow.pas +++ b/src/BrowserWindow.pas @@ -1,34 +1,39 @@ -unit MainWindow; +unit BrowserWindow; {$mode objfpc}{$H+} interface uses + GopherClient, + BrowserWidget, + TurboGopherApplication, + TurboGopherWindow, + App, Classes, - SysUtils, + CustApp, Objects, - Views, - TurboGopherApplication, - GopherClient, - BrowserWidget; + SysUtils, + Views; + type - TMainWindow = class + PBrowserWindow = ^TBrowserWindow; + TBrowserWindow = class(TTurboGopherWindow) + public + constructor Create(var TheApp: TTurboGopherApplication); + procedure Get(url: AnsiString); private Rect: TRect; Win: PWindow; - var App: TTurboGopherApplication; Text: AnsiString; Browser: PBrowserWidget; - public - constructor Create(var TheApp: TTurboGopherApplication); - procedure Get(); + App: TTurboGopherApplication; end; implementation - constructor TMainWindow.Create(var TheApp: TTurboGopherApplication); + constructor TBrowserWindow.Create(var TheApp: TTurboGopherApplication); begin App := TheApp; (* Figure out where to position ourselves *) @@ -53,16 +58,15 @@ implementation end; end; - procedure TMainWindow.Get(); + procedure TBrowserWindow.Get(url: AnsiString); var - url: string; client: TGopherClient; begin client := App.GetClient(); - url := 'gopher://gopher.linkerror.com/0/testfile'; text := client.Get(url); Browser^.Add(text); Browser^.Draw; + Win^.Draw; end; end. diff --git a/src/DrawUtils.pas b/src/DrawUtils.pas new file mode 100644 index 0000000..24e71fd --- /dev/null +++ b/src/DrawUtils.pas @@ -0,0 +1,46 @@ +unit DrawUtils; + +interface + +uses + Views; + +(* This is a custom version of MoveChar that doesn't preserve the original + attributes in the buffer if attributes equals zero. *) +procedure AddToDrawBuf( + var buffer: TDrawBuffer; + character: Byte; + attributes: Byte; + count: SizeInt); + +(* Same as above, but allows for setting characters at a specific index. *) +procedure SetInDrawBuf( + var buffer: TDrawBuffer; + character: Byte; + attributes: Byte; + position: SizeInt); + +implementation + +procedure AddToDrawBuf( + var buffer: TDrawBuffer; + character: Byte; + attributes: Byte; + count: SizeInt); +var + P: SizeInt; +begin + for P := 0 to count do + buffer[P] := (attributes * 256) + Byte(character); +end; + +procedure SetInDrawBuf( + var buffer: TDrawBuffer; + character: Byte; + attributes: Byte; + position: SizeInt); +begin + buffer[position] := (attributes * 256) + Byte(character); +end; + +end. diff --git a/src/GopherClient.pas b/src/GopherClient.pas index 87720b5..6d80951 100644 --- a/src/GopherClient.pas +++ b/src/GopherClient.pas @@ -45,6 +45,40 @@ type implementation + { Helper functions } + + (* The idea of this is to replace widely used multibyte characters in UTF8 + (mostly block drawing stuff, with their nearest equivalent in ANSI rather + than just showing an unknown glyph question mark. *) + function UTF8Hack(const inputString: RawByteString): AnsiString; + begin + Result := inputString; + Result := Result.Replace(chr($e2) + chr($95) + chr($b1), '/'); + Result := Result.Replace(chr($e2) + chr($95) + chr($b2), '\'); + Result := Result.Replace(chr($e2) + chr($96) + chr($80), chr($df)); + Result := Result.Replace(chr($e2) + chr($96) + chr($84), chr($dc)); + Result := Result.Replace(chr($e2) + chr($96) + chr($91), chr($b0)); + Result := Result.Replace(chr($e2) + chr($96) + chr($92), chr($b1)); + Result := Result.Replace(chr($e2) + chr($96) + chr($93), chr($b2)); + Result := Result.Replace(chr($e2) + chr($96) + chr($88), chr($db)); + Result := Result.Replace(chr($e2) + chr($96) + chr($8c), chr($dd)); + Result := Result.Replace(chr($e2) + chr($96) + chr($96), chr($dd)); + Result := Result.Replace(chr($e2) + chr($96) + chr($98), chr($dd)); + Result := Result.Replace(chr($e2) + chr($96) + chr($90), chr($de)); + Result := Result.Replace(chr($e2) + chr($96) + chr($97), chr($de)); + Result := Result.Replace(chr($e2) + chr($96) + chr($9c), chr($de)); + Result := Result.Replace(chr($e2) + chr($96) + chr($9d), chr($de)); + Result := Result.Replace(chr($e2) + chr($96) + chr($99), chr($db)); + Result := Result.Replace(chr($e2) + chr($96) + chr($9b), chr($db)); + Result := Result.Replace(chr($e2) + chr($96) + chr($9c), chr($db)); + Result := Result.Replace(chr($e2) + chr($96) + chr($9f), chr($db)); + Result := Result.Replace(chr($e2) + chr($88) + chr($99), chr($f9)); + Result := Result.Replace(chr($e2) + chr($80) + chr($ba), '>'); + Result := Result.Replace(chr($c2) + chr($b7), chr($fa)); + end; + + { TGopherClient } + constructor TGopherClient.Create(LoggerObject: PLogger); begin Logger := LoggerObject; @@ -66,7 +100,7 @@ implementation for I := 0 to Length(Lines) - 1 do begin - if (Length(Lines) > 2) then + if (Length(Lines) > 0) then begin if Lines[I] = '.' then Exit; { Single dot marks the end. } MenuItem := ParseMenuItem(Lines[I]); @@ -174,8 +208,8 @@ implementation begin Logger^.Error('Could not connect to host: ' + TokenizedUrl.Host + ' on port ' + IntToStr(TokenizedUrl.Port) - + ' - Error: ' + E. Message); - ClientSocket.Free; + + ' - Error: ' + E.Message); + if ClientSocket <> nil then ClientSocket.Free; Exit; end; end; @@ -201,6 +235,7 @@ implementation Result += Part; Buf := ''; end; + Result := UTF8Hack(Result); Logger^.Debug('Successfully retrieved ' + Url); ClientSocket.Free; Menu := ParseMenu(Result); diff --git a/src/LogWindow.pas b/src/LogWindow.pas index c9f8390..c2e34a5 100644 --- a/src/LogWindow.pas +++ b/src/LogWindow.pas @@ -5,11 +5,14 @@ unit LogWindow; interface uses - App, + Logger, TurboGopherApplication, + TurboGopherWindow, + App, Classes, + CustApp, Drivers, Objects, SysUtils, @@ -17,6 +20,8 @@ uses type + PCustomApplication = ^TCustomApplication; + TLogWidget = object(TScroller) constructor Init( LoggerObject: PLogger; @@ -27,30 +32,36 @@ type private var Logger: PLogger; var Filter: TLogLevelFilter; + const defaultAttrs = $1f; end; PLogWidget = ^TLogWidget; - TLogWindow = class + TLogWindow = class(TTurboGopherWindow) public - constructor Create(var TheApp: TTurboGopherApplication); + + constructor Init(TheApp: TTurboGopherApplication); virtual; procedure OnLogMessage( const Message: string; const Level: TLogLevel ); private - var App: TTurboGopherApplication; - LogWidget: TLogWidget; + LogWidget: PLogWidget; Rect: TRect; Win: PWindow; + var App: TTurboGopherApplication; end; + PLogWindow = ^TLogWindow; implementation + uses + DrawUtils; + { TLogWidget } constructor TLogWidget.Init( LoggerObject: PLogger; - Bounds: TRect; + Bounds: Objects.TRect; AHScrollBar, AVScrollBar: PScrollBar); begin TScroller.Init(Bounds, AHScrollBar, AVScrollBar); @@ -82,12 +93,13 @@ implementation ToLine := FromLine + Size.Y; LogEntries := Logger^.GetRange(FromLine, ToLine, Filter); - Color := GetColor($FF); + Color := defaultAttrs; LineCount := Length(LogEntries); { clear the screen } MoveChar(DrawBuffer, ' ', Color, Size.X); WriteLine(0, 0, Size.X, Size.Y, DrawBuffer); + DrawBuffer := default(TDrawBuffer); { render characters } for L := 0 to LineCount - 1 do @@ -97,14 +109,22 @@ implementation if Length(LogEntry.Message) > LongestLine then LongestLine := Length(LogEntry.Message); Str := Copy(LogEntry.Message, Delta.X + 1, Size.X); - WriteStr(0, Y, Str, Color); + case LogEntry.Level of + TLogLevel.debug: Color := (Hi(Color) * 16) + 2; + TLogLevel.info: Color := (Hi(Color) * 16) + 15; + TLogLevel.warning: Color := (Hi(Color) * 16) + 6; + TLogLevel.error: Color := (Hi(Color) * 16) + 12; + TLogLevel.fatal: Color := (4 * 16) + 0; + end; + MoveStr(DrawBuffer, Str, Color); + WriteBuf(0, Y, Length(Str), 1, DrawBuffer); end; SetLimit(LongestLine, Logger^.Count(Filter)); end; { TLogWindow } - constructor TLogWindow.Create(var TheApp: TTurboGopherApplication); + constructor TLogWindow.Init(TheApp: TTurboGopherApplication); begin App := TheApp; (* Figure out where we're going to put ourselves - a window height of @@ -114,20 +134,19 @@ implementation Rect.Move(0, -2); { move up to account for the borders } Win := New(PWindow, Init(Rect, 'Log messages', wnNoNumber)); Desktop^.Insert(Win); - Win^.GetExtent(Rect); Rect.Grow(-2, -1); - LogWidget.Init( + LogWidget := New(PLogWidget, Init( App.GetLogger(), Rect, Win^.StandardScrollBar(sbHorizontal), Win^.StandardScrollBar(sbVertical) - ); - Win^.Insert(@LogWidget); - + )); + if LogWidget = nil then + raise Exception.Create('Could not instantiate log widget.'); + Win^.Insert(LogWidget); (* Register our callback into the logger. *) App.GetLogger()^.RegisterCallback(@OnLogMessage); - end; procedure TLogWindow.OnLogMessage( @@ -135,7 +154,8 @@ implementation const Level: TLogLevel ); begin - LogWidget.Draw; + LogWidget^.Draw; + Win^.Draw; end; end. diff --git a/src/TurboGopherApplication.pas b/src/TurboGopherApplication.pas index 2175ce3..c4b77e6 100644 --- a/src/TurboGopherApplication.pas +++ b/src/TurboGopherApplication.pas @@ -49,11 +49,11 @@ implementation uses LogWindow, - MainWindow; + BrowserWindow; var FLogWindow: TLogWindow; - FMainWindow: TMainWindow; + FBrowserWindow: TBrowserWindow; { TTGApp } @@ -114,8 +114,8 @@ implementation Logger := TLogger.Create; FileLogger := TFileLogger.Create(@Logger, '/tmp/turbogopher_debug.txt'); Client := TGopherClient.Create(@Logger); - FLogWindow := TLogWindow.Create(Self); - FMainWindow := TMainWindow.Create(Self); + FLogWindow := TLogWindow.Init(Self); + FBrowserWindow := TBrowserWindow.Create(Self); end; procedure TTurboGopherApplication.DoRun; @@ -138,7 +138,7 @@ implementation Exit; end; { TEST } - FMainWindow.Get; + FBrowserWindow.Get('gopher://baud.baby'); { Run TG app } TurboGraphicsApplication.Run; { Clean shutdown } diff --git a/src/TurboGopherWindow.pas b/src/TurboGopherWindow.pas new file mode 100644 index 0000000..2a8f8ad --- /dev/null +++ b/src/TurboGopherWindow.pas @@ -0,0 +1,19 @@ +unit TurboGopherWindow; + +{$mode objfpc}{$H+} + +interface + +uses + Classes; + +type + + TTurboGopherWindow = class abstract + public + + end; + +implementation + +end.