Ansi stuff works, some unicode translation implemented - baud.baby renders correctly!

This commit is contained in:
John Sennesael 2020-11-29 19:13:00 -06:00
parent ef83179147
commit d8fcccecb5
7 changed files with 662 additions and 99 deletions

View File

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

View File

@ -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.

46
src/DrawUtils.pas Normal file
View File

@ -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.

View File

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

View File

@ -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.

View File

@ -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 }

19
src/TurboGopherWindow.pas Normal file
View File

@ -0,0 +1,19 @@
unit TurboGopherWindow;
{$mode objfpc}{$H+}
interface
uses
Classes;
type
TTurboGopherWindow = class abstract
public
end;
implementation
end.