Browse Source

Remove trailing spaces in source of FO

Arthur Yefimov 3 years ago
parent
commit
1e06cb152b
7 changed files with 2776 additions and 2776 deletions
  1. 104 104
      src/Dir.Mod
  2. 1022 1022
      src/Editor.Mod
  3. 3 3
      src/EditorText.Mod
  4. 1235 1235
      src/Graph.Mod
  5. 3 3
      src/OV.Mod
  6. 53 53
      src/Term.Mod
  7. 356 356
      src/Terminal.Mod

+ 104 - 104
src/Dir.Mod

@@ -1,104 +1,104 @@
-MODULE Dir; (*This is Oberon-07 style file*)
-IMPORT C := arC, arCString, SYSTEM, Platform, Out;
-
-TYPE
-  DIR = SYSTEM.ADRINT;
-  DIRENT = SYSTEM.ADRINT;
-
-  Rec* = RECORD
-    eod*: BOOLEAN; (* End of directory *)
-    isDir*: BOOLEAN; (* TRUE if current entry is a directory *)
-    res*: INTEGER; (* 0 means no error *)
-    name*: ARRAY 512 OF CHAR;
-    path: ARRAY 1024 OF CHAR;
-    pathlen: INTEGER;
-    dir: DIR
-  END;
-
-PROCEDURE -AAincludeDirent "#include <dirent.h>";
-
-PROCEDURE -opendir(name: ARRAY OF CHAR): DIR "(SYSTEM_ADRINT) opendir((const char *)name)";
-PROCEDURE -closedir(dir: DIR) "closedir((DIR*) dir)";
-PROCEDURE -readdir(dir: DIR): DIRENT "(SYSTEM_ADRINT) readdir((DIR*) dir)";
-PROCEDURE -rewinddir(dir: DIR) "rewinddir((DIR*) dir)";
-PROCEDURE -getdirname(ent: DIRENT): C.string "(CHAR *)(((struct dirent *) ent) -> d_name)";
-PROCEDURE -getdirtype(ent: DIRENT): C.int "(int)((struct dirent *) ent) -> d_type)";
-PROCEDURE -typeDir "DT_DIR";
-PROCEDURE -typeReg "DT_REG";
-PROCEDURE -typeLink "DT_LNK";
-
-PROCEDURE Close*(VAR r: Rec);
-(* Stop enumerating files. MUST BE CALLED to avoid leaking C directory streams *)
-BEGIN
-  IF r.dir # 0 THEN closedir(r.dir); r.dir := 0 END
-  (*!TODO add this to finalization *)
-END Close;
-
-PROCEDURE Next*(VAR r: Rec);
-VAR ent: DIRENT;
-  i, j: INTEGER;
-BEGIN
-  IF r.dir # 0 THEN
-    r.res := 0;
-    ent := readdir(r.dir);
-    IF ent # 0 THEN
-      arCString.CopyToArray(getdirname(ent), r.name);
-
-      (* To set the value of r.isDir, we (maybe) append '/' and name to path,
-         pass it to DirExists and then restore the original value of path. *)
-
-      i := 0; j := r.pathlen;
-      IF r.path[j] # Platform.PathDelimiter THEN
-        r.path[j] := Platform.PathDelimiter; INC(j)
-      END;
-      WHILE (j < LEN(r.path) - 1) & (r.name[i] # 0X) DO
-        r.path[j] := r.name[i]; INC(i); INC(j)
-      END;
-      IF j < LEN(r.path) - 1 THEN
-        r.path[j] := 0X;
-        r.isDir := Platform.DirExists(r.path)
-      ELSE r.isDir := FALSE; r.res := 3; r.eod := TRUE
-      END;
-      r.path[r.pathlen] := 0X
-
-    ELSE r.eod := TRUE
-    END
-  ELSE r.res := 4; r.eod := TRUE
-  END
-END Next;
-
-PROCEDURE First*(VAR r: Rec; path: ARRAY OF CHAR);
-VAR i: INTEGER;
-BEGIN r.isDir := FALSE; r.name[0] := 0X;
-  i := 0; WHILE path[i] # 0X DO INC(i) END;
-  IF i < LEN(r.path) - 13 THEN
-    IF path = '' THEN r.dir := opendir('.'); r.path := './'; r.pathlen := 2
-    ELSE r.dir := opendir(path); r.path := path; r.pathlen := i
-    END;
-    IF r.dir # 0 THEN r.eod := FALSE; Next(r)
-    ELSE r.res := 1; r.eod := TRUE; r.path[0] := 0X
-    END
-  ELSE r.res := 2; r.eod := TRUE; r.path[0] := 0X
-  END
-END First;
-
-PROCEDURE Rewind*(VAR r: Rec);
-BEGIN
-  IF r.dir # 0 THEN rewinddir(r.dir); r.res := 0; r.eod := FALSE; Next(r)
-  ELSE First(r, r.path)
-  END
-END Rewind;
-
-PROCEDURE IsDir*(name: ARRAY OF CHAR): BOOLEAN;
-RETURN (name = '') OR Platform.DirExists(name) END IsDir;
-
-PROCEDURE GetCwd*(VAR dir: ARRAY OF CHAR);
-VAR i: INTEGER;
-BEGIN i := 0;
-  WHILE (Platform.CWD[i] # 0X) & (i < LEN(dir) - 1) DO
-    dir[i] := Platform.CWD[i]; INC(i)
-  END;
-  dir[i] := 0X
-END GetCwd;
-
-END Dir.
+MODULE Dir; (*This is Oberon-07 style file*)
+IMPORT C := arC, arCString, SYSTEM, Platform, Out;
+
+TYPE
+  DIR = SYSTEM.ADRINT;
+  DIRENT = SYSTEM.ADRINT;
+
+  Rec* = RECORD
+    eod*: BOOLEAN; (* End of directory *)
+    isDir*: BOOLEAN; (* TRUE if current entry is a directory *)
+    res*: INTEGER; (* 0 means no error *)
+    name*: ARRAY 512 OF CHAR;
+    path: ARRAY 1024 OF CHAR;
+    pathlen: INTEGER;
+    dir: DIR
+  END;
+
+PROCEDURE -AAincludeDirent "#include <dirent.h>";
+
+PROCEDURE -opendir(name: ARRAY OF CHAR): DIR "(SYSTEM_ADRINT) opendir((const char *)name)";
+PROCEDURE -closedir(dir: DIR) "closedir((DIR*) dir)";
+PROCEDURE -readdir(dir: DIR): DIRENT "(SYSTEM_ADRINT) readdir((DIR*) dir)";
+PROCEDURE -rewinddir(dir: DIR) "rewinddir((DIR*) dir)";
+PROCEDURE -getdirname(ent: DIRENT): C.string "(CHAR *)(((struct dirent *) ent) -> d_name)";
+PROCEDURE -getdirtype(ent: DIRENT): C.int "(int)((struct dirent *) ent) -> d_type)";
+PROCEDURE -typeDir "DT_DIR";
+PROCEDURE -typeReg "DT_REG";
+PROCEDURE -typeLink "DT_LNK";
+
+PROCEDURE Close*(VAR r: Rec);
+(* Stop enumerating files. MUST BE CALLED to avoid leaking C directory streams *)
+BEGIN
+  IF r.dir # 0 THEN closedir(r.dir); r.dir := 0 END
+  (*!TODO add this to finalization *)
+END Close;
+
+PROCEDURE Next*(VAR r: Rec);
+VAR ent: DIRENT;
+  i, j: INTEGER;
+BEGIN
+  IF r.dir # 0 THEN
+    r.res := 0;
+    ent := readdir(r.dir);
+    IF ent # 0 THEN
+      arCString.CopyToArray(getdirname(ent), r.name);
+
+      (* To set the value of r.isDir, we (maybe) append '/' and name to path,
+         pass it to DirExists and then restore the original value of path. *)
+
+      i := 0; j := r.pathlen;
+      IF r.path[j] # Platform.PathDelimiter THEN
+        r.path[j] := Platform.PathDelimiter; INC(j)
+      END;
+      WHILE (j < LEN(r.path) - 1) & (r.name[i] # 0X) DO
+        r.path[j] := r.name[i]; INC(i); INC(j)
+      END;
+      IF j < LEN(r.path) - 1 THEN
+        r.path[j] := 0X;
+        r.isDir := Platform.DirExists(r.path)
+      ELSE r.isDir := FALSE; r.res := 3; r.eod := TRUE
+      END;
+      r.path[r.pathlen] := 0X
+
+    ELSE r.eod := TRUE
+    END
+  ELSE r.res := 4; r.eod := TRUE
+  END
+END Next;
+
+PROCEDURE First*(VAR r: Rec; path: ARRAY OF CHAR);
+VAR i: INTEGER;
+BEGIN r.isDir := FALSE; r.name[0] := 0X;
+  i := 0; WHILE path[i] # 0X DO INC(i) END;
+  IF i < LEN(r.path) - 13 THEN
+    IF path = '' THEN r.dir := opendir('.'); r.path := './'; r.pathlen := 2
+    ELSE r.dir := opendir(path); r.path := path; r.pathlen := i
+    END;
+    IF r.dir # 0 THEN r.eod := FALSE; Next(r)
+    ELSE r.res := 1; r.eod := TRUE; r.path[0] := 0X
+    END
+  ELSE r.res := 2; r.eod := TRUE; r.path[0] := 0X
+  END
+END First;
+
+PROCEDURE Rewind*(VAR r: Rec);
+BEGIN
+  IF r.dir # 0 THEN rewinddir(r.dir); r.res := 0; r.eod := FALSE; Next(r)
+  ELSE First(r, r.path)
+  END
+END Rewind;
+
+PROCEDURE IsDir*(name: ARRAY OF CHAR): BOOLEAN;
+RETURN (name = '') OR Platform.DirExists(name) END IsDir;
+
+PROCEDURE GetCwd*(VAR dir: ARRAY OF CHAR);
+VAR i: INTEGER;
+BEGIN i := 0;
+  WHILE (Platform.CWD[i] # 0X) & (i < LEN(dir) - 1) DO
+    dir[i] := Platform.CWD[i]; INC(i)
+  END;
+  dir[i] := 0X
+END GetCwd;
+
+END Dir.

+ 1022 - 1022
src/Editor.Mod

@@ -1,1022 +1,1022 @@
-MODULE Editor;
-(* Copyright 2017-2021 Arthur Yefimov
-
-This file is part of Free Oberon.
-
-Free Oberon is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Free Oberon is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
-*)
-IMPORT OV, T := Terminal, G := Graph, Text := EditorText,
-  Strings, StrList, Dir, Out;
-CONST
-  stdPath* = 'Programs/';
-
-  (* Direction of Selection *)
-  dirLeft  = 0;
-  dirRight = 1;
-  dirUp    = 2;
-  dirDown  = 3;
-
-  (* Character Classes *)
-  charOther       = 0;
-  charAlpha       = 1;
-  charDigit       = 2;
-  charMinusPlus   = 3;
-  charQuote       = 4;
-  charOpenBracket = 5;
-
-  (* Token Classes *)
-  tokenOther   = 0;
-  tokenKeyword = 1;
-  tokenNumber  = 2;
-  tokenString  = 3;
-  tokenComment = 4;
-
-  (* FileDialog type *)
-  open* = 1;
-  save* = 2;
-
-TYPE
-  CHAR = SHORTCHAR;
-
-  FileDialog* = POINTER TO FileDialogDesc;
-  FileDialogDesc* = RECORD(OV.WindowDesc)
-    type*: INTEGER; (* open or save *)
-    edtFilename*: OV.Edit;
-    btnOk*, btnCancel*: OV.Button;
-    path*: ARRAY 1024 OF CHAR; (* full path to current chosen dir *)
-    home*: ARRAY 1024 OF CHAR; (* i.e. 'C:/FreeOberon/Programs' *)
-    colFiles*: OV.ColumnList;
-    onFileOk*: PROCEDURE (c: OV.Control; fname: ARRAY OF CHAR)
-  END;
-
-  Editor* = POINTER TO EditorDesc;
-  EditorDesc* = RECORD(OV.WindowDesc)
-    text*: Text.Text;
-    fname*: ARRAY 1000 OF CHAR
-  END;
-
-VAR
-  clipboard: ARRAY 16000 OF CHAR;
-  editorMethod-: OV.ControlMethod;
-  fileDialogMethod-: OV.ControlMethod;
-
-(* FileDialog *)
-
-PROCEDURE FileNamesCmp(IN a, b: ARRAY OF CHAR): INTEGER;
-VAR r: INTEGER;
-BEGIN
-  IF (a[0] = '[') & (b[0] # '[') OR (a = '[..]') THEN r := -1
-  ELSIF (a[0] # '[') & (b[0] = '[') OR (b = '[..]') THEN r := 1
-  ELSIF a < b THEN r := -1
-  ELSIF a = b THEN r := 0
-  ELSE r := 1
-  END ;
-RETURN r END FileNamesCmp;
-
-PROCEDURE FileDialogUpdateFileList*(c: FileDialog);
-VAR L: StrList.List;
-  r: Dir.Rec;
-  s: ARRAY 512 OF CHAR;
-  first: BOOLEAN;
-  n: INTEGER;
-BEGIN L := c.colFiles.items;
-  StrList.Clear(L);
-  Dir.First(r, c.path); first := TRUE;
-  WHILE ~r.eod DO
-    IF r.name # '.' THEN s := r.name$;
-      IF r.isDir THEN
-        IF first THEN n := Strings.Length(s);
-          s[n] := '/'; s[n + 1] := 0X;
-          OV.EditSetCaption(c.edtFilename, s);
-          s[n] := ']'; Strings.Insert('[', 0, s)
-        ELSE Strings.Insert('[', 0, s); Strings.Append(']', s)
-        END
-      ELSIF first THEN OV.EditSetCaption(c.edtFilename, s)
-      END;
-      StrList.Append(L, s); first := FALSE
-    END;
-    Dir.Next(r)
-  END;
-  StrList.Sort(L, FileNamesCmp);
-  OV.ColumnListSetCur(c.colFiles, 0);
-  OV.NeedRedraw(c.app)
-END FileDialogUpdateFileList;
-
-PROCEDURE FileDialogApplyDir*(c: FileDialog; s: ARRAY OF CHAR);
-VAR i: INTEGER;
-  path: ARRAY 1024 OF CHAR;
-BEGIN
-  IF (s[0] = '/') OR (* Asbolute path *)
-     ('A' <= CAP(s[0])) & (CAP(s[0]) <= 'Z') & (s[1] = ':')
-  THEN path := s$
-  ELSE path := c.path$;
-    WHILE (s[0] = '.') & (s[1] = '.') & (s[2] = '/') &
-          ~((path[1] = ':') & (path[2] = '/') & (path[3] = 0X))
-    DO Strings.Delete(s, 0, 3); i := Strings.Length(path) - 2;
-      WHILE (i >= 0) & (path[i] # '/') DO DEC(i) END;
-      IF i >= 0 THEN path[i + 1] := 0X ELSE path := '' END
-    END;
-    IF ~((s[0] = '.') & (s[1] = '.') & (s[2] = '/')) THEN
-      Strings.Append(s, path)
-    END
-  END;
-  IF Dir.IsDir(path) THEN c.path := path$ END
-END FileDialogApplyDir;
-
-PROCEDURE Match(IN what, where: ARRAY OF CHAR; off: INTEGER): BOOLEAN;
-VAR i, j: INTEGER;
-BEGIN i := 0; j := off;
-  WHILE (i < LEN(what)) & (what[i] # 0X) &
-        (j < LEN(where)) & (where[j] # 0X) &
-        (what[i] = where[j])
-  DO INC(i); INC(j)
-  END ;
-RETURN (i < LEN(what)) & (j < LEN(where)) & (what[i] = 0X) END Match;
-
-PROCEDURE FileDialogOkClick*(c: OV.Control);
-VAR w: FileDialog;
-  s, full: ARRAY 257 OF CHAR;
-  i, L: INTEGER;
-BEGIN w := c.parent(FileDialog); s := w.edtFilename.caption$;
-  (* Replace all \ with / and set L to length of s *)
-  L := 0; WHILE s[L] # 0X DO IF s[L] = '\' THEN s[L] := '/' END; INC(L) END;
-  IF L > 0 THEN
-    IF (s[L - 1] = '/') OR (s[0] = '/') THEN
-      IF s[L - 1] # '/' THEN s[L] := '/'; INC(L); s[L] := 0X END;
-      FileDialogApplyDir(w, s); FileDialogUpdateFileList(w)
-    ELSE full := w.path$;
-      WHILE (s[0] = '.') & (s[1] = '.') & (s[2] = '/') &
-            ~((full[1] = ':') & (full[2] = '/') & (full[3] = 0X))
-      DO Strings.Delete(s, 0, 3); i := Strings.Length(full) - 2;
-        WHILE (i >= 0) & (full[i] # '/') DO DEC(i) END;
-        IF i >= 0 THEN full[i + 1] := 0X ELSE full := '' END
-      END;
-      IF ~((s[0] = '.') & (s[1] = '.') & (s[2] = '/')) THEN
-        Strings.Append(s, full);
-        IF Match(w.home, full, 0) THEN
-          Strings.Delete(full, 0, Strings.Length(w.home));
-          Strings.Insert(stdPath, 0, full)
-        END;
-        OV.CloseCurWindow(c);
-        IF w.onFileOk # NIL THEN w.onFileOk(c, full) END
-      END
-    END
-  END
-END FileDialogOkClick;
-
-PROCEDURE FileDialogSetFname*(F: FileDialog; fname: ARRAY OF CHAR);
-VAR s, path: ARRAY 256 OF CHAR;
-  L, last: INTEGER;
-BEGIN s := fname$; path[0] := 0X;
-  L := 0; last := -1; (* Position of the last / or \ *)
-  WHILE s[L] # 0X DO (* Change all \ to /, set L to length of s, set last *)
-    IF (s[L] = '\') OR (s[L] = '/') THEN s[L] := '/'; last := L END;
-    INC(L)
-  END;
-
-  IF last # -1 THEN (* Split path and fname (s) if there was / or \ *)
-    Strings.Extract(s, 0, last + 1, path);
-    Strings.Delete(s, 0, last + 1)
-  END;
-
-  F.path := path$;
-  FileDialogUpdateFileList(F);
-  OV.EditSetCaption(F.edtFilename, s)
-END FileDialogSetFname;
-
-PROCEDURE FileDialogListChange*(c: OV.Control);
-VAR C: OV.ColumnList; w: FileDialog;
-  s: ARRAY 256 OF CHAR;
-BEGIN C := c(OV.ColumnList); w := C.parent(FileDialog);
-  StrList.SetPos(C.items, C.cur); StrList.Next(C.items, s);
-  IF s[0] = '[' THEN Strings.Delete(s, 0, 1);
-    s[Strings.Length(s) - 1] := '/'
-  END;
-  OV.EditSetCaption(w.edtFilename, s)
-END FileDialogListChange;
-
-PROCEDURE InitFileDialog*(c: FileDialog; type: INTEGER);
-VAR L: INTEGER;
-BEGIN OV.InitWindow(c); c.do := fileDialogMethod; c.type := type;
-  IF type = open THEN c.caption := 'Open a File'
-  ELSE c.caption := 'Save File As'
-  END;
-  c.modal := TRUE; c.w := 70; c.h := 20; OV.CenterWindow(c);
-
-  (* File Name Edit *)
-  c.edtFilename := OV.NewEdit();
-  c.edtFilename.do.resize(c.edtFilename, 3, 3, c.w - 18, 1);
-  OV.Add(c, c.edtFilename);
-
-  (* Open/Save (Ok) button *)
-  IF type = open THEN c.btnOk := OV.NewButton('&Open')
-  ELSE c.btnOk := OV.NewButton('O&K')
-  END;
-  c.btnOk.default := TRUE;
-  c.btnOk.tabIndex := 2;
-  c.btnOk.onClick := FileDialogOkClick;
-  c.btnOk.do.resize(c.btnOk, c.w - 13, 3, 9, 1);
-  OV.Add(c, c.btnOk);
-
-  (* Cancel button *)
-  c.btnCancel := OV.NewButton('Cancel');
-  c.btnCancel.tabIndex := 3;
-  c.btnCancel.onClick := OV.CloseCurWindow;
-  c.btnCancel.do.resize(c.btnCancel, c.w - 13, 11, 9, 1);
-  OV.Add(c, c.btnCancel);
-
-  Dir.GetCwd(c.home); L := 0;
-  WHILE c.home[L] # 0X DO (* Replace \ with /, set L to length of c.home *)
-    IF c.home[L] = '\' THEN c.home[L] := '/' END; INC(L)
-  END; (* L = length of c.home; append / if it is not in the end *)
-  IF c.home[L - 1] # '/' THEN c.home[L] := '/'; INC(L); c.home[L] := 0X END;
-  Strings.Append(stdPath, c.home); c.path := c.home$;
-
-  (* ColumnList *)
-  c.colFiles := OV.NewColumnList();
-  c.colFiles.tabIndex := 1;
-  c.colFiles.onChange := FileDialogListChange;
-  c.colFiles.onDblClick := FileDialogOkClick;
-  c.colFiles.do.resize(c.colFiles, 3, 6, c.w - 18, c.h - 8);
-  FileDialogUpdateFileList(c);
-  OV.Refresh(c.colFiles);
-  OV.Add(c, c.colFiles)
-END InitFileDialog;
-
-PROCEDURE NewFileDialog*(type: INTEGER): FileDialog;
-VAR c: FileDialog;
-BEGIN NEW(c); InitFileDialog(c, type) ;
-RETURN c END NewFileDialog;
-
-(* FileDialog Method *)
-
-PROCEDURE FileDialogDraw*(c: OV.Control; x, y: INTEGER);
-VAR f: FileDialog;
-BEGIN f := c(FileDialog);
-  OV.WindowDraw(c, x, y); INC(x, c.x); INC(y, c.y);
-  IF f.type = open THEN OV.PutMarkedString(x + 3, y + 2, '&Name', 15, 14, 7, 0)
-  ELSE OV.PutMarkedString(x + 3, y + 2, '&Save file as', 15, 14, 7, 0)
-  END;
-  OV.PutMarkedString(x + 3, y + 5, '&Files', 0, 14, 7, 0)
-END FileDialogDraw;
-
-PROCEDURE FileDialogGetFocus(c: OV.Control);
-BEGIN OV.WindowGetFocus(c)
-END FileDialogGetFocus;
-
-PROCEDURE InitFileDialogMethod*(m: OV.ControlMethod);
-BEGIN OV.InitWindowMethod(m);
-  m.draw := FileDialogDraw;
-  m.getFocus := FileDialogGetFocus
-END InitFileDialogMethod;
-
-(* Editor *)
-
-PROCEDURE IsEmpty*(e: Editor): BOOLEAN;
-BEGIN
-  RETURN (e.fname[0] = 0X) & (e.text.first = e.text.last) &
-         (e.text.first.len = 0)
-END IsEmpty;
-
-PROCEDURE IntToStr*(n: INTEGER; VAR s: ARRAY OF CHAR); (* !TODO move out *)
-(* LEN(s) > 1 *)
-VAR i, j: INTEGER; tmp: CHAR; neg: BOOLEAN;
-BEGIN
-  IF n = 0 THEN
-    s[0] := '0'; i := 1
-  ELSE i := 0; neg := n < 0; 
-    IF neg THEN n := -n END;
-    WHILE (n > 0) & (i < LEN(s) - 1) DO
-      s[i] := CHR(ORD('0') + n MOD 10);
-      n := n DIV 10; INC(i)
-    END;
-    IF neg & (i < LEN(s) - 1) THEN s[i] := '-'; INC(i) END;
-  END;
-  s[i] := 0X; j := 0; DEC(i);
-  WHILE j < i DO
-    tmp := s[j]; s[j] := s[i]; s[i] := tmp;
-    INC(j); DEC(i)
-  END
-END IntToStr;
-
-PROCEDURE StringsFindPrev* (pattern, stringToSearch: ARRAY OF CHAR; startPos: INTEGER;
-                     VAR patternFound: BOOLEAN; VAR posOfPattern: INTEGER); (* !TODO move out *)
-VAR patternPos, stringLength, patternLength: INTEGER;
-BEGIN
-  (* correct `startPos' if it is larger than the possible searching range *)
-  stringLength := Strings.Length (stringToSearch);
-  patternLength := Strings.Length (pattern);
-  IF (startPos > stringLength-patternLength) THEN
-    startPos := stringLength-patternLength
-  END;
-  
-  IF (startPos >= 0) THEN
-    patternPos := 0;
-    LOOP
-      IF (pattern[patternPos] = 0X) THEN     
-        (* reached end of pattern *)
-        patternFound := TRUE;
-        posOfPattern := startPos-patternPos;
-        EXIT
-      ELSIF (stringToSearch[startPos] # pattern[patternPos]) THEN
-        (* characters differ: reset indices and restart *)
-        IF (startPos > patternPos) THEN
-          startPos := startPos-patternPos-1;
-          patternPos := 0
-        ELSE
-          (* reached beginning of `stringToSearch' without finding a match *)
-          patternFound := FALSE;
-          EXIT
-        END
-      ELSE  (* characters identic, compare next one *)
-        INC (startPos); 
-        INC (patternPos)
-      END
-    END
-  ELSE
-    patternFound := FALSE
-  END
-END StringsFindPrev;
-
-PROCEDURE GetCharClass(ch: CHAR): INTEGER;
-VAR class: INTEGER;
-BEGIN
-  CASE ch OF
-    'a'..'z', 'A'..'Z': class := charAlpha
-  | '0'..'9': class := charDigit
-  | '-', '+': class := charMinusPlus
-  | '"', "'": class := charQuote
-  | '(': class := charOpenBracket
-  ELSE class := charOther END ;
-RETURN class END GetCharClass;
-
-PROCEDURE IsHexDigit(ch: CHAR): BOOLEAN;
-VAR result: BOOLEAN;
-BEGIN
-  CASE ch OF
-    'A'..'F': result := TRUE
-  ELSE result := FALSE END ;
-RETURN result END IsHexDigit;
-
-PROCEDURE IsKeywordInString(s: ARRAY OF CHAR; x, len: INTEGER): BOOLEAN;
-VAR part: ARRAY 32 OF CHAR; result: BOOLEAN;
-  PROCEDURE KW(keyword: ARRAY OF CHAR): BOOLEAN;
-  BEGIN RETURN keyword = part
-  END KW;
-BEGIN
-  IF (s[x] >= 'A') & (s[x] <= 'Z') THEN
-    Strings.Extract(s, x, len, part);
-    result := KW('ABS') OR KW('ASH') OR KW('BOOLEAN') OR KW('CAP') OR
-      KW('CHAR') OR KW('CHR') OR KW('COPY') OR KW('DEC') OR KW('ENTIER') OR
-      KW('EXCL') OR KW('FALSE') OR KW('HALT') OR KW('INC') OR KW('INCL') OR
-      KW('INTEGER') OR KW('LEN') OR KW('LONG') OR KW('LONGINT') OR
-      KW('LONGREAL') OR KW('MAX') OR KW('MIN') OR KW('NEW') OR KW('ODD') OR
-      KW('ORD') OR KW('REAL') OR KW('SET') OR KW('SHORT') OR
-      KW('SHORTINT') OR KW('SIZE') OR KW('TRUE') OR KW('ARRAY') OR
-      KW('BEGIN') OR KW('BY') OR KW('CASE') OR KW('DIV') OR KW('DO') OR
-      KW('ELSIF') OR KW('END') OR KW('EXIT') OR KW('FOR') OR KW('IF') OR
-      KW('IMPORT') OR KW('IN') OR KW('IS') OR KW('LOOP') OR
-      KW('MODULE') OR KW('NIL') OR KW('OR') OR KW('POINTER') OR
-      KW('PROCEDURE') OR KW('RECORD') OR KW('REPEAT') OR KW('RETURN') OR
-      KW('THEN') OR KW('TO') OR KW('TYPE') OR KW('VAR') OR KW('WHILE') OR
-      KW('ELSE') OR KW('OF') OR KW('WITH') OR KW('LONGSET') OR
-      KW('UNTIL') OR KW('CONST') OR KW('MOD') OR KW('FLOOR')
-  ELSE result := FALSE
-  END ;
-RETURN result END IsKeywordInString;
-
-PROCEDURE GetToken(s: ARRAY OF CHAR; x: INTEGER;
-  VAR class, len, comLevel: INTEGER);
-
-VAR i, chClass: INTEGER;
-
-  PROCEDURE TryNumber;
-  VAR ok, hex, hexEnding, point, scale,
-      scaleSign, scaleNum, finish: BOOLEAN;
-  BEGIN
-    IF (x = 0) OR
-       (~(GetCharClass(s[x - 1]) IN {charDigit, charAlpha}) &
-        (s[x - 1] # '.')) THEN
-      ok := TRUE; finish := FALSE; point := FALSE;
-      hex := FALSE; hexEnding := FALSE;
-      scale := FALSE; scaleSign := FALSE; scaleNum := FALSE;
-      REPEAT
-        CASE s[i] OF
-          '0'..'9': IF scale THEN scaleNum := TRUE END
-        | 'D', 'E':
-          IF point THEN
-            IF scale THEN ok := FALSE
-            ELSE scale := TRUE END
-          ELSE hex := TRUE END
-        | 'A', 'B', 'C', 'F':
-          IF point THEN ok := FALSE ELSE hex := TRUE END
-        | 'H', 'X':
-          IF point OR hexEnding THEN ok := FALSE
-          ELSE hexEnding := TRUE END
-        | '.': IF point OR hex THEN ok := FALSE ELSE point := TRUE END
-        | '-', '+':
-          IF hexEnding OR ~scale OR scaleNum THEN finish := TRUE
-          ELSE scaleSign := TRUE END
-        ELSE
-          IF point & scale & ~scaleNum THEN ok := FALSE
-          ELSE finish := TRUE END
-        END;
-        INC(i)
-      UNTIL ~ok OR finish;
-      IF ok & (~hex OR hexEnding) THEN
-        IF GetCharClass(s[i - 1]) # charAlpha THEN
-          len := i - x - 1;
-          class := tokenNumber
-        END
-      END
-    END
-  END TryNumber;
-
-  PROCEDURE TryString;
-  VAR quote: CHAR;
-  BEGIN
-    quote := s[x];
-    WHILE (s[i] # 0X) & (s[i] # quote) DO INC(i) END;
-    IF s[i] # 0X THEN
-      len := i - x + 1;
-      class := tokenString
-    END
-  END TryString;
-
-  PROCEDURE TryComment;
-  BEGIN
-    IF (i > 0) & (s[i] = '*') THEN INC(i); INC(comLevel) END;
-    IF comLevel > 0 THEN
-      REPEAT
-        WHILE (s[i] # 0X) & (s[i] # '*') DO INC(i) END;
-        IF s[i] = '*' THEN
-          IF (i > 0) & (s[i - 1] = '(') THEN INC(comLevel)
-          ELSIF s[i + 1] = ')' THEN DEC(comLevel) END;
-          INC(i)
-        END
-      UNTIL (s[i] = 0X) OR (comLevel <= 0);
-      len := i - x + 1;
-      class := tokenComment
-    END
-  END TryComment;
-
-BEGIN
-  class := tokenOther; len := 0;
-  IF s[x] # 0X THEN
-    IF (x = 0) & (comLevel > 0) THEN i := 0; TryComment END;
-    IF (s[x] # 0X) & (class # tokenComment) THEN
-      i := x + 1;
-      chClass := GetCharClass(s[x]);
-      CASE chClass OF
-        charAlpha:
-        IF (x = 0) OR (GetCharClass(s[x - 1]) # charDigit) THEN
-          WHILE GetCharClass(s[i]) = charAlpha DO INC(i) END;
-          len := i - x;
-          IF IsKeywordInString(s, x, len) THEN class := tokenKeyword END
-        END
-      | charDigit: TryNumber
-      | charQuote: TryString
-      | charOpenBracket: TryComment
-      ELSE len := 1 END
-    END
-  END
-END GetToken;
-
-PROCEDURE GetTokenColor(class: INTEGER): INTEGER;
-VAR color: INTEGER;
-BEGIN
-  CASE class OF
-    tokenKeyword: color := 15
-  | tokenNumber:  color := 3
-  | tokenString:  color := 11
-  | tokenComment: color := 7
-  ELSE color := 14 END ;
-RETURN color END GetTokenColor;
-
-(* Like T.PutString, but highlighted *)
-PROCEDURE PutStringH*(x, y: INTEGER; s: ARRAY OF CHAR;
-  bg, limit: INTEGER; VAR comLevel: INTEGER);
-VAR i, fg, class, len: INTEGER;
-BEGIN
-  IF limit = 0 THEN limit := T.charsX END; 
-  GetToken(s, 0, class, len, comLevel);
-  fg := GetTokenColor(class); i := 0;
-  WHILE (s[i] # 0X) & (x < limit) DO
-    T.PutChar(x, y, s[i], fg, bg);
-    INC(i); INC(x); DEC(len);
-    IF len <= 0 THEN
-      GetToken(s, i, class, len, comLevel);
-      fg := GetTokenColor(class)
-    END
-  END;
-  DEC(i);
-  WHILE (i >= 0) & (s[i] = ' ') DO
-    DEC(x);
-    T.PutChar(x, y, 0FAX, 3, bg);
-    DEC(i)
-  END
-END PutStringH;
-
-(* Put selected string (highlighted) *)
-PROCEDURE PutSelStringH*(x, y: INTEGER; s: ARRAY OF CHAR;
-  bg, limit: INTEGER; VAR comLevel: INTEGER;
-  fgSel, bgSel: INTEGER; x0, x1: INTEGER);
-VAR i, fg, class, len: INTEGER; sel: BOOLEAN;
-BEGIN
-  sel := FALSE;
-  GetToken(s, 0, class, len, comLevel);
-  fg := GetTokenColor(class); i := 0;
-  WHILE (s[i] # 0X) & (x < limit) DO
-    IF i = x1 + 1 THEN sel := FALSE
-    ELSIF i = x0 THEN sel := TRUE END;
-    IF sel THEN T.PutChar(x, y, s[i], fgSel, bgSel)
-    ELSE T.PutChar(x, y, s[i], fg, bg) END;
-    INC(i); INC(x); DEC(len);
-    IF len <= 0 THEN
-      GetToken(s, i, class, len, comLevel);
-      fg := GetTokenColor(class)
-    END
-  END
-END PutSelStringH;
-
-PROCEDURE TextChanged*(c: Editor): BOOLEAN;
-BEGIN RETURN c.text.changed
-END TextChanged;
-
-PROCEDURE PrintText*(c: Editor);
-VAR L: Text.Line; scrY: INTEGER;
-  textY, x0, x1, minX, maxX, maxY, comLevel: INTEGER;
-  cursorHandled: BOOLEAN;
-BEGIN T.CharFill(c.x + 1, c.y + 1, c.w - 2, c.h - 2, ' ', 15, c.bg);
-  L := c.text.scrFirst; scrY := c.y + 1; textY := c.text.scrY;
-  minX := c.x + 1; maxX := c.x + c.w - 1;
-  maxY := c.y + c.h - 1; comLevel := L.comLevel;
-  cursorHandled := FALSE;
-  WHILE (L # NIL) & (scrY < maxY) DO
-    IF c.text.selected & (c.text.selT <= textY) & (textY <= c.text.selB) THEN
-      IF textY = c.text.selT THEN x0 := c.text.selL ELSE x0 := 0 END;
-      IF textY = c.text.selB THEN x1 := c.text.selR - 1 ELSE x1 := L.len END;
-      PutSelStringH(minX, scrY, L.s, c.bg, maxX, comLevel, 1, 7, x0, x1)
-    ELSE PutStringH(minX, scrY, L.s, c.bg, maxX, comLevel)
-    END;
-    IF (L = c.text.cur) & (c.text.x + 1 < T.charsX) &
-       ~OV.HasModalWindow(c.app) THEN
-      x0 := minX + c.text.x;
-      IF c.focused & (x0 <= maxX) THEN
-        T.ShowCursor(TRUE); T.GoToXY(x0, scrY);
-        cursorHandled := TRUE
-      END
-    END;
-    L := L.next; INC(scrY); INC(textY);
-    IF L # NIL THEN L.comLevel := comLevel END
-  END;
-  IF c.focused & ~cursorHandled THEN T.ShowCursor(FALSE) END
-END PrintText;
-
-PROCEDURE StartSelection(c: Editor);
-BEGIN c.text.selected := TRUE;
-  c.text.selL := c.text.x; c.text.selR := c.text.x;
-  c.text.selT := c.text.y; c.text.selB := c.text.y
-END StartSelection;
-
-PROCEDURE CheckSelBorders(c: Editor; VAR onLeft, onRight: BOOLEAN);
-BEGIN
-  onLeft := (c.text.x = c.text.selL) & (c.text.y = c.text.selT);
-  onRight := (c.text.x = c.text.selR) & (c.text.y = c.text.selB)
-END CheckSelBorders;
-
-PROCEDURE Swap(VAR a, b: INTEGER);
-VAR tmp: INTEGER;
-BEGIN
-  tmp := a; a := b; b := tmp
-END Swap;
-
-PROCEDURE Order(VAR a, b: INTEGER);
-VAR tmp: INTEGER;
-BEGIN
-  IF a > b THEN tmp := a; a := b; b := tmp END
-END Order;
-
-(* Starts, removes, upgrades selection if Shift held. *)
-(* dir is one of Direction of Selection constants. *)
-PROCEDURE HandleSelection(c: Editor; dir: INTEGER; viaHoriz: BOOLEAN);
-VAR mod: SET; onLeft, onRight: BOOLEAN;
-BEGIN
-  IF ~G.ShiftPressed() THEN c.text.selected := FALSE
-  ELSE CheckSelBorders(c, onLeft, onRight);
-    IF ~c.text.selected OR (~onLeft & ~onRight) THEN
-      StartSelection(c); (* Reset/new selection *)
-      CheckSelBorders(c, onLeft, onRight)
-    END;
-    CASE dir OF
-      dirRight:
-      IF onRight THEN INC(c.text.selR)
-      ELSIF onLeft THEN INC(c.text.selL) END
-    | dirLeft:
-      IF onLeft THEN DEC(c.text.selL)
-      ELSIF onRight THEN DEC(c.text.selR) END
-    | dirUp:
-      IF onLeft THEN
-        DEC(c.text.selT);
-        IF viaHoriz THEN c.text.selL := c.text.cur.prev.len
-        ELSIF c.text.selL > c.text.cur.prev.len
-        THEN c.text.selL := c.text.cur.prev.len END
-      ELSIF viaHoriz THEN
-        DEC(c.text.selB); c.text.selR := c.text.cur.prev.len
-      ELSIF (c.text.selB > c.text.selT + 1) OR
-         ((c.text.selB = c.text.selT + 1) &
-          (c.text.selR >= c.text.selL)) THEN
-        DEC(c.text.selB);
-        IF c.text.selR > c.text.cur.prev.len
-        THEN c.text.selR := c.text.cur.prev.len END
-      ELSIF c.text.selT = c.text.selB THEN
-        DEC(c.text.selT); Order(c.text.selR, c.text.selL);
-        IF c.text.selL > c.text.cur.prev.len
-        THEN c.text.selL := c.text.cur.prev.len END
-      ELSE
-        DEC(c.text.selB); Order(c.text.selL, c.text.selR);
-        IF c.text.selR > c.text.cur.prev.len
-        THEN c.text.selR := c.text.cur.prev.len END
-      END
-    | dirDown:
-      IF onRight THEN
-        INC(c.text.selB);
-        IF viaHoriz THEN c.text.selR := 0
-        ELSIF c.text.selR > c.text.cur.next.len
-        THEN c.text.selR := c.text.cur.next.len END
-      ELSIF viaHoriz THEN
-        INC(c.text.selT); c.text.selL := 0
-      ELSIF (c.text.selB > c.text.selT + 1) OR
-         ((c.text.selB = c.text.selT + 1) &
-          (c.text.selR >= c.text.selL)) THEN
-        INC(c.text.selT);
-        IF c.text.selL > c.text.cur.next.len
-        THEN c.text.selL := c.text.cur.next.len END
-      ELSIF c.text.selT = c.text.selB THEN
-        INC(c.text.selB); Swap(c.text.selL, c.text.selR);
-        IF c.text.selR > c.text.cur.next.len
-        THEN c.text.selR := c.text.cur.next.len END
-      ELSE
-        INC(c.text.selT); Swap(c.text.selL, c.text.selR);
-        IF c.text.selL > c.text.cur.next.len
-        THEN c.text.selL := c.text.cur.next.len END
-      END
-    ELSE END;
-    IF ((c.text.selT >= c.text.selB) & (c.text.selL >= c.text.selR)) OR
-       (c.text.selT > c.text.selB) THEN
-      c.text.selected := FALSE
-    END
-  END
-END HandleSelection;
-
-PROCEDURE MoveScreenByLine(c: Editor; down: BOOLEAN);
-BEGIN
-  IF down THEN
-    ASSERT(c.text.scrFirst.next # NIL, 98);
-    c.text.scrFirst := c.text.scrFirst.next; INC(c.text.scrY)
-  ELSE (* Up *)
-    ASSERT(c.text.scrFirst.prev # NIL, 98); ASSERT(c.text.scrY > 0, 99);
-    c.text.scrFirst := c.text.scrFirst.prev; DEC(c.text.scrY)
-  END;
-  T.ResetCursorBlink
-END MoveScreenByLine;
-
-PROCEDURE MoveScreen(c: Editor; delta: INTEGER);
-BEGIN
-  IF delta > 0 THEN
-    WHILE (delta > 0) & (c.text.scrFirst.next # NIL) DO
-      MoveScreenByLine(c, TRUE); DEC(delta)
-    END
-  ELSE (* Up *)
-    WHILE (delta < 0) & (c.text.scrFirst.prev # NIL) DO
-      MoveScreenByLine(c, FALSE); INC(delta)
-    END
-  END
-END MoveScreen;
-
-(* Moves input cursor up and down *)
-PROCEDURE MoveByLine(c: Editor; down, viaHoriz: BOOLEAN);
-VAR moved: BOOLEAN;
-BEGIN
-  moved := FALSE;
-  IF down THEN
-    IF c.text.cur.next # NIL THEN
-      HandleSelection(c, dirDown, viaHoriz);
-      moved := TRUE;
-      c.text.cur := c.text.cur.next; INC(c.text.y);
-      IF c.text.y >= c.text.scrY + c.h - 2 THEN MoveScreenByLine(c, TRUE) END
-    END
-  ELSE (* Up *)
-    IF c.text.cur.prev # NIL THEN
-      HandleSelection(c, dirUp, viaHoriz);
-      moved := TRUE;
-      IF c.text.scrFirst = c.text.cur THEN MoveScreenByLine(c, FALSE) END;
-      c.text.cur := c.text.cur.prev; DEC(c.text.y)
-    END
-  END;
-  IF moved THEN
-    IF c.text.x > c.text.cur.len THEN c.text.x := c.text.cur.len END;
-    PrintText(c)
-  END;
-  T.ResetCursorBlink
-END MoveByLine;
-
-(* Moves input cursor up and down by page *)
-PROCEDURE MoveByPage(c: Editor; down: BOOLEAN);
-VAR i: INTEGER; moved: BOOLEAN;
-BEGIN i := c.h - 3;
-  IF down THEN
-    moved := c.text.cur.next # NIL;
-    WHILE (i > 0) & (c.text.cur.next # NIL) DO
-      c.text.scrFirst := c.text.scrFirst.next; INC(c.text.scrY);
-      MoveByLine(c, TRUE, FALSE);
-      DEC(i)
-    END
-  ELSE (* Up *)
-    moved := c.text.cur.prev # NIL;
-    WHILE (i > 0) & (c.text.cur.prev # NIL) DO
-      IF c.text.scrY > 0 THEN c.text.scrFirst := c.text.scrFirst.prev; DEC(c.text.scrY) END;
-      MoveByLine(c, FALSE, FALSE);
-      DEC(i)
-    END
-  END;
-  T.ResetCursorBlink;
-  IF moved THEN PrintText(c) END
-END MoveByPage;
-
-(* Moves input cursor left and right by one char *)
-PROCEDURE MoveInLine(c: Editor; right: BOOLEAN);
-BEGIN
-  IF right THEN
-    IF c.text.x < c.text.cur.len THEN
-      HandleSelection(c, dirRight, FALSE); INC(c.text.x)
-    ELSIF c.text.cur.next # NIL THEN
-      MoveByLine(c, TRUE, TRUE);
-      c.text.x := 0;
-    END
-  ELSE (* Left *)
-    IF c.text.x > 0 THEN
-      HandleSelection(c, dirLeft, FALSE); DEC(c.text.x)
-    ELSIF c.text.cur.prev # NIL THEN
-      MoveByLine(c, FALSE, TRUE);
-      c.text.x := c.text.cur.len;
-    END
-  END;
-  T.ResetCursorBlink
-END MoveInLine;
-
-(* Moves input cursor left and right by one word *)
-PROCEDURE MoveByWord(c: Editor; right: BOOLEAN);
-VAR kind: INTEGER;
-BEGIN
-  IF ~right THEN
-    REPEAT MoveInLine(c, FALSE)
-    UNTIL c.text.IsEdge() OR (c.text.CurCharKind() # Text.whitespace)
-  END;
-  kind := c.text.CurCharKind();
-  REPEAT MoveInLine(c, right)
-  UNTIL c.text.IsEdge() OR (c.text.CurCharKind() # kind);
-  IF right THEN
-    WHILE ~c.text.IsEdge() & (c.text.CurCharKind() = Text.whitespace) DO
-      MoveInLine(c, TRUE)
-    END
-  ELSIF ~c.text.IsEdge() THEN MoveInLine(c, TRUE)
-  END
-END MoveByWord;
-
-(* Moves input cursor to start and to end *)
-PROCEDURE MoveToLineEdge(c: Editor; end: BOOLEAN);
-VAR onLeft, onRight: BOOLEAN;
-BEGIN
-  IF G.ShiftPressed() THEN
-    IF ~c.text.selected THEN StartSelection(c)
-    ELSE
-      CheckSelBorders(c, onLeft, onRight);
-      IF ~onLeft & ~onRight THEN StartSelection(c) END
-    END;
-    CheckSelBorders(c, onLeft, onRight);
-    IF end THEN
-      IF onRight THEN c.text.selR := c.text.cur.len
-      ELSE c.text.selL := c.text.cur.len END
-    ELSE (* Home *)
-      IF onRight THEN c.text.selR := 0
-      ELSE c.text.selL := 0 END
-    END;
-    IF c.text.selT = c.text.selB THEN Order(c.text.selL, c.text.selR) END
-  END;
-  IF end THEN
-    IF c.text.x < c.text.cur.len THEN c.text.x := c.text.cur.len END
-  ELSE (* Home *)
-    IF c.text.x > 0 THEN c.text.x := 0 END
-  END;
-  T.ResetCursorBlink;
-  PrintText(c)
-END MoveToLineEdge;
-
-PROCEDURE HandleBackspace(c: Editor);
-BEGIN
-  c.text.HandleBackspace;
-  PrintText(c)
-END HandleBackspace;
-
-PROCEDURE HandleDelete(c: Editor);
-BEGIN
-  c.text.HandleDelete;
-  PrintText(c)
-END HandleDelete;
-
-PROCEDURE HandleTab(e: Editor; shift: BOOLEAN);
-BEGIN
-  IF shift THEN e.text.RemoveIndent
-  ELSIF e.text.WholeLineSelected() THEN
-    IF shift THEN e.text.RemoveIndent
-    ELSE e.text.AddIndent
-    END
-  ELSE
-    IF e.text.selected THEN e.text.DeleteSelection END;
-    e.text.InsertChar(' ');
-    IF ODD(e.text.x MOD 2) THEN e.text.InsertChar(' ') END
-  END;
-  PrintText(e)
-END HandleTab;
-
-PROCEDURE HandleEnter(c: Editor);
-BEGIN
-  c.text.HandleEnter(TRUE);
-  IF c.text.y >= c.text.scrY + c.h - 2 THEN MoveScreenByLine(c, TRUE) END;
-  PrintText(c)
-END HandleEnter;
-
-PROCEDURE InitEditor*(c: Editor);
-BEGIN OV.InitWindow(c); c.closeOnEsc := FALSE;
-  c.fname[0] := 0X; c.bg := 1; c.resizable := TRUE; c.caption := 'NONAME';
-  c.text := Text.NewText(); c.do := editorMethod
-END InitEditor;
-
-PROCEDURE NewEditor*(): Editor;
-VAR w: Editor;
-BEGIN NEW(w); InitEditor(w) ;
-RETURN w END NewEditor;
-
-(* Standard Menu Hanlders *)
-
-PROCEDURE EditCut*(c: OV.Control);
-BEGIN
-  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
-    c.app.windows(Editor).text.CopySelection(clipboard);
-    c.app.windows(Editor).text.DeleteSelection
-  END
-END EditCut;
-
-PROCEDURE EditCopy*(c: OV.Control);
-BEGIN
-  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
-    c.app.windows(Editor).text.CopySelection(clipboard);
-    PrintText(c.app.windows(Editor))
-  END
-END EditCopy;
-
-PROCEDURE EditClear*(c: OV.Control);
-VAR e: OV.Control;
-BEGIN e := c.app.windows;
-  IF (e # NIL) & (e IS Editor) THEN
-    e(Editor).text.DeleteSelection;
-    PrintText(e(Editor))
-  END
-END EditClear;
-
-PROCEDURE EditPaste*(c: OV.Control);
-VAR e: Editor;
-BEGIN
-  IF (c.app.windows # NIL) & (c.app.windows IS Editor) &
-      (clipboard[0] # 0X) THEN
-    e := c.app.windows(Editor);
-    e.text.DeleteSelection;
-    e.text.Insert(clipboard, FALSE);
-    IF e.text.y >= e.text.scrY + e.h - 2 THEN
-      MoveScreen(e, e.text.y - e.h + 3 - e.text.scrY)
-    END;
-    PrintText(e)
-  END
-END EditPaste;
-
-PROCEDURE EditSelectAll*(c: OV.Control);
-BEGIN
-  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
-    c.app.windows(Editor).text.SelectAll;
-    PrintText(c.app.windows(Editor))
-  END
-END EditSelectAll;
-
-PROCEDURE EditUnselect*(c: OV.Control);
-BEGIN
-  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
-    c.app.windows(Editor).text.selected := FALSE;
-    PrintText(c.app.cur(Editor))
-  END
-END EditUnselect;
-
-(* EditorMethod *)
-
-PROCEDURE EditorDraw*(c: OV.Control; x, y: INTEGER);
-BEGIN OV.WindowDraw(c, x, y);
-  PrintText(c(Editor))
-END EditorDraw;
-
-PROCEDURE EditorMouseDown*(c: OV.Control; x, y, button: INTEGER);
-VAR t: Text.Text; L: Text.Line; i: INTEGER;
-BEGIN OV.WindowMouseDown(c, x, y, button);
-  IF (x > 0) & (x < c.w - 1) & (y > 0) & (y < c.h - 1) THEN
-    DEC(x); DEC(y); t := c(Editor).text;
-    t.selected := FALSE; L := t.scrFirst; i := y;
-    WHILE (i > 0) & (L # NIL) DO DEC(i); L := L.next END;
-    IF L # NIL THEN
-      t.cur := L; t.y := y + t.scrY;
-      IF x > L.len THEN x := L.len END;
-      t.x := x;
-      T.GoToXY(c.x + x + 1, c.y + y + 1)
-    END;
-    PrintText(c(Editor))
-  END
-END EditorMouseDown;
-
-PROCEDURE EditorMouseUp*(c: OV.Control; x, y, button: INTEGER);
-BEGIN
-  OV.WindowMouseUp(c, x, y, button)
-END EditorMouseUp;
-
-PROCEDURE EditorMouseMove*(c: OV.Control; x, y: INTEGER; buttons: SET);
-BEGIN
-  OV.WindowMouseMove(c, x, y, buttons)
-END EditorMouseMove;
-
-PROCEDURE EditorTextInput(c: OV.Control; ch: INTEGER);
-BEGIN
-  IF ch # 0 THEN c(Editor).text.InsertChar(OV.ToCP866(ch)); T.ResetCursorBlink;
-    c(Editor).text.selected := FALSE; PrintText(c(Editor))
-  END
-END EditorTextInput;
-
-PROCEDURE EditorKeyDown*(c: OV.Control; key: G.Key);
-BEGIN OV.WindowKeyDown(c, key);
-  CASE key.code OF
-    G.kLeft:
-    IF key.mod * G.mCtrl # {} THEN MoveByWord(c(Editor), FALSE)
-    ELSE MoveInLine(c(Editor), FALSE)
-    END;
-    PrintText(c(Editor))
-  | G.kRight:
-    IF key.mod * G.mCtrl # {} THEN MoveByWord(c(Editor), TRUE)
-    ELSE MoveInLine(c(Editor), TRUE)
-    END;
-    PrintText(c(Editor))
-  | G.kUp:        MoveByLine(c(Editor), FALSE, FALSE)
-  | G.kDown:      MoveByLine(c(Editor), TRUE, FALSE)
-  | G.kHome:      MoveToLineEdge(c(Editor), FALSE)
-  | G.kEnd:       MoveToLineEdge(c(Editor), TRUE)
-  | G.kPgUp:      MoveByPage(c(Editor), FALSE)
-  | G.kPgDn:      MoveByPage(c(Editor), TRUE)
-  | G.kBackspace: HandleBackspace(c(Editor))
-  | G.kDel:       HandleDelete(c(Editor))
-  | G.kEnter, G.kEnterPad: HandleEnter(c(Editor))
-  | G.kTab:       HandleTab(c(Editor), key.mod * G.mShift # {})
-  ELSE END
-END EditorKeyDown;
-
-PROCEDURE EditorGetFocus*(c: OV.Control);
-BEGIN
-  IF OV.windowMethod.getFocus # NIL THEN OV.windowMethod.getFocus(c) END;
-  G.StartTextInput; T.ShowCursor(TRUE)
-END EditorGetFocus;
-
-PROCEDURE EditorRefresh(c: OV.Control);
-BEGIN OV.WindowRefresh(c)
-END EditorRefresh;
-
-PROCEDURE InitEditorMethod*(m: OV.ControlMethod);
-BEGIN OV.InitWindowMethod(m);
-  m.getFocus := EditorGetFocus;
-  m.refresh := EditorRefresh;
-  m.draw := EditorDraw;
-  m.mouseDown := EditorMouseDown;
-  m.mouseUp := EditorMouseUp;
-  m.mouseMove := EditorMouseMove;
-  m.keyDown := EditorKeyDown;
-  m.textInput := EditorTextInput
-END InitEditorMethod;
-
-BEGIN
-  NEW(editorMethod); InitEditorMethod(editorMethod);
-  NEW(fileDialogMethod); InitFileDialogMethod(fileDialogMethod)
-END Editor.
+MODULE Editor;
+(* Copyright 2017-2021 Arthur Yefimov
+
+This file is part of Free Oberon.
+
+Free Oberon is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Free Oberon is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT OV, T := Terminal, G := Graph, Text := EditorText,
+  Strings, StrList, Dir, Out;
+CONST
+  stdPath* = 'Programs/';
+
+  (* Direction of Selection *)
+  dirLeft  = 0;
+  dirRight = 1;
+  dirUp    = 2;
+  dirDown  = 3;
+
+  (* Character Classes *)
+  charOther       = 0;
+  charAlpha       = 1;
+  charDigit       = 2;
+  charMinusPlus   = 3;
+  charQuote       = 4;
+  charOpenBracket = 5;
+
+  (* Token Classes *)
+  tokenOther   = 0;
+  tokenKeyword = 1;
+  tokenNumber  = 2;
+  tokenString  = 3;
+  tokenComment = 4;
+
+  (* FileDialog type *)
+  open* = 1;
+  save* = 2;
+
+TYPE
+  CHAR = SHORTCHAR;
+
+  FileDialog* = POINTER TO FileDialogDesc;
+  FileDialogDesc* = RECORD(OV.WindowDesc)
+    type*: INTEGER; (* open or save *)
+    edtFilename*: OV.Edit;
+    btnOk*, btnCancel*: OV.Button;
+    path*: ARRAY 1024 OF CHAR; (* full path to current chosen dir *)
+    home*: ARRAY 1024 OF CHAR; (* i.e. 'C:/FreeOberon/Programs' *)
+    colFiles*: OV.ColumnList;
+    onFileOk*: PROCEDURE (c: OV.Control; fname: ARRAY OF CHAR)
+  END;
+
+  Editor* = POINTER TO EditorDesc;
+  EditorDesc* = RECORD(OV.WindowDesc)
+    text*: Text.Text;
+    fname*: ARRAY 1000 OF CHAR
+  END;
+
+VAR
+  clipboard: ARRAY 16000 OF CHAR;
+  editorMethod-: OV.ControlMethod;
+  fileDialogMethod-: OV.ControlMethod;
+
+(* FileDialog *)
+
+PROCEDURE FileNamesCmp(IN a, b: ARRAY OF CHAR): INTEGER;
+VAR r: INTEGER;
+BEGIN
+  IF (a[0] = '[') & (b[0] # '[') OR (a = '[..]') THEN r := -1
+  ELSIF (a[0] # '[') & (b[0] = '[') OR (b = '[..]') THEN r := 1
+  ELSIF a < b THEN r := -1
+  ELSIF a = b THEN r := 0
+  ELSE r := 1
+  END ;
+RETURN r END FileNamesCmp;
+
+PROCEDURE FileDialogUpdateFileList*(c: FileDialog);
+VAR L: StrList.List;
+  r: Dir.Rec;
+  s: ARRAY 512 OF CHAR;
+  first: BOOLEAN;
+  n: INTEGER;
+BEGIN L := c.colFiles.items;
+  StrList.Clear(L);
+  Dir.First(r, c.path); first := TRUE;
+  WHILE ~r.eod DO
+    IF r.name # '.' THEN s := r.name$;
+      IF r.isDir THEN
+        IF first THEN n := Strings.Length(s);
+          s[n] := '/'; s[n + 1] := 0X;
+          OV.EditSetCaption(c.edtFilename, s);
+          s[n] := ']'; Strings.Insert('[', 0, s)
+        ELSE Strings.Insert('[', 0, s); Strings.Append(']', s)
+        END
+      ELSIF first THEN OV.EditSetCaption(c.edtFilename, s)
+      END;
+      StrList.Append(L, s); first := FALSE
+    END;
+    Dir.Next(r)
+  END;
+  StrList.Sort(L, FileNamesCmp);
+  OV.ColumnListSetCur(c.colFiles, 0);
+  OV.NeedRedraw(c.app)
+END FileDialogUpdateFileList;
+
+PROCEDURE FileDialogApplyDir*(c: FileDialog; s: ARRAY OF CHAR);
+VAR i: INTEGER;
+  path: ARRAY 1024 OF CHAR;
+BEGIN
+  IF (s[0] = '/') OR (* Asbolute path *)
+     ('A' <= CAP(s[0])) & (CAP(s[0]) <= 'Z') & (s[1] = ':')
+  THEN path := s$
+  ELSE path := c.path$;
+    WHILE (s[0] = '.') & (s[1] = '.') & (s[2] = '/') &
+          ~((path[1] = ':') & (path[2] = '/') & (path[3] = 0X))
+    DO Strings.Delete(s, 0, 3); i := Strings.Length(path) - 2;
+      WHILE (i >= 0) & (path[i] # '/') DO DEC(i) END;
+      IF i >= 0 THEN path[i + 1] := 0X ELSE path := '' END
+    END;
+    IF ~((s[0] = '.') & (s[1] = '.') & (s[2] = '/')) THEN
+      Strings.Append(s, path)
+    END
+  END;
+  IF Dir.IsDir(path) THEN c.path := path$ END
+END FileDialogApplyDir;
+
+PROCEDURE Match(IN what, where: ARRAY OF CHAR; off: INTEGER): BOOLEAN;
+VAR i, j: INTEGER;
+BEGIN i := 0; j := off;
+  WHILE (i < LEN(what)) & (what[i] # 0X) &
+        (j < LEN(where)) & (where[j] # 0X) &
+        (what[i] = where[j])
+  DO INC(i); INC(j)
+  END ;
+RETURN (i < LEN(what)) & (j < LEN(where)) & (what[i] = 0X) END Match;
+
+PROCEDURE FileDialogOkClick*(c: OV.Control);
+VAR w: FileDialog;
+  s, full: ARRAY 257 OF CHAR;
+  i, L: INTEGER;
+BEGIN w := c.parent(FileDialog); s := w.edtFilename.caption$;
+  (* Replace all \ with / and set L to length of s *)
+  L := 0; WHILE s[L] # 0X DO IF s[L] = '\' THEN s[L] := '/' END; INC(L) END;
+  IF L > 0 THEN
+    IF (s[L - 1] = '/') OR (s[0] = '/') THEN
+      IF s[L - 1] # '/' THEN s[L] := '/'; INC(L); s[L] := 0X END;
+      FileDialogApplyDir(w, s); FileDialogUpdateFileList(w)
+    ELSE full := w.path$;
+      WHILE (s[0] = '.') & (s[1] = '.') & (s[2] = '/') &
+            ~((full[1] = ':') & (full[2] = '/') & (full[3] = 0X))
+      DO Strings.Delete(s, 0, 3); i := Strings.Length(full) - 2;
+        WHILE (i >= 0) & (full[i] # '/') DO DEC(i) END;
+        IF i >= 0 THEN full[i + 1] := 0X ELSE full := '' END
+      END;
+      IF ~((s[0] = '.') & (s[1] = '.') & (s[2] = '/')) THEN
+        Strings.Append(s, full);
+        IF Match(w.home, full, 0) THEN
+          Strings.Delete(full, 0, Strings.Length(w.home));
+          Strings.Insert(stdPath, 0, full)
+        END;
+        OV.CloseCurWindow(c);
+        IF w.onFileOk # NIL THEN w.onFileOk(c, full) END
+      END
+    END
+  END
+END FileDialogOkClick;
+
+PROCEDURE FileDialogSetFname*(F: FileDialog; fname: ARRAY OF CHAR);
+VAR s, path: ARRAY 256 OF CHAR;
+  L, last: INTEGER;
+BEGIN s := fname$; path[0] := 0X;
+  L := 0; last := -1; (* Position of the last / or \ *)
+  WHILE s[L] # 0X DO (* Change all \ to /, set L to length of s, set last *)
+    IF (s[L] = '\') OR (s[L] = '/') THEN s[L] := '/'; last := L END;
+    INC(L)
+  END;
+
+  IF last # -1 THEN (* Split path and fname (s) if there was / or \ *)
+    Strings.Extract(s, 0, last + 1, path);
+    Strings.Delete(s, 0, last + 1)
+  END;
+
+  F.path := path$;
+  FileDialogUpdateFileList(F);
+  OV.EditSetCaption(F.edtFilename, s)
+END FileDialogSetFname;
+
+PROCEDURE FileDialogListChange*(c: OV.Control);
+VAR C: OV.ColumnList; w: FileDialog;
+  s: ARRAY 256 OF CHAR;
+BEGIN C := c(OV.ColumnList); w := C.parent(FileDialog);
+  StrList.SetPos(C.items, C.cur); StrList.Next(C.items, s);
+  IF s[0] = '[' THEN Strings.Delete(s, 0, 1);
+    s[Strings.Length(s) - 1] := '/'
+  END;
+  OV.EditSetCaption(w.edtFilename, s)
+END FileDialogListChange;
+
+PROCEDURE InitFileDialog*(c: FileDialog; type: INTEGER);
+VAR L: INTEGER;
+BEGIN OV.InitWindow(c); c.do := fileDialogMethod; c.type := type;
+  IF type = open THEN c.caption := 'Open a File'
+  ELSE c.caption := 'Save File As'
+  END;
+  c.modal := TRUE; c.w := 70; c.h := 20; OV.CenterWindow(c);
+
+  (* File Name Edit *)
+  c.edtFilename := OV.NewEdit();
+  c.edtFilename.do.resize(c.edtFilename, 3, 3, c.w - 18, 1);
+  OV.Add(c, c.edtFilename);
+
+  (* Open/Save (Ok) button *)
+  IF type = open THEN c.btnOk := OV.NewButton('&Open')
+  ELSE c.btnOk := OV.NewButton('O&K')
+  END;
+  c.btnOk.default := TRUE;
+  c.btnOk.tabIndex := 2;
+  c.btnOk.onClick := FileDialogOkClick;
+  c.btnOk.do.resize(c.btnOk, c.w - 13, 3, 9, 1);
+  OV.Add(c, c.btnOk);
+
+  (* Cancel button *)
+  c.btnCancel := OV.NewButton('Cancel');
+  c.btnCancel.tabIndex := 3;
+  c.btnCancel.onClick := OV.CloseCurWindow;
+  c.btnCancel.do.resize(c.btnCancel, c.w - 13, 11, 9, 1);
+  OV.Add(c, c.btnCancel);
+
+  Dir.GetCwd(c.home); L := 0;
+  WHILE c.home[L] # 0X DO (* Replace \ with /, set L to length of c.home *)
+    IF c.home[L] = '\' THEN c.home[L] := '/' END; INC(L)
+  END; (* L = length of c.home; append / if it is not in the end *)
+  IF c.home[L - 1] # '/' THEN c.home[L] := '/'; INC(L); c.home[L] := 0X END;
+  Strings.Append(stdPath, c.home); c.path := c.home$;
+
+  (* ColumnList *)
+  c.colFiles := OV.NewColumnList();
+  c.colFiles.tabIndex := 1;
+  c.colFiles.onChange := FileDialogListChange;
+  c.colFiles.onDblClick := FileDialogOkClick;
+  c.colFiles.do.resize(c.colFiles, 3, 6, c.w - 18, c.h - 8);
+  FileDialogUpdateFileList(c);
+  OV.Refresh(c.colFiles);
+  OV.Add(c, c.colFiles)
+END InitFileDialog;
+
+PROCEDURE NewFileDialog*(type: INTEGER): FileDialog;
+VAR c: FileDialog;
+BEGIN NEW(c); InitFileDialog(c, type) ;
+RETURN c END NewFileDialog;
+
+(* FileDialog Method *)
+
+PROCEDURE FileDialogDraw*(c: OV.Control; x, y: INTEGER);
+VAR f: FileDialog;
+BEGIN f := c(FileDialog);
+  OV.WindowDraw(c, x, y); INC(x, c.x); INC(y, c.y);
+  IF f.type = open THEN OV.PutMarkedString(x + 3, y + 2, '&Name', 15, 14, 7, 0)
+  ELSE OV.PutMarkedString(x + 3, y + 2, '&Save file as', 15, 14, 7, 0)
+  END;
+  OV.PutMarkedString(x + 3, y + 5, '&Files', 0, 14, 7, 0)
+END FileDialogDraw;
+
+PROCEDURE FileDialogGetFocus(c: OV.Control);
+BEGIN OV.WindowGetFocus(c)
+END FileDialogGetFocus;
+
+PROCEDURE InitFileDialogMethod*(m: OV.ControlMethod);
+BEGIN OV.InitWindowMethod(m);
+  m.draw := FileDialogDraw;
+  m.getFocus := FileDialogGetFocus
+END InitFileDialogMethod;
+
+(* Editor *)
+
+PROCEDURE IsEmpty*(e: Editor): BOOLEAN;
+BEGIN
+  RETURN (e.fname[0] = 0X) & (e.text.first = e.text.last) &
+         (e.text.first.len = 0)
+END IsEmpty;
+
+PROCEDURE IntToStr*(n: INTEGER; VAR s: ARRAY OF CHAR); (* !TODO move out *)
+(* LEN(s) > 1 *)
+VAR i, j: INTEGER; tmp: CHAR; neg: BOOLEAN;
+BEGIN
+  IF n = 0 THEN
+    s[0] := '0'; i := 1
+  ELSE i := 0; neg := n < 0;
+    IF neg THEN n := -n END;
+    WHILE (n > 0) & (i < LEN(s) - 1) DO
+      s[i] := CHR(ORD('0') + n MOD 10);
+      n := n DIV 10; INC(i)
+    END;
+    IF neg & (i < LEN(s) - 1) THEN s[i] := '-'; INC(i) END;
+  END;
+  s[i] := 0X; j := 0; DEC(i);
+  WHILE j < i DO
+    tmp := s[j]; s[j] := s[i]; s[i] := tmp;
+    INC(j); DEC(i)
+  END
+END IntToStr;
+
+PROCEDURE StringsFindPrev* (pattern, stringToSearch: ARRAY OF CHAR; startPos: INTEGER;
+                     VAR patternFound: BOOLEAN; VAR posOfPattern: INTEGER); (* !TODO move out *)
+VAR patternPos, stringLength, patternLength: INTEGER;
+BEGIN
+  (* correct `startPos' if it is larger than the possible searching range *)
+  stringLength := Strings.Length (stringToSearch);
+  patternLength := Strings.Length (pattern);
+  IF (startPos > stringLength-patternLength) THEN
+    startPos := stringLength-patternLength
+  END;
+
+  IF (startPos >= 0) THEN
+    patternPos := 0;
+    LOOP
+      IF (pattern[patternPos] = 0X) THEN
+        (* reached end of pattern *)
+        patternFound := TRUE;
+        posOfPattern := startPos-patternPos;
+        EXIT
+      ELSIF (stringToSearch[startPos] # pattern[patternPos]) THEN
+        (* characters differ: reset indices and restart *)
+        IF (startPos > patternPos) THEN
+          startPos := startPos-patternPos-1;
+          patternPos := 0
+        ELSE
+          (* reached beginning of `stringToSearch' without finding a match *)
+          patternFound := FALSE;
+          EXIT
+        END
+      ELSE  (* characters identic, compare next one *)
+        INC (startPos);
+        INC (patternPos)
+      END
+    END
+  ELSE
+    patternFound := FALSE
+  END
+END StringsFindPrev;
+
+PROCEDURE GetCharClass(ch: CHAR): INTEGER;
+VAR class: INTEGER;
+BEGIN
+  CASE ch OF
+    'a'..'z', 'A'..'Z': class := charAlpha
+  | '0'..'9': class := charDigit
+  | '-', '+': class := charMinusPlus
+  | '"', "'": class := charQuote
+  | '(': class := charOpenBracket
+  ELSE class := charOther END ;
+RETURN class END GetCharClass;
+
+PROCEDURE IsHexDigit(ch: CHAR): BOOLEAN;
+VAR result: BOOLEAN;
+BEGIN
+  CASE ch OF
+    'A'..'F': result := TRUE
+  ELSE result := FALSE END ;
+RETURN result END IsHexDigit;
+
+PROCEDURE IsKeywordInString(s: ARRAY OF CHAR; x, len: INTEGER): BOOLEAN;
+VAR part: ARRAY 32 OF CHAR; result: BOOLEAN;
+  PROCEDURE KW(keyword: ARRAY OF CHAR): BOOLEAN;
+  BEGIN RETURN keyword = part
+  END KW;
+BEGIN
+  IF (s[x] >= 'A') & (s[x] <= 'Z') THEN
+    Strings.Extract(s, x, len, part);
+    result := KW('ABS') OR KW('ASH') OR KW('BOOLEAN') OR KW('CAP') OR
+      KW('CHAR') OR KW('CHR') OR KW('COPY') OR KW('DEC') OR KW('ENTIER') OR
+      KW('EXCL') OR KW('FALSE') OR KW('HALT') OR KW('INC') OR KW('INCL') OR
+      KW('INTEGER') OR KW('LEN') OR KW('LONG') OR KW('LONGINT') OR
+      KW('LONGREAL') OR KW('MAX') OR KW('MIN') OR KW('NEW') OR KW('ODD') OR
+      KW('ORD') OR KW('REAL') OR KW('SET') OR KW('SHORT') OR
+      KW('SHORTINT') OR KW('SIZE') OR KW('TRUE') OR KW('ARRAY') OR
+      KW('BEGIN') OR KW('BY') OR KW('CASE') OR KW('DIV') OR KW('DO') OR
+      KW('ELSIF') OR KW('END') OR KW('EXIT') OR KW('FOR') OR KW('IF') OR
+      KW('IMPORT') OR KW('IN') OR KW('IS') OR KW('LOOP') OR
+      KW('MODULE') OR KW('NIL') OR KW('OR') OR KW('POINTER') OR
+      KW('PROCEDURE') OR KW('RECORD') OR KW('REPEAT') OR KW('RETURN') OR
+      KW('THEN') OR KW('TO') OR KW('TYPE') OR KW('VAR') OR KW('WHILE') OR
+      KW('ELSE') OR KW('OF') OR KW('WITH') OR KW('LONGSET') OR
+      KW('UNTIL') OR KW('CONST') OR KW('MOD') OR KW('FLOOR')
+  ELSE result := FALSE
+  END ;
+RETURN result END IsKeywordInString;
+
+PROCEDURE GetToken(s: ARRAY OF CHAR; x: INTEGER;
+  VAR class, len, comLevel: INTEGER);
+
+VAR i, chClass: INTEGER;
+
+  PROCEDURE TryNumber;
+  VAR ok, hex, hexEnding, point, scale,
+      scaleSign, scaleNum, finish: BOOLEAN;
+  BEGIN
+    IF (x = 0) OR
+       (~(GetCharClass(s[x - 1]) IN {charDigit, charAlpha}) &
+        (s[x - 1] # '.')) THEN
+      ok := TRUE; finish := FALSE; point := FALSE;
+      hex := FALSE; hexEnding := FALSE;
+      scale := FALSE; scaleSign := FALSE; scaleNum := FALSE;
+      REPEAT
+        CASE s[i] OF
+          '0'..'9': IF scale THEN scaleNum := TRUE END
+        | 'D', 'E':
+          IF point THEN
+            IF scale THEN ok := FALSE
+            ELSE scale := TRUE END
+          ELSE hex := TRUE END
+        | 'A', 'B', 'C', 'F':
+          IF point THEN ok := FALSE ELSE hex := TRUE END
+        | 'H', 'X':
+          IF point OR hexEnding THEN ok := FALSE
+          ELSE hexEnding := TRUE END
+        | '.': IF point OR hex THEN ok := FALSE ELSE point := TRUE END
+        | '-', '+':
+          IF hexEnding OR ~scale OR scaleNum THEN finish := TRUE
+          ELSE scaleSign := TRUE END
+        ELSE
+          IF point & scale & ~scaleNum THEN ok := FALSE
+          ELSE finish := TRUE END
+        END;
+        INC(i)
+      UNTIL ~ok OR finish;
+      IF ok & (~hex OR hexEnding) THEN
+        IF GetCharClass(s[i - 1]) # charAlpha THEN
+          len := i - x - 1;
+          class := tokenNumber
+        END
+      END
+    END
+  END TryNumber;
+
+  PROCEDURE TryString;
+  VAR quote: CHAR;
+  BEGIN
+    quote := s[x];
+    WHILE (s[i] # 0X) & (s[i] # quote) DO INC(i) END;
+    IF s[i] # 0X THEN
+      len := i - x + 1;
+      class := tokenString
+    END
+  END TryString;
+
+  PROCEDURE TryComment;
+  BEGIN
+    IF (i > 0) & (s[i] = '*') THEN INC(i); INC(comLevel) END;
+    IF comLevel > 0 THEN
+      REPEAT
+        WHILE (s[i] # 0X) & (s[i] # '*') DO INC(i) END;
+        IF s[i] = '*' THEN
+          IF (i > 0) & (s[i - 1] = '(') THEN INC(comLevel)
+          ELSIF s[i + 1] = ')' THEN DEC(comLevel) END;
+          INC(i)
+        END
+      UNTIL (s[i] = 0X) OR (comLevel <= 0);
+      len := i - x + 1;
+      class := tokenComment
+    END
+  END TryComment;
+
+BEGIN
+  class := tokenOther; len := 0;
+  IF s[x] # 0X THEN
+    IF (x = 0) & (comLevel > 0) THEN i := 0; TryComment END;
+    IF (s[x] # 0X) & (class # tokenComment) THEN
+      i := x + 1;
+      chClass := GetCharClass(s[x]);
+      CASE chClass OF
+        charAlpha:
+        IF (x = 0) OR (GetCharClass(s[x - 1]) # charDigit) THEN
+          WHILE GetCharClass(s[i]) = charAlpha DO INC(i) END;
+          len := i - x;
+          IF IsKeywordInString(s, x, len) THEN class := tokenKeyword END
+        END
+      | charDigit: TryNumber
+      | charQuote: TryString
+      | charOpenBracket: TryComment
+      ELSE len := 1 END
+    END
+  END
+END GetToken;
+
+PROCEDURE GetTokenColor(class: INTEGER): INTEGER;
+VAR color: INTEGER;
+BEGIN
+  CASE class OF
+    tokenKeyword: color := 15
+  | tokenNumber:  color := 3
+  | tokenString:  color := 11
+  | tokenComment: color := 7
+  ELSE color := 14 END ;
+RETURN color END GetTokenColor;
+
+(* Like T.PutString, but highlighted *)
+PROCEDURE PutStringH*(x, y: INTEGER; s: ARRAY OF CHAR;
+  bg, limit: INTEGER; VAR comLevel: INTEGER);
+VAR i, fg, class, len: INTEGER;
+BEGIN
+  IF limit = 0 THEN limit := T.charsX END;
+  GetToken(s, 0, class, len, comLevel);
+  fg := GetTokenColor(class); i := 0;
+  WHILE (s[i] # 0X) & (x < limit) DO
+    T.PutChar(x, y, s[i], fg, bg);
+    INC(i); INC(x); DEC(len);
+    IF len <= 0 THEN
+      GetToken(s, i, class, len, comLevel);
+      fg := GetTokenColor(class)
+    END
+  END;
+  DEC(i);
+  WHILE (i >= 0) & (s[i] = ' ') DO
+    DEC(x);
+    T.PutChar(x, y, 0FAX, 3, bg);
+    DEC(i)
+  END
+END PutStringH;
+
+(* Put selected string (highlighted) *)
+PROCEDURE PutSelStringH*(x, y: INTEGER; s: ARRAY OF CHAR;
+  bg, limit: INTEGER; VAR comLevel: INTEGER;
+  fgSel, bgSel: INTEGER; x0, x1: INTEGER);
+VAR i, fg, class, len: INTEGER; sel: BOOLEAN;
+BEGIN
+  sel := FALSE;
+  GetToken(s, 0, class, len, comLevel);
+  fg := GetTokenColor(class); i := 0;
+  WHILE (s[i] # 0X) & (x < limit) DO
+    IF i = x1 + 1 THEN sel := FALSE
+    ELSIF i = x0 THEN sel := TRUE END;
+    IF sel THEN T.PutChar(x, y, s[i], fgSel, bgSel)
+    ELSE T.PutChar(x, y, s[i], fg, bg) END;
+    INC(i); INC(x); DEC(len);
+    IF len <= 0 THEN
+      GetToken(s, i, class, len, comLevel);
+      fg := GetTokenColor(class)
+    END
+  END
+END PutSelStringH;
+
+PROCEDURE TextChanged*(c: Editor): BOOLEAN;
+BEGIN RETURN c.text.changed
+END TextChanged;
+
+PROCEDURE PrintText*(c: Editor);
+VAR L: Text.Line; scrY: INTEGER;
+  textY, x0, x1, minX, maxX, maxY, comLevel: INTEGER;
+  cursorHandled: BOOLEAN;
+BEGIN T.CharFill(c.x + 1, c.y + 1, c.w - 2, c.h - 2, ' ', 15, c.bg);
+  L := c.text.scrFirst; scrY := c.y + 1; textY := c.text.scrY;
+  minX := c.x + 1; maxX := c.x + c.w - 1;
+  maxY := c.y + c.h - 1; comLevel := L.comLevel;
+  cursorHandled := FALSE;
+  WHILE (L # NIL) & (scrY < maxY) DO
+    IF c.text.selected & (c.text.selT <= textY) & (textY <= c.text.selB) THEN
+      IF textY = c.text.selT THEN x0 := c.text.selL ELSE x0 := 0 END;
+      IF textY = c.text.selB THEN x1 := c.text.selR - 1 ELSE x1 := L.len END;
+      PutSelStringH(minX, scrY, L.s, c.bg, maxX, comLevel, 1, 7, x0, x1)
+    ELSE PutStringH(minX, scrY, L.s, c.bg, maxX, comLevel)
+    END;
+    IF (L = c.text.cur) & (c.text.x + 1 < T.charsX) &
+       ~OV.HasModalWindow(c.app) THEN
+      x0 := minX + c.text.x;
+      IF c.focused & (x0 <= maxX) THEN
+        T.ShowCursor(TRUE); T.GoToXY(x0, scrY);
+        cursorHandled := TRUE
+      END
+    END;
+    L := L.next; INC(scrY); INC(textY);
+    IF L # NIL THEN L.comLevel := comLevel END
+  END;
+  IF c.focused & ~cursorHandled THEN T.ShowCursor(FALSE) END
+END PrintText;
+
+PROCEDURE StartSelection(c: Editor);
+BEGIN c.text.selected := TRUE;
+  c.text.selL := c.text.x; c.text.selR := c.text.x;
+  c.text.selT := c.text.y; c.text.selB := c.text.y
+END StartSelection;
+
+PROCEDURE CheckSelBorders(c: Editor; VAR onLeft, onRight: BOOLEAN);
+BEGIN
+  onLeft := (c.text.x = c.text.selL) & (c.text.y = c.text.selT);
+  onRight := (c.text.x = c.text.selR) & (c.text.y = c.text.selB)
+END CheckSelBorders;
+
+PROCEDURE Swap(VAR a, b: INTEGER);
+VAR tmp: INTEGER;
+BEGIN
+  tmp := a; a := b; b := tmp
+END Swap;
+
+PROCEDURE Order(VAR a, b: INTEGER);
+VAR tmp: INTEGER;
+BEGIN
+  IF a > b THEN tmp := a; a := b; b := tmp END
+END Order;
+
+(* Starts, removes, upgrades selection if Shift held. *)
+(* dir is one of Direction of Selection constants. *)
+PROCEDURE HandleSelection(c: Editor; dir: INTEGER; viaHoriz: BOOLEAN);
+VAR mod: SET; onLeft, onRight: BOOLEAN;
+BEGIN
+  IF ~G.ShiftPressed() THEN c.text.selected := FALSE
+  ELSE CheckSelBorders(c, onLeft, onRight);
+    IF ~c.text.selected OR (~onLeft & ~onRight) THEN
+      StartSelection(c); (* Reset/new selection *)
+      CheckSelBorders(c, onLeft, onRight)
+    END;
+    CASE dir OF
+      dirRight:
+      IF onRight THEN INC(c.text.selR)
+      ELSIF onLeft THEN INC(c.text.selL) END
+    | dirLeft:
+      IF onLeft THEN DEC(c.text.selL)
+      ELSIF onRight THEN DEC(c.text.selR) END
+    | dirUp:
+      IF onLeft THEN
+        DEC(c.text.selT);
+        IF viaHoriz THEN c.text.selL := c.text.cur.prev.len
+        ELSIF c.text.selL > c.text.cur.prev.len
+        THEN c.text.selL := c.text.cur.prev.len END
+      ELSIF viaHoriz THEN
+        DEC(c.text.selB); c.text.selR := c.text.cur.prev.len
+      ELSIF (c.text.selB > c.text.selT + 1) OR
+         ((c.text.selB = c.text.selT + 1) &
+          (c.text.selR >= c.text.selL)) THEN
+        DEC(c.text.selB);
+        IF c.text.selR > c.text.cur.prev.len
+        THEN c.text.selR := c.text.cur.prev.len END
+      ELSIF c.text.selT = c.text.selB THEN
+        DEC(c.text.selT); Order(c.text.selR, c.text.selL);
+        IF c.text.selL > c.text.cur.prev.len
+        THEN c.text.selL := c.text.cur.prev.len END
+      ELSE
+        DEC(c.text.selB); Order(c.text.selL, c.text.selR);
+        IF c.text.selR > c.text.cur.prev.len
+        THEN c.text.selR := c.text.cur.prev.len END
+      END
+    | dirDown:
+      IF onRight THEN
+        INC(c.text.selB);
+        IF viaHoriz THEN c.text.selR := 0
+        ELSIF c.text.selR > c.text.cur.next.len
+        THEN c.text.selR := c.text.cur.next.len END
+      ELSIF viaHoriz THEN
+        INC(c.text.selT); c.text.selL := 0
+      ELSIF (c.text.selB > c.text.selT + 1) OR
+         ((c.text.selB = c.text.selT + 1) &
+          (c.text.selR >= c.text.selL)) THEN
+        INC(c.text.selT);
+        IF c.text.selL > c.text.cur.next.len
+        THEN c.text.selL := c.text.cur.next.len END
+      ELSIF c.text.selT = c.text.selB THEN
+        INC(c.text.selB); Swap(c.text.selL, c.text.selR);
+        IF c.text.selR > c.text.cur.next.len
+        THEN c.text.selR := c.text.cur.next.len END
+      ELSE
+        INC(c.text.selT); Swap(c.text.selL, c.text.selR);
+        IF c.text.selL > c.text.cur.next.len
+        THEN c.text.selL := c.text.cur.next.len END
+      END
+    ELSE END;
+    IF ((c.text.selT >= c.text.selB) & (c.text.selL >= c.text.selR)) OR
+       (c.text.selT > c.text.selB) THEN
+      c.text.selected := FALSE
+    END
+  END
+END HandleSelection;
+
+PROCEDURE MoveScreenByLine(c: Editor; down: BOOLEAN);
+BEGIN
+  IF down THEN
+    ASSERT(c.text.scrFirst.next # NIL, 98);
+    c.text.scrFirst := c.text.scrFirst.next; INC(c.text.scrY)
+  ELSE (* Up *)
+    ASSERT(c.text.scrFirst.prev # NIL, 98); ASSERT(c.text.scrY > 0, 99);
+    c.text.scrFirst := c.text.scrFirst.prev; DEC(c.text.scrY)
+  END;
+  T.ResetCursorBlink
+END MoveScreenByLine;
+
+PROCEDURE MoveScreen(c: Editor; delta: INTEGER);
+BEGIN
+  IF delta > 0 THEN
+    WHILE (delta > 0) & (c.text.scrFirst.next # NIL) DO
+      MoveScreenByLine(c, TRUE); DEC(delta)
+    END
+  ELSE (* Up *)
+    WHILE (delta < 0) & (c.text.scrFirst.prev # NIL) DO
+      MoveScreenByLine(c, FALSE); INC(delta)
+    END
+  END
+END MoveScreen;
+
+(* Moves input cursor up and down *)
+PROCEDURE MoveByLine(c: Editor; down, viaHoriz: BOOLEAN);
+VAR moved: BOOLEAN;
+BEGIN
+  moved := FALSE;
+  IF down THEN
+    IF c.text.cur.next # NIL THEN
+      HandleSelection(c, dirDown, viaHoriz);
+      moved := TRUE;
+      c.text.cur := c.text.cur.next; INC(c.text.y);
+      IF c.text.y >= c.text.scrY + c.h - 2 THEN MoveScreenByLine(c, TRUE) END
+    END
+  ELSE (* Up *)
+    IF c.text.cur.prev # NIL THEN
+      HandleSelection(c, dirUp, viaHoriz);
+      moved := TRUE;
+      IF c.text.scrFirst = c.text.cur THEN MoveScreenByLine(c, FALSE) END;
+      c.text.cur := c.text.cur.prev; DEC(c.text.y)
+    END
+  END;
+  IF moved THEN
+    IF c.text.x > c.text.cur.len THEN c.text.x := c.text.cur.len END;
+    PrintText(c)
+  END;
+  T.ResetCursorBlink
+END MoveByLine;
+
+(* Moves input cursor up and down by page *)
+PROCEDURE MoveByPage(c: Editor; down: BOOLEAN);
+VAR i: INTEGER; moved: BOOLEAN;
+BEGIN i := c.h - 3;
+  IF down THEN
+    moved := c.text.cur.next # NIL;
+    WHILE (i > 0) & (c.text.cur.next # NIL) DO
+      c.text.scrFirst := c.text.scrFirst.next; INC(c.text.scrY);
+      MoveByLine(c, TRUE, FALSE);
+      DEC(i)
+    END
+  ELSE (* Up *)
+    moved := c.text.cur.prev # NIL;
+    WHILE (i > 0) & (c.text.cur.prev # NIL) DO
+      IF c.text.scrY > 0 THEN c.text.scrFirst := c.text.scrFirst.prev; DEC(c.text.scrY) END;
+      MoveByLine(c, FALSE, FALSE);
+      DEC(i)
+    END
+  END;
+  T.ResetCursorBlink;
+  IF moved THEN PrintText(c) END
+END MoveByPage;
+
+(* Moves input cursor left and right by one char *)
+PROCEDURE MoveInLine(c: Editor; right: BOOLEAN);
+BEGIN
+  IF right THEN
+    IF c.text.x < c.text.cur.len THEN
+      HandleSelection(c, dirRight, FALSE); INC(c.text.x)
+    ELSIF c.text.cur.next # NIL THEN
+      MoveByLine(c, TRUE, TRUE);
+      c.text.x := 0;
+    END
+  ELSE (* Left *)
+    IF c.text.x > 0 THEN
+      HandleSelection(c, dirLeft, FALSE); DEC(c.text.x)
+    ELSIF c.text.cur.prev # NIL THEN
+      MoveByLine(c, FALSE, TRUE);
+      c.text.x := c.text.cur.len;
+    END
+  END;
+  T.ResetCursorBlink
+END MoveInLine;
+
+(* Moves input cursor left and right by one word *)
+PROCEDURE MoveByWord(c: Editor; right: BOOLEAN);
+VAR kind: INTEGER;
+BEGIN
+  IF ~right THEN
+    REPEAT MoveInLine(c, FALSE)
+    UNTIL c.text.IsEdge() OR (c.text.CurCharKind() # Text.whitespace)
+  END;
+  kind := c.text.CurCharKind();
+  REPEAT MoveInLine(c, right)
+  UNTIL c.text.IsEdge() OR (c.text.CurCharKind() # kind);
+  IF right THEN
+    WHILE ~c.text.IsEdge() & (c.text.CurCharKind() = Text.whitespace) DO
+      MoveInLine(c, TRUE)
+    END
+  ELSIF ~c.text.IsEdge() THEN MoveInLine(c, TRUE)
+  END
+END MoveByWord;
+
+(* Moves input cursor to start and to end *)
+PROCEDURE MoveToLineEdge(c: Editor; end: BOOLEAN);
+VAR onLeft, onRight: BOOLEAN;
+BEGIN
+  IF G.ShiftPressed() THEN
+    IF ~c.text.selected THEN StartSelection(c)
+    ELSE
+      CheckSelBorders(c, onLeft, onRight);
+      IF ~onLeft & ~onRight THEN StartSelection(c) END
+    END;
+    CheckSelBorders(c, onLeft, onRight);
+    IF end THEN
+      IF onRight THEN c.text.selR := c.text.cur.len
+      ELSE c.text.selL := c.text.cur.len END
+    ELSE (* Home *)
+      IF onRight THEN c.text.selR := 0
+      ELSE c.text.selL := 0 END
+    END;
+    IF c.text.selT = c.text.selB THEN Order(c.text.selL, c.text.selR) END
+  END;
+  IF end THEN
+    IF c.text.x < c.text.cur.len THEN c.text.x := c.text.cur.len END
+  ELSE (* Home *)
+    IF c.text.x > 0 THEN c.text.x := 0 END
+  END;
+  T.ResetCursorBlink;
+  PrintText(c)
+END MoveToLineEdge;
+
+PROCEDURE HandleBackspace(c: Editor);
+BEGIN
+  c.text.HandleBackspace;
+  PrintText(c)
+END HandleBackspace;
+
+PROCEDURE HandleDelete(c: Editor);
+BEGIN
+  c.text.HandleDelete;
+  PrintText(c)
+END HandleDelete;
+
+PROCEDURE HandleTab(e: Editor; shift: BOOLEAN);
+BEGIN
+  IF shift THEN e.text.RemoveIndent
+  ELSIF e.text.WholeLineSelected() THEN
+    IF shift THEN e.text.RemoveIndent
+    ELSE e.text.AddIndent
+    END
+  ELSE
+    IF e.text.selected THEN e.text.DeleteSelection END;
+    e.text.InsertChar(' ');
+    IF ODD(e.text.x MOD 2) THEN e.text.InsertChar(' ') END
+  END;
+  PrintText(e)
+END HandleTab;
+
+PROCEDURE HandleEnter(c: Editor);
+BEGIN
+  c.text.HandleEnter(TRUE);
+  IF c.text.y >= c.text.scrY + c.h - 2 THEN MoveScreenByLine(c, TRUE) END;
+  PrintText(c)
+END HandleEnter;
+
+PROCEDURE InitEditor*(c: Editor);
+BEGIN OV.InitWindow(c); c.closeOnEsc := FALSE;
+  c.fname[0] := 0X; c.bg := 1; c.resizable := TRUE; c.caption := 'NONAME';
+  c.text := Text.NewText(); c.do := editorMethod
+END InitEditor;
+
+PROCEDURE NewEditor*(): Editor;
+VAR w: Editor;
+BEGIN NEW(w); InitEditor(w) ;
+RETURN w END NewEditor;
+
+(* Standard Menu Hanlders *)
+
+PROCEDURE EditCut*(c: OV.Control);
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
+    c.app.windows(Editor).text.CopySelection(clipboard);
+    c.app.windows(Editor).text.DeleteSelection
+  END
+END EditCut;
+
+PROCEDURE EditCopy*(c: OV.Control);
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
+    c.app.windows(Editor).text.CopySelection(clipboard);
+    PrintText(c.app.windows(Editor))
+  END
+END EditCopy;
+
+PROCEDURE EditClear*(c: OV.Control);
+VAR e: OV.Control;
+BEGIN e := c.app.windows;
+  IF (e # NIL) & (e IS Editor) THEN
+    e(Editor).text.DeleteSelection;
+    PrintText(e(Editor))
+  END
+END EditClear;
+
+PROCEDURE EditPaste*(c: OV.Control);
+VAR e: Editor;
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor) &
+      (clipboard[0] # 0X) THEN
+    e := c.app.windows(Editor);
+    e.text.DeleteSelection;
+    e.text.Insert(clipboard, FALSE);
+    IF e.text.y >= e.text.scrY + e.h - 2 THEN
+      MoveScreen(e, e.text.y - e.h + 3 - e.text.scrY)
+    END;
+    PrintText(e)
+  END
+END EditPaste;
+
+PROCEDURE EditSelectAll*(c: OV.Control);
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
+    c.app.windows(Editor).text.SelectAll;
+    PrintText(c.app.windows(Editor))
+  END
+END EditSelectAll;
+
+PROCEDURE EditUnselect*(c: OV.Control);
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor) THEN
+    c.app.windows(Editor).text.selected := FALSE;
+    PrintText(c.app.cur(Editor))
+  END
+END EditUnselect;
+
+(* EditorMethod *)
+
+PROCEDURE EditorDraw*(c: OV.Control; x, y: INTEGER);
+BEGIN OV.WindowDraw(c, x, y);
+  PrintText(c(Editor))
+END EditorDraw;
+
+PROCEDURE EditorMouseDown*(c: OV.Control; x, y, button: INTEGER);
+VAR t: Text.Text; L: Text.Line; i: INTEGER;
+BEGIN OV.WindowMouseDown(c, x, y, button);
+  IF (x > 0) & (x < c.w - 1) & (y > 0) & (y < c.h - 1) THEN
+    DEC(x); DEC(y); t := c(Editor).text;
+    t.selected := FALSE; L := t.scrFirst; i := y;
+    WHILE (i > 0) & (L # NIL) DO DEC(i); L := L.next END;
+    IF L # NIL THEN
+      t.cur := L; t.y := y + t.scrY;
+      IF x > L.len THEN x := L.len END;
+      t.x := x;
+      T.GoToXY(c.x + x + 1, c.y + y + 1)
+    END;
+    PrintText(c(Editor))
+  END
+END EditorMouseDown;
+
+PROCEDURE EditorMouseUp*(c: OV.Control; x, y, button: INTEGER);
+BEGIN
+  OV.WindowMouseUp(c, x, y, button)
+END EditorMouseUp;
+
+PROCEDURE EditorMouseMove*(c: OV.Control; x, y: INTEGER; buttons: SET);
+BEGIN
+  OV.WindowMouseMove(c, x, y, buttons)
+END EditorMouseMove;
+
+PROCEDURE EditorTextInput(c: OV.Control; ch: INTEGER);
+BEGIN
+  IF ch # 0 THEN c(Editor).text.InsertChar(OV.ToCP866(ch)); T.ResetCursorBlink;
+    c(Editor).text.selected := FALSE; PrintText(c(Editor))
+  END
+END EditorTextInput;
+
+PROCEDURE EditorKeyDown*(c: OV.Control; key: G.Key);
+BEGIN OV.WindowKeyDown(c, key);
+  CASE key.code OF
+    G.kLeft:
+    IF key.mod * G.mCtrl # {} THEN MoveByWord(c(Editor), FALSE)
+    ELSE MoveInLine(c(Editor), FALSE)
+    END;
+    PrintText(c(Editor))
+  | G.kRight:
+    IF key.mod * G.mCtrl # {} THEN MoveByWord(c(Editor), TRUE)
+    ELSE MoveInLine(c(Editor), TRUE)
+    END;
+    PrintText(c(Editor))
+  | G.kUp:        MoveByLine(c(Editor), FALSE, FALSE)
+  | G.kDown:      MoveByLine(c(Editor), TRUE, FALSE)
+  | G.kHome:      MoveToLineEdge(c(Editor), FALSE)
+  | G.kEnd:       MoveToLineEdge(c(Editor), TRUE)
+  | G.kPgUp:      MoveByPage(c(Editor), FALSE)
+  | G.kPgDn:      MoveByPage(c(Editor), TRUE)
+  | G.kBackspace: HandleBackspace(c(Editor))
+  | G.kDel:       HandleDelete(c(Editor))
+  | G.kEnter, G.kEnterPad: HandleEnter(c(Editor))
+  | G.kTab:       HandleTab(c(Editor), key.mod * G.mShift # {})
+  ELSE END
+END EditorKeyDown;
+
+PROCEDURE EditorGetFocus*(c: OV.Control);
+BEGIN
+  IF OV.windowMethod.getFocus # NIL THEN OV.windowMethod.getFocus(c) END;
+  G.StartTextInput; T.ShowCursor(TRUE)
+END EditorGetFocus;
+
+PROCEDURE EditorRefresh(c: OV.Control);
+BEGIN OV.WindowRefresh(c)
+END EditorRefresh;
+
+PROCEDURE InitEditorMethod*(m: OV.ControlMethod);
+BEGIN OV.InitWindowMethod(m);
+  m.getFocus := EditorGetFocus;
+  m.refresh := EditorRefresh;
+  m.draw := EditorDraw;
+  m.mouseDown := EditorMouseDown;
+  m.mouseUp := EditorMouseUp;
+  m.mouseMove := EditorMouseMove;
+  m.keyDown := EditorKeyDown;
+  m.textInput := EditorTextInput
+END InitEditorMethod;
+
+BEGIN
+  NEW(editorMethod); InitEditorMethod(editorMethod);
+  NEW(fileDialogMethod); InitFileDialogMethod(fileDialogMethod)
+END Editor.

+ 3 - 3
src/EditorText.Mod

@@ -201,7 +201,7 @@ VAR x0, x, y: INTEGER; L0, L: Line;
 BEGIN
 BEGIN
   IF t.selected THEN
   IF t.selected THEN
     L := t.GetFirstSelLine();
     L := t.GetFirstSelLine();
-    L0 := L; y := t.selT; 
+    L0 := L; y := t.selT;
     WHILE y < t.selB DO L := L.next; INC(y) END;
     WHILE y < t.selB DO L := L.next; INC(y) END;
     L0.next := L.next;
     L0.next := L.next;
     IF L.next = NIL THEN t.last := L0
     IF L.next = NIL THEN t.last := L0
@@ -440,14 +440,14 @@ BEGIN success := FALSE;
       success := TRUE
       success := TRUE
     END
     END
   END ;
   END ;
-RETURN success END LoadFromFile; 
+RETURN success END LoadFromFile;
 
 
 PROCEDURE (t: Text) MoveToLineCol*(line, col, winH: INTEGER), NEW;
 PROCEDURE (t: Text) MoveToLineCol*(line, col, winH: INTEGER), NEW;
 VAR i, centerY, tx, ty: INTEGER; L: Line;
 VAR i, centerY, tx, ty: INTEGER; L: Line;
 BEGIN L := t.first; tx := 0; ty := 0; DEC(line); DEC(col);
 BEGIN L := t.first; tx := 0; ty := 0; DEC(line); DEC(col);
   WHILE (L # NIL) & (ty # line) DO L := L.next; INC(ty) END;
   WHILE (L # NIL) & (ty # line) DO L := L.next; INC(ty) END;
   IF L # NIL THEN
   IF L # NIL THEN
-    WHILE (L.s[tx] # 0X) & (col > 0) DO 
+    WHILE (L.s[tx] # 0X) & (col > 0) DO
       IF L.s[tx] < 80X THEN DEC(col) ELSE DEC(col, 2) END;
       IF L.s[tx] < 80X THEN DEC(col) ELSE DEC(col, 2) END;
       INC(tx)
       INC(tx)
     END;
     END;

+ 1235 - 1235
src/Graph.Mod

@@ -1,1235 +1,1235 @@
-MODULE Graph;
-(* Copyright 2017-2021 Arthur Yefimov
-
-This file is part of Free Oberon.
-
-Free Oberon is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Free Oberon is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
-*)
-IMPORT SDL := SDL2, SYSTEM, Platform, Out;
-
-CONST
-  (* Flip Flags *)
-  flipNone* = {};
-  flipH*    = 0;
-  flipV*    = 1;
-
-  (* Draw Mode Flags *)
-  drawSpriteNormal* = 0;
-  drawSpriteLit*    = 1;
-  drawSpriteTrans*  = 2;
-
-  (* Settings, see global varialbe settings *)
-  fullscreen*  = 0;
-  buffered*    = 1;
-  spread*      = 2;
-  sharpPixels* = 3;
-  software*    = 4;
-  initMouse*   = 8;
-  noPng* = 11;
-  noJpg* = 12;
-
-  (* Event Types *)
-  quit*        = 1;
-  windowEvent* = 2;
-  keyDown*     = 3;
-  keyUp*       = 4;
-  textInput*   = 5;
-  mouseMove*   = 6;
-  mouseDown*   = 7;
-  mouseUp*     = 8;
-  mouseWheel*  = 9;
-
-  (* Mouse Buttons *)
-  btnLeft*  = 0;
-  btnRight* = 1;
-  btnMid*   = 2;
-
-  (* Random Modulo *)
-  randomModulo* = 2147483647; (* =2^31-1 *)
-
-  (* Key Codes *)
-  kA*           = 4;
-  kB*           = 5;
-  kC*           = 6;
-  kD*           = 7;
-  kE*           = 8;
-  kF*           = 9;
-  kG*           = 10;
-  kH*           = 11;
-  kI*           = 12;
-  kJ*           = 13;
-  kK*           = 14;
-  kL*           = 15;
-  kM*           = 16;
-  kN*           = 17;
-  kO*           = 18;
-  kP*           = 19;
-  kQ*           = 20;
-  kR*           = 21;
-  kS*           = 22;
-  kT*           = 23;
-  kU*           = 24;
-  kV*           = 25;
-  kW*           = 26;
-  kX*           = 27;
-  kY*           = 28;
-  kZ*           = 29;
-  k1*           = 30;
-  k2*           = 31;
-  k3*           = 32;
-  k4*           = 33;
-  k5*           = 34;
-  k6*           = 35;
-  k7*           = 36;
-  k8*           = 37;
-  k9*           = 38;
-  k0*           = 39;
-  k1Pad*        = 89;
-  k2Pad*        = 90;
-  k3Pad*        = 91;
-  k4Pad*        = 92;
-  k5Pad*        = 93;
-  k6Pad*        = 94;
-  k7Pad*        = 95;
-  k8Pad*        = 96;
-  k9Pad*        = 97;
-  k0Pad*        = 98;
-  kF1*          = 58;
-  kF2*          = 59;
-  kF3*          = 60;
-  kF4*          = 61;
-  kF5*          = 62;
-  kF6*          = 63;
-  kF7*          = 64;
-  kF8*          = 65;
-  kF9*          = 66;
-  kF10*         = 67;
-  kF11*         = 68;
-  kF12*         = 69;
-  kEsc*         = 41;
-  kTilde*       = 53;
-  kMinus*       = 45;
-  kEquals*      = 46;
-  kBackspace*   = 42;
-  kTab*         = 43;
-  kOpenBrace*   = 47;
-  kCloseBrace*  = 48;
-  kEnter*       = 40;
-  kColon*       = 51;
-  kQuote*       = 52;
-  kBackslash*   = 49;
-  kBackslash2*  = 100;
-  kComma*       = 54;
-  kStop*        = 55;
-  kSlash*       = 56;
-  kSpace*       = 44;
-  kInsert*      = 73;
-  kDel*         = 76;
-  kHome*        = 74;
-  kEnd*         = 77;
-  kPgUp*        = 75;
-  kPgDn*        = 78;
-  kLeft*        = 80;
-  kRight*       = 79;
-  kUp*          = 82;
-  kDown*        = 81;
-  kSlashPad*    = 84;
-  kAsterisk*    = 85;
-  kMinusPad*    = 86;
-  kPlusPad*     = 87;
-  kDelPad*      = 99;
-  kEnterPad*    = 88;
-  kPrtScr*      = 70;
-  kPause*       = 72;
-
-  kModifiers*   = 115;
-
-  kLShift*      = 225;
-  kRShift*      = 229;
-  kLCtrl*       = 224;
-  kRCtrl*       = 228;
-  kAlt*         = 226;
-  kAltGr*       = 230;
-  kLWin*        = 227;
-  kRWin*        = 231;
-  kMenu*        = 123;
-  kScrLock*     = 124;
-  kNumLock*     = 125;
-  kCapsLock*    = 126;
-
-  kMax*         = 127;
-
-  (* Modifiers Set *)
-  mLShift*   = 0;
-  mRShift*   = 1;
-  mLCtrl*    = 6;
-  mRCtrl*    = 7;
-  mLAlt*     = 8;
-  mRAlt*     = 9;
-  mLGui*     = 10;
-  mRGui*     = 11;
-  mNum*      = 12;
-  mCaps*     = 13;
-  mMode*     = 14;
-  mReserved* = 15;
-  mCtrl*  = {mLCtrl, mRCtrl};
-  mShift* = {mLShift, mRShift};
-  mAlt*   = {mLAlt, mRAlt};
-  mGui*   = {mLGui, mRGui};
-
-TYPE
-  ADRINT = SYSTEM.ADRINT;
-  CHAR = SHORTCHAR;
-  SET32 = SET;
-
-  Bitmap* = POINTER TO BitmapDesc;
-  BitmapDesc* = RECORD
-    surface: SDL.Surface;
-    w*, h*: INTEGER
-  END;
-
-  Font* = POINTER TO FontDesc;
-  FontDesc* = RECORD
-    bmp*: Bitmap;
-    charW*, charH*: INTEGER;
-    charRows*, charsInRow*: INTEGER;
-    sprites*: POINTER TO ARRAY OF ARRAY OF SDL.Rect
-  END;
-
-  KeyArray = SDL.KeyArray;
-
-  Key* = RECORD
-    code*: INTEGER; (* Physical key code *)
-    sym*: INTEGER; (* Virtual key code *)
-    mod*: SET; (* Key modifiers *)
-    repeat*: BOOLEAN
-  END;
-
-  Region* = RECORD
-    x*, y*, w*, h*: INTEGER
-  END;
-
-  Event* = RECORD
-    type*: INTEGER;
-    key*: Key;
-    x*, y*: INTEGER;
-    xRel*, yRel*: INTEGER;
-    button*: INTEGER;
-    buttons*: SET; (* What mouse buttons are pressed *)
-    down*: BOOLEAN;
-    s*: ARRAY 32 OF CHAR;
-    ch*: INTEGER(*SHOULD BE 2-byte CHAR*)
-  END;
-
-  EventQueue* = RECORD
-    buf: ARRAY 256 OF Event;
-    first, last: INTEGER; (* Index of first and last element *)
-    len: INTEGER (* Amount of elements currently in queue *)
-  END;
-
-  CloseBtnProc* = PROCEDURE;
-
-VAR
-  window: SDL.Window;
-  renderer: SDL.Renderer;
-  screen: Bitmap;
-  screenTexture: SDL.Texture;
-  events: EventQueue;
-  keyPressed: INTEGER;
-
-  settings, initSettings: SET; (* See constants above *)
-  sizeStepX, sizeStepY: INTEGER;
-  scaleX, scaleY: REAL;
-  scrW, scrH: INTEGER;
-  wantFPS: INTEGER;
-  buffer: Bitmap;
-  lastFlip: INTEGER;
-  frames, framesT: INTEGER;
-
-  (* Flip Region *)
-  flipRegion: Region;
-
-  (* Mouse *)
-  mouseX, mouseY: INTEGER;
-  mouseFocusX, mouseFocusY: INTEGER;
-  lastBlitMouseOutside: BOOLEAN;
-  lastBlitMouseX, lastBlitMouseY: INTEGER;
-  needRedrawMouse: BOOLEAN; (* True if mouse has moved since last redraw *)
-  showMouse: BOOLEAN; (* Whether to show mouse pointer on screen *)
-  stdMousePointer: Bitmap;
-  mousePointer: Bitmap;
-  underMouse: Bitmap; (* Buffer to copy part of image under the mouse *)
-
-  randomSeed-: INTEGER;
-
-PROCEDURE -AAIncludeSDL2h0 '#include "SDL2.h0"';
-
-(* General *)
-
-PROCEDURE GetError*(VAR s: ARRAY OF CHAR);
-TYPE P = POINTER TO ARRAY 10240 OF CHAR;
-VAR p: P;
-BEGIN p := SYSTEM.VAL(P, SDL.GetError()); s := p^$
-END GetError;
-
-PROCEDURE Settings*(w, h: INTEGER; flags: SET);
-BEGIN scrW := w; scrH := h;
-  initSettings := flags;
-  showMouse := initMouse IN flags
-END Settings;
-
-PROCEDURE SetSizeStep*(w, h: INTEGER);
-BEGIN sizeStepX := w; sizeStepY := h
-END SetSizeStep;
-
-PROCEDURE ApplyScale;
-BEGIN
-  SDL.RenderSetLogicalSize(renderer,
-    SHORT(ENTIER(scrW * scaleX)), SHORT(ENTIER(scrH * scaleY)));
-END ApplyScale;
-
-PROCEDURE SetScale*(x, y: REAL);
-BEGIN scaleX := x; scaleY := y;
-  IF renderer # 0 THEN ApplyScale END
-END SetScale;
-
-PROCEDURE SetFPS*(fps: INTEGER);
-BEGIN IF fps <= 0 THEN fps := -1 END;
-  wantFPS := fps
-END SetFPS;
-
-PROCEDURE GetDesktopResolution*(VAR w, h: INTEGER);
-VAR mode: SDL.DisplayMode;
-BEGIN SDL.GetDesktopDisplayMode(0, mode);
-  w := mode.w; h := mode.h
-END GetDesktopResolution;
-
-(* Flip Region *)
-PROCEDURE SetRegion*(x, y, w, h: INTEGER);
-BEGIN
-  flipRegion.x := x; flipRegion.y := y;
-  flipRegion.w := w; flipRegion.h := h
-END SetRegion;
-
-PROCEDURE UnsetRegion*;
-BEGIN flipRegion.w := -1
-END UnsetRegion;
-
-PROCEDURE AddRegion*(x, y, w, h: INTEGER);
-BEGIN
-  IF flipRegion.w = -1 THEN (* No flip region yet *)
-    SetRegion(x, y, w, h) (* Just set it *)
-  ELSE (* Flip Region exists, add to it *)
-    IF x < flipRegion.x THEN flipRegion.x := x END;
-    IF y < flipRegion.y THEN flipRegion.y := y END;
-    IF x + w > flipRegion.x + flipRegion.w THEN
-      flipRegion.w := w + x - flipRegion.x END;
-    IF y + h > flipRegion.y + flipRegion.h THEN
-      flipRegion.h := h + y - flipRegion.y END
-  END
-END AddRegion;
-
-(* Drawing *)
-
-PROCEDURE MakeCol*(r, g, b: INTEGER): INTEGER;
-BEGIN
-  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, r) * {0..7});
-  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, g) * {0..7});
-  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, b) * {0..7}) ;
-RETURN SYSTEM.LSH(SYSTEM.LSH(0FF00H + b, 8) + g, 8) + r END MakeCol;
-
-PROCEDURE ColorToRGB*(color: INTEGER; VAR r, g, b: INTEGER);
-BEGIN
-  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, color) * {0..7});
-  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -8)) * {0..7});
-  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -16)) * {0..7})
-END ColorToRGB;
-
-PROCEDURE BmpCol*(bmp: Bitmap; r, g, b: INTEGER): INTEGER;
-BEGIN RETURN SDL.MapRGB(bmp.surface.format, SHORT(r), SHORT(g), SHORT(b))
-END BmpCol;
-
-PROCEDURE ClearToColor*(bmp: Bitmap; color: INTEGER);
-BEGIN SDL.FillRectNil(bmp.surface, color)
-END ClearToColor;
-
-PROCEDURE ClearBitmap*(bmp: Bitmap);
-BEGIN ClearToColor(bmp, MakeCol(0, 0, 0))
-END ClearBitmap;
-
-PROCEDURE ClearScreenToColor*(color: INTEGER);
-BEGIN ClearToColor(screen, color)
-END ClearScreenToColor;
-
-PROCEDURE ClearScreen*;
-BEGIN ClearToColor(screen, MakeCol(0, 0, 0))
-END ClearScreen;
-
-PROCEDURE LockBitmap*(bmp: Bitmap);
-BEGIN SDL.LockSurface(bmp.surface)
-END LockBitmap;
-
-PROCEDURE UnlockBitmap*(bmp: Bitmap);
-BEGIN SDL.UnlockSurface(bmp.surface)
-END UnlockBitmap;
-
-PROCEDURE PutPixelFast*(bmp: Bitmap; x, y, color: INTEGER);
-VAR n: ADRINT;
-BEGIN n := SYSTEM.VAL(ADRINT, bmp.surface.pixels);
-  INC(n, (y * bmp.w + x) * 4);
-  SYSTEM.PUT(n, color)
-END PutPixelFast;
-
-PROCEDURE PutPixel*(bmp: Bitmap; x, y, color: INTEGER);
-VAR n: ADRINT;
-BEGIN
-  IF (x >= 0) & (x < bmp.w) &
-     (y >= 0) & (y < bmp.h) THEN
-    SDL.LockSurface(bmp.surface);
-    n := SYSTEM.VAL(ADRINT, bmp.surface.pixels);
-    INC(n, (y * bmp.w + x) * 4);
-    SYSTEM.PUT(n, color);
-    SDL.UnlockSurface(bmp.surface)
-  END
-END PutPixel;
-
-PROCEDURE GetPixel*(bmp: Bitmap; x, y: INTEGER): INTEGER;
-VAR color: INTEGER;
-  n: ADRINT;
-BEGIN
-  IF (x >= 0) & (x < bmp.w) &
-     (y >= 0) & (y < bmp.h) THEN
-    SDL.LockSurface(bmp.surface);
-    n := SYSTEM.VAL(ADRINT, bmp.surface.pixels);
-    INC(n, (y * bmp.w + x) * 4);
-    SYSTEM.GET(n, color);
-    SDL.UnlockSurface(bmp.surface)
-  ELSE color := 0
-  END ;
-RETURN color END GetPixel;
-
-PROCEDURE HLine*(bmp: Bitmap; x1, y, x2, color: INTEGER);
-VAR rect: SDL.Rect; t: INTEGER;
-BEGIN
-  IF x1 > x2 THEN t := x1; x1 := x2; x2 := t END;
-  rect.x := x1; rect.y := y;
-  rect.w := x2 - x1 + 1; rect.h := 1;
-  SDL.FillRect(bmp.surface, rect, color)
-END HLine;
-
-PROCEDURE VLine*(bmp: Bitmap; x, y1, y2, color: INTEGER);
-VAR rect: SDL.Rect; t: INTEGER;
-BEGIN
-  IF y1 > y2 THEN t := y1; y1 := y2; y2 := t END;
-  rect.x := x; rect.y := y1;
-  rect.w := 1; rect.h := y2 - y1 + 1;
-  SDL.FillRect(bmp.surface, rect, color)
-END VLine;
-
-PROCEDURE Line*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
-VAR x, y, i, dx, dy, sx, sy, e: INTEGER; vert: BOOLEAN;
-BEGIN
-  IF x1 = x2 THEN VLine(bmp, x1, y1, y2, color)
-  ELSIF y1 = y2 THEN HLine(bmp, x1, y1, x2, color)
-  ELSE
-    SDL.LockSurface(bmp.surface);
-    dx := ABS(x1 - x2); dy := ABS(y1 - y2);
-    IF x2 > x1 THEN sx := 1 ELSE sx := -1 END;
-    IF y2 > y1 THEN sy := 1 ELSE sy := -1 END;
-    x := x1; y := y1; vert := dy > dx;
-    IF vert THEN i := dx; dx := dy; dy := i END;
-    e := 2 * dy - dx;
-    FOR i := 0 TO dx DO
-      IF (x >= 0) & (x < bmp.w) &
-         (y >= 0) & (y < bmp.h) THEN
-        PutPixelFast(bmp, x, y, color)
-      END;
-      IF e >= 0 THEN
-        IF vert THEN INC(x, sx) ELSE INC(y, sy) END;
-        DEC(e, 2 * dx)
-      END;
-      IF vert THEN INC(y, sy) ELSE INC(x, sx) END;
-      INC(e, 2 * dy)
-    END;
-    SDL.UnlockSurface(bmp.surface)
-  END
-END Line;
-
-PROCEDURE FastLine*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
-BEGIN
-  (*Al.FastLine(bmp.bmp, x1, y1, x2, y2, color)*)
-END FastLine;
-
-PROCEDURE Rect*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER); (*!FIXME*)
-VAR rect: SDL.Rect;
-BEGIN
-  rect.x := x1; rect.y := y1;
-  rect.w := 1; rect.h := y2 - y1 + 1;
-  SDL.FillRect(bmp.surface, rect, color);
-  rect.x := x2;
-  SDL.FillRect(bmp.surface, rect, color);
-  rect.x := x1; rect.w := x2 - x1 + 1; rect.h := 1;
-  SDL.FillRect(bmp.surface, rect, color);
-  rect.y := y2;
-  SDL.FillRect(bmp.surface, rect, color)
-END Rect;
-
-PROCEDURE RectFill*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
-VAR rect: SDL.Rect;
-BEGIN
-  rect.x := x1; rect.y := y1;
-  rect.w := x2 - x1 + 1; rect.h := y2 - y1 + 1;
-  SDL.FillRect(bmp.surface, rect, color)
-END RectFill;
-
-PROCEDURE Circle*(b: Bitmap; cx, cy, r, col: INTEGER);
-VAR x, y, d: INTEGER;
-BEGIN x := 0; y := r; d := 3 - 2 * r;
-  WHILE x <= y DO
-    PutPixel(b, cx + x, cy + y, col);
-    PutPixel(b, cx + y, cy + x, col);
-    PutPixel(b, cx + x, cy - y, col);
-    PutPixel(b, cx + y, cy - x, col);
-    PutPixel(b, cx - x, cy + y, col);
-    PutPixel(b, cx - y, cy + x, col);
-    PutPixel(b, cx - x, cy - y, col);
-    PutPixel(b, cx - y, cy - x, col);
-    IF d < 0 THEN d := d + 4 * x + 6
-    ELSE d := d + 4 * (x - y) + 10; DEC(y)
-    END;
-    INC(x)
-  END
-END Circle;
-
-PROCEDURE CircleFill*(b: Bitmap; cx, cy, r, col: INTEGER);
-VAR x, y, d: INTEGER;
-BEGIN x := 0; y := r; d := 3 - 2 * r;
-  WHILE x <= y DO
-    HLine(b, cx - x, cy + y, cx + x, col);
-    HLine(b, cx - y, cy + x, cx + y, col);
-    HLine(b, cx - x, cy - y, cx + x, col);
-    HLine(b, cx - y, cy - x, cx + y, col);
-    IF d < 0 THEN d := d + 4 * x + 6
-    ELSE d := d + 4 * (x - y) + 10; DEC(y)
-    END;
-    INC(x)
-  END
-END CircleFill;
-
-PROCEDURE Ellipse*(bmp: Bitmap; x, y, rx, ry, color: INTEGER);
-BEGIN
-END Ellipse;
-
-PROCEDURE EllipseFill*(bmp: Bitmap; x, y, rx, ry, color: INTEGER);
-BEGIN
-END EllipseFill;
-
-PROCEDURE FloodFill*(bmp: Bitmap; x, y, color: INTEGER);
-BEGIN
-END FloodFill;
-
-(* Bitmap *)
-
-PROCEDURE (bmp: Bitmap) Finalize*, NEW;
-BEGIN
-END Finalize;
-
-PROCEDURE CreateBitmap*(w, h: INTEGER): Bitmap;
-VAR bmp: Bitmap;
-  s: ARRAY 2560 OF CHAR;
-BEGIN NEW(bmp);
-  bmp.surface := SDL.CreateRGBSurface(0, w, h, 32,
-    000000FFH, 0000FF00H, 00FF0000H, -1000000H);
-  IF bmp.surface = NIL THEN
-    GetError(s); Out.String(s); Out.Ln
-  END;
-  bmp.w := w; bmp.h := h ;
-RETURN bmp END CreateBitmap;
-
-PROCEDURE DestroyBitmap*(bmp: Bitmap);
-BEGIN SDL.FreeSurface(bmp.surface)
-END DestroyBitmap;
-
-PROCEDURE LoadBitmap*(filename: ARRAY OF CHAR): Bitmap;
-VAR bmp: Bitmap;
-BEGIN NEW(bmp); bmp.surface := SDL.ImgLoad(filename);
-  IF bmp.surface = NIL THEN bmp := NIL
-  ELSE bmp.w := bmp.surface.w; bmp.h := bmp.surface.h END ;
-RETURN bmp END LoadBitmap;
-
-PROCEDURE SaveBmp*(bmp: Bitmap; filename: ARRAY OF CHAR): BOOLEAN;
-BEGIN
-  RETURN SDL.SaveBmpRW(bmp.surface, SDL.RWFromFile(filename, 'wb'), 1) = 0
-END SaveBmp;
-
-PROCEDURE SavePng*(bmp: Bitmap; filename: ARRAY OF CHAR): BOOLEAN;
-BEGIN
-RETURN SDL.ImgSavePng(bmp.surface, filename) = 0 END SavePng;
-
-PROCEDURE SaveJpg*(bmp: Bitmap; filename: ARRAY OF CHAR): BOOLEAN;
-BEGIN
-RETURN SDL.ImgSaveJpg(bmp.surface, filename) = 0 END SaveJpg;
-
-PROCEDURE Blit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy: INTEGER);
-VAR a, b: SDL.Rect;
-BEGIN a.x := sx; a.y := sy; a.w := sw; a.h := sh;
-  b.x := dx; b.y := dy;
-  SDL.BlitSurface(src.surface, a, dest.surface, b)
-END Blit;
-
-PROCEDURE BlitWhole*(src, dest: Bitmap; x, y: INTEGER);
-VAR b: SDL.Rect;
-BEGIN b.x := x; b.y := y;
-  SDL.BlitSurfaceNil(src.surface, dest.surface, b)
-END BlitWhole;
-
-PROCEDURE StretchBlit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy, dw, dh: INTEGER);
-VAR a, b: SDL.Rect;
-BEGIN
-  a.x := sx; a.y := sy; a.w := sw; a.h := sh;
-  b.x := dx; b.y := dy; b.w := dw; b.h := dh;
-  SDL.BlitScaled(src.surface, a, dest.surface, b)
-END StretchBlit;
-
-PROCEDURE SetAlpha*(bmp: Bitmap; alpha: INTEGER);
-BEGIN
-  IF SDL.SetSurfaceAlphaMod(bmp.surface, CHR(alpha)) = 0 THEN END
-END SetAlpha;
-
-PROCEDURE MaskedBlit*(src, dest: Bitmap; sx, sy, dx, dy, w, h: INTEGER);
-BEGIN
-  (*Al.MaskedBlit(src.bmp, dest.bmp, sx, sy, dx, dy, w, h)*)
-END MaskedBlit;
-
-PROCEDURE DrawSpriteEx*(dest, sprite: Bitmap; x, y, mode: INTEGER; flip: SET);
-BEGIN
-  (*Al.DrawSpriteEx(dest.bmp, sprite.bmp, x, y, mode, flip)*)
-END DrawSpriteEx;
-
-PROCEDURE DrawCharacterEx*(dest, sprite: Bitmap; x, y, color, bg: INTEGER);
-BEGIN
-  (*Al.DrawCharacterEx(dest.bmp, sprite.bmp, x, y, color, bg)*)
-END DrawCharacterEx;
-
-PROCEDURE SetColorKey*(bmp: Bitmap; color: INTEGER);
-BEGIN SDL.SetColorKey(bmp.surface, 1, color)
-END SetColorKey;
-
-(* Font *)
-
-PROCEDURE LoadFont*(filename: ARRAY OF CHAR; charW, charH: INTEGER): Font;
-VAR bmp: Bitmap; font: Font;
-    x, y, sx, sy, tmp: INTEGER;
-BEGIN bmp := LoadBitmap(filename);
-  IF bmp = NIL THEN font := NIL
-  ELSE
-    bmp.surface := SDL.ConvertSurface(bmp.surface,
-      screen.surface.format, 0);
-    SetColorKey(bmp, BmpCol(bmp, 0, 0, 0));
-    NEW(font); font.bmp := bmp;
-    font.charW := charW; font.charH := charH;
-    font.charsInRow := font.bmp.w DIV charW;
-    font.charRows := font.bmp.h DIV charH;
-    (*!FIXME remove sprites from here at all*)
-    NEW(font.sprites, font.charRows, font.charsInRow);
-    sy := 0;
-    FOR y := 0 TO font.charRows - 1 DO
-      sx := 0;
-      FOR x := 0 TO font.charsInRow - 1 DO
-        font.sprites[y, x].x := sx;
-        font.sprites[y, x].y := sy;
-        font.sprites[y, x].w := charW;
-        font.sprites[y, x].h := charH;
-        INC(sx, charW)
-      END;
-      INC(sy, charH)
-    END
-  END ;
-RETURN font END LoadFont;
-
-PROCEDURE DrawCharacter*(dest: Bitmap; font: Font;
-  x, y: INTEGER; ch: CHAR; fg: INTEGER);
-VAR fx, fy, r, g, b: INTEGER; dstRect: SDL.Rect;
-BEGIN dstRect.x := x; dstRect.y := y;
-  fx := ORD(ch) MOD font.charsInRow;
-  fy := ORD(ch) DIV font.charsInRow;
-  ColorToRGB(fg, r, g, b);
-  SDL.SetSurfaceColorMod(font.bmp.surface, r, g, b);
-  SDL.BlitSurface(font.bmp.surface, font.sprites[fy, fx],
-    screen.surface, dstRect)
-END DrawCharacter;
-
-PROCEDURE DrawString*(dest: Bitmap; font: Font;
-  x, y: INTEGER; s: ARRAY OF CHAR; fg: INTEGER);
-VAR i, cx: INTEGER;
-BEGIN i := 0; cx := x;
-  WHILE (s[i] # 0X) & (cx < dest.w) DO
-    DrawCharacter(dest, font, cx, y, s[i], fg);
-    INC(i); INC(cx, font.charW)
-  END
-END DrawString;
-
-PROCEDURE StartTextInput*;
-BEGIN SDL.StartTextInput
-END StartTextInput;
-
-PROCEDURE StopTextInput*;
-BEGIN SDL.StopTextInput
-END StopTextInput;
-
-PROCEDURE QueueEvent;
-BEGIN INC(events.len); INC(events.last);
-  IF events.last = LEN(events.buf) THEN events.last := 0 END
-END QueueEvent;
-
-PROCEDURE PumpKeyDown(VAR event: SDL.Event);
-VAR e: SDL.KeyboardEvent;
-  n: INTEGER; mod: SET;
-BEGIN
-  IF events.len < LEN(events.buf) THEN
-    e := SYSTEM.VAL(SDL.KeyboardEvent, SYSTEM.ADR(event));
-    n := e.keysym.mod; mod := SYSTEM.VAL(SET32, n);
-    QueueEvent;
-    events.buf[events.last].type := keyDown;
-    events.buf[events.last].key.code := e.keysym.scancode;
-    events.buf[events.last].key.sym := e.keysym.sym;
-    events.buf[events.last].key.mod := mod;
-    events.buf[events.last].key.repeat := e.repeat # 0;
-    INC(keyPressed)
-  END
-END PumpKeyDown;
-
-PROCEDURE DecodeChar(IN s: ARRAY OF CHAR): INTEGER;
-VAR i, x, c: INTEGER;
-BEGIN c := ORD(s[0]);
-  IF c > 80H THEN x := ORD(s[1]) MOD 64; (* Not 1 byte *)
-    IF c DIV 32 = 6 THEN (* 2 bytes *)
-      c := c MOD 32 * 64 + x
-    ELSIF c DIV 16 = 14 THEN (* 3 bytes *)
-      c := (c MOD 16 * 64 + x) * 64 + ORD(s[2]) MOD 64
-    ELSIF c DIV 8 = 30 THEN (* 4 bytes *)
-      c := ((c MOD 8 * 64 + x) * 64 + ORD(s[2]) MOD 64) * 64 + ORD(s[3]) MOD 64
-    ELSE c := 0
-    END
-  END ;
-RETURN c END DecodeChar;
-
-PROCEDURE PumpTextEvent(event: SDL.Event);
-VAR sym: INTEGER;
-  e: SDL.TextInputEvent;
-BEGIN
-  IF events.len < LEN(events.buf) THEN
-    e := SYSTEM.VAL(SDL.TextInputEvent, SYSTEM.ADR(event));
-    IF e.text[1] = 0X THEN (* ASCII character *)
-      sym := ORD(e.text[0])
-    ELSE (* Unicode character. Assuming 2 bytes *)
-      sym := ORD(e.text[1]);
-      (* UTF-8 cyrillic *)
-      IF (e.text[0] = 0D0X) OR (e.text[0] = 0D1X) THEN
-        IF e.text[0] = 0D0X THEN DEC(sym, 090H)
-        ELSE DEC(sym, 060H - 16)
-        END;
-        (* Convert to CP866 *)
-        IF sym = 65 THEN sym := 0F1H (* jo *)
-        ELSIF sym = -15 THEN sym := 0F0H (* JO *)
-        ELSIF sym < 48 THEN INC(sym, 80H) (* A..JA, a..p *)
-        ELSE INC(sym, 0E0H - 48) (* r..ja *)
-        END
-      END
-    END;
-    QueueEvent;
-    events.buf[events.last].type := textInput;
-    events.buf[events.last].s := e.text$;
-    events.buf[events.last].ch := DecodeChar(e.text)
-  END
-END PumpTextEvent;
-
-PROCEDURE UpdateMousePos(event: SDL.Event);
-VAR e: SDL.MouseMotionEvent; newX, newY: INTEGER;
-BEGIN
-  e := SYSTEM.VAL(SDL.MouseMotionEvent, SYSTEM.ADR(event));
-  newX := e.x; newY := e.y;
-  IF newX < 0 THEN newX := 0
-  ELSIF newX >= screen.w THEN newX := screen.w - 1 END;
-  IF newY < 0 THEN newY := 0
-  ELSIF newY >= screen.h THEN newY := screen.h - 1 END;
-  IF (newX # mouseX) OR (newY # mouseY) THEN
-    mouseX := newX;  mouseY := newY;
-    needRedrawMouse := TRUE
-  END
-END UpdateMousePos;
-
-(* Keyboard *)
-
-PROCEDURE GetKeyArray(): KeyArray;
-BEGIN
-RETURN SYSTEM.VAL(KeyArray, SDL.GetKeyboardStateNil()) END GetKeyArray;
-
-PROCEDURE KeyDown*(key: INTEGER): BOOLEAN;
-VAR keys: KeyArray;
-BEGIN keys := GetKeyArray() ;
-RETURN keys[key] END KeyDown;
-
-PROCEDURE AltPressed*(): BOOLEAN;
-VAR keys: KeyArray;
-BEGIN keys := GetKeyArray() ;
-RETURN keys[kAlt] OR keys[kAltGr] END AltPressed;
-
-PROCEDURE ShiftPressed*(): BOOLEAN;
-VAR keys: KeyArray;
-BEGIN keys := GetKeyArray() ;
-RETURN keys[kLShift] OR keys[kRShift] END ShiftPressed;
-
-PROCEDURE CtrlPressed*(): BOOLEAN;
-VAR keys: KeyArray;
-BEGIN keys := GetKeyArray() ;
-RETURN keys[kLCtrl] OR keys[kRCtrl] END CtrlPressed;
-
-(* Mouse *)
-
-PROCEDURE MouseOnScreen*(): BOOLEAN;
-VAR flags: SET;
-BEGIN flags := SDL.GetWindowFlags(window);
-RETURN SDL.windowMouseFocus IN flags END MouseOnScreen;
-
-PROCEDURE ShowMouse*(show: BOOLEAN);
-BEGIN showMouse := show
-END ShowMouse;
-
-PROCEDURE GetRealMousePos*(VAR x, y: INTEGER);
-BEGIN IF SDL.GetMouseState(x, y) = 0 THEN END
-END GetRealMousePos;
-
-PROCEDURE GetMousePos*(VAR x, y: INTEGER);
-BEGIN x := mouseX; y := mouseY
-END GetMousePos;
-
-PROCEDURE GetMouseButtons*(): SET;
-VAR x, y: INTEGER;
-BEGIN
-RETURN SYSTEM.VAL(SET32, SDL.GetMouseState(x, y)) END GetMouseButtons;
-
-PROCEDURE CreateStdMousePointer*;
-VAR b: Bitmap; fg, bg: INTEGER;
-BEGIN b := CreateBitmap(12, 19);
-  bg := MakeCol(255, 0, 255); fg := MakeCol(0, 0, 0);
-  ClearToColor(b, bg); SetColorKey(b, bg);
-  Line(b, 0, 0, 10, 10, fg); Line(b, 0, 0, 0, 14, fg);
-  Line(b, 0, 14, 3, 11, fg); Line(b, 10, 10, 6, 10, fg);
-  Line(b, 4, 12, 6, 17, fg); Line(b, 6, 11, 9, 17, fg);
-  Line(b, 7, 18, 8, 18, fg); bg := MakeCol(255, 255, 255);
-  VLine(b, 1, 2, 12, bg); VLine(b, 2, 3, 11, bg);
-  VLine(b, 3, 4, 10, bg); VLine(b, 4, 5, 11, bg);
-  VLine(b, 5, 6, 13, bg); VLine(b, 6, 7, 9, bg);
-  VLine(b, 7, 8, 9, bg); VLine(b, 8, 9, 9, bg);
-  VLine(b, 6, 12, 15, bg); VLine(b, 7, 14, 17, bg);
-  VLine(b, 8, 16, 17, bg);
-  stdMousePointer := b
-END CreateStdMousePointer;
-
-PROCEDURE SetMouseFocus*(x, y: INTEGER);
-BEGIN
-  mouseFocusX := x; mouseFocusY := y;
-  needRedrawMouse := TRUE
-END SetMouseFocus;
-
-PROCEDURE SetMousePointer*(bmp: Bitmap; x, y: INTEGER);
-BEGIN
-  IF bmp = NIL THEN
-    mousePointer := stdMousePointer;
-    x := 0; y := 0
-  ELSE mousePointer := bmp
-  END;
-  SetMouseFocus(x, y);
-  underMouse := CreateBitmap(mousePointer.w, mousePointer.h);
-  needRedrawMouse := TRUE
-END SetMousePointer;
-
-PROCEDURE GetMousePointer*(): Bitmap;
-BEGIN
-RETURN mousePointer END GetMousePointer;
-
-PROCEDURE SetStdMousePointer*;
-BEGIN SetMousePointer(stdMousePointer, 0, 0)
-END SetStdMousePointer;
-
-PROCEDURE InitMouseData;
-BEGIN CreateStdMousePointer; SetStdMousePointer
-END InitMouseData;
-
-(* Misc *)
-PROCEDURE SetWindowTitle*(title: ARRAY OF CHAR);
-BEGIN SDL.SetWindowTitle(window, title)
-END SetWindowTitle;
-
-PROCEDURE SwitchToWindowed*;
-BEGIN
-  IF fullscreen IN settings THEN
-    SDL.SetWindowSize(window, screen.w, screen.h);
-    IF SDL.SetWindowFullscreen(window, {}) = 0 THEN
-      EXCL(settings, fullscreen)
-    END
-  END
-END SwitchToWindowed;
-
-PROCEDURE SwitchToFullscreen*;
-BEGIN
-  IF ~(fullscreen IN settings) THEN
-    IF SDL.SetWindowFullscreen(window, SDL.windowFullscreenDesktop) = 0 THEN
-      INCL(settings, fullscreen)
-    END
-  END
-END SwitchToFullscreen;
-
-PROCEDURE ToggleFullscreen*;
-BEGIN
-  IF fullscreen IN settings THEN SwitchToWindowed
-  ELSE SwitchToFullscreen
-  END
-END ToggleFullscreen;
-
-PROCEDURE Delay*(n: INTEGER);
-BEGIN SDL.Delay(n)
-END Delay;
-
-PROCEDURE HandleMouseButton(VAR event: SDL.Event);
-VAR e: SDL.MouseButtonEvent;
-BEGIN
-END HandleMouseButton;
-
-PROCEDURE PumpQuit;
-BEGIN
-  IF events.len < LEN(events.buf) THEN
-    QueueEvent;
-    events.buf[events.last].type := quit
-  END
-END PumpQuit;
-
-PROCEDURE PumpMouseMove(VAR event: SDL.Event);
-VAR e: SDL.MouseMotionEvent;
-  newX, newY: INTEGER;
-BEGIN
-  e := SYSTEM.VAL(SDL.MouseMotionEvent, SYSTEM.ADR(event));
-  newX := e.x; newY := e.y;
-  IF newX < 0 THEN newX := 0
-  ELSIF newX >= screen.w THEN newX := screen.w - 1
-  END;
-  IF newY < 0 THEN newY := 0
-  ELSIF newY >= screen.h THEN newY := screen.h - 1
-  END;
-  IF (newX # mouseX) OR (newY # mouseY) THEN
-    mouseX := newX; mouseY := newY;
-    needRedrawMouse := TRUE;
-    IF events.len < LEN(events.buf) THEN
-      QueueEvent;
-      events.buf[events.last].type := mouseMove;
-      events.buf[events.last].x := SHORT(ENTIER(newX / scaleX));
-      events.buf[events.last].y := SHORT(ENTIER(newY / scaleY));
-      events.buf[events.last].xRel := e.xRel;
-      events.buf[events.last].yRel := e.yRel;
-      events.buf[events.last].buttons := SYSTEM.VAL(SET32, e.state)
-    END
-  END
-END PumpMouseMove;
-
-PROCEDURE PumpMouseButton(VAR event: SDL.Event; type: INTEGER);
-VAR e: SDL.MouseButtonEvent;
-BEGIN
-  e := SYSTEM.VAL(SDL.MouseButtonEvent, SYSTEM.ADR(event));
-  IF events.len < LEN(events.buf) THEN
-    QueueEvent;
-    events.buf[events.last].type := type;
-    events.buf[events.last].button := e.button - 1;
-    events.buf[events.last].down := e.state # 0;
-    IF e.x < 0 THEN e.x := 0
-    ELSIF e.x >= screen.w THEN e.x := screen.w - 1
-    END;
-    IF e.y < 0 THEN e.y := 0
-    ELSIF e.y >= screen.h THEN e.y := screen.h - 1
-    END;
-    events.buf[events.last].x := SHORT(ENTIER(e.x / scaleX));
-    events.buf[events.last].y := SHORT(ENTIER(e.y / scaleY))
-  END
-END PumpMouseButton;
-
-PROCEDURE RepeatFlip*;
-BEGIN
-  IF screenTexture # 0 THEN
-    SDL.SetRenderDrawColor(renderer, 0, 0, 0, 255);
-    SDL.RenderClear(renderer);
-    SDL.RenderCopyNil(renderer, screenTexture);
-    SDL.RenderPresent(renderer)
-  END
-END RepeatFlip;
-
-PROCEDURE WaitEvents*(timeout: INTEGER);
-VAR event: SDL.Event; n: INTEGER;
-BEGIN
-  n := SDL.PollEvent(event);
-  IF (n # 0) OR (events.len = 0) THEN
-    IF n = 0 THEN
-      IF timeout > 0 THEN n := SDL.WaitEventTimeout(event, timeout)
-      ELSIF timeout < 0 THEN n := SDL.WaitEvent(event)
-      END
-    END;
-    IF n # 0 THEN
-      REPEAT
-        IF event.type = SDL.mouseMotion THEN
-          PumpMouseMove(event)
-        ELSIF event.type = SDL.mouseButtonDown THEN
-          PumpMouseButton(event, mouseDown)
-        ELSIF event.type = SDL.mouseButtonUp THEN
-          PumpMouseButton(event, mouseUp)
-        ELSIF event.type = SDL.keyDown THEN
-          PumpKeyDown(event)
-        ELSIF event.type = SDL.textInput THEN
-          PumpTextEvent(event)
-        ELSIF event.type = SDL.quit THEN
-          PumpQuit
-        END
-      UNTIL SDL.PollEvent(event) = 0
-    END
-  END
-END WaitEvents;
-
-PROCEDURE PollEvent*(VAR event: Event): BOOLEAN;
-VAR hasEvent: BOOLEAN;
-BEGIN
-  IF events.len > 0 THEN
-    event := events.buf[events.first];
-    IF event.type = keyDown THEN DEC(keyPressed) END;
-    DEC(events.len); INC(events.first);
-    IF events.first = LEN(events.buf) THEN events.first := 0 END;
-    hasEvent := TRUE
-  ELSE hasEvent := FALSE
-  END ;
-RETURN hasEvent END PollEvent;
-
-PROCEDURE KeyPressed*(): BOOLEAN;
-BEGIN WaitEvents(0) ;
-RETURN keyPressed > 0 END KeyPressed;
-
-PROCEDURE ReadKey*(): CHAR;
-VAR event: Event; done: BOOLEAN; ch: CHAR;
-BEGIN done := FALSE;
-  REPEAT
-    WaitEvents(-1);
-    WHILE PollEvent(event) DO
-      CASE event.type OF
-        keyDown: ch := CHR(event.key.sym); done := TRUE
-      | quit: ch := 0X; done := TRUE
-      ELSE
-      END
-    END
-  UNTIL done ;
-RETURN ch END ReadKey;
-
-PROCEDURE Pause*;
-BEGIN IF ReadKey() = 0X THEN END
-END Pause;
-
-PROCEDURE WindowShown*(): BOOLEAN;
-VAR flags: SET;
-BEGIN flags := SDL.GetWindowFlags(window) ;
-RETURN SDL.windowShown IN flags END WindowShown;
-
-PROCEDURE GetTicks*(): INTEGER;
-BEGIN
-RETURN SDL.GetTicks() END GetTicks;
-
-PROCEDURE Flip*;
-VAR mx, my: INTEGER; (* Mouse bitmap X Y *)
-    blitMouse: BOOLEAN;
-    dt: INTEGER; (* Delta time *)
-
-  PROCEDURE PrepareMouse;
-  BEGIN
-    mx := mouseX - mouseFocusX;
-    my := mouseY - mouseFocusY;
-    (* Save image under mouse from buffer *)
-    Blit(screen, underMouse, mx, my,
-      underMouse.w, underMouse.h, 0, 0);
-    (* Blit mouse pointer onto buffer *)
-    Blit(mousePointer, screen, 0, 0,
-      mousePointer.w, mousePointer.h, mx, my)
-  END PrepareMouse;
-
-  PROCEDURE CleanMouse;
-  BEGIN (* Restore image under mouse in buffer *)
-    Blit(underMouse, screen, 0, 0,
-      underMouse.w, underMouse.h, mx, my);
-    needRedrawMouse := FALSE
-  END CleanMouse;
-
-BEGIN
-  IF wantFPS # -1 THEN
-    IF lastFlip # -1 THEN
-      dt := 1000 DIV wantFPS - (GetTicks() - lastFlip);
-      IF (dt > 0) & (dt < 1000) THEN Delay(dt) END
-    END;
-    lastFlip := GetTicks()
-  END;
-  IF WindowShown() THEN
-    mx := 0; my := 0;
-    blitMouse := showMouse & MouseOnScreen();
-    IF blitMouse THEN PrepareMouse END;
-
-    (* Blit buffer on screen *)
-    SDL.SetRenderDrawColor(renderer, 0, 0, 0, 255);
-    SDL.RenderClear(renderer);
-    IF screenTexture # 0 THEN
-      SDL.DestroyTexture(screenTexture);
-      screenTexture := 0
-    END;
-    screenTexture := SDL.CreateTextureFromSurface(renderer, screen.surface);
-    SDL.RenderCopyNil(renderer, screenTexture);
-    SDL.RenderPresent(renderer);
-
-    IF blitMouse THEN CleanMouse END
-  END
-END Flip;
-
-(* Random *)
-
-PROCEDURE Time(): INTEGER;
-BEGIN
-RETURN SHORT(Platform.Time()) END Time;
-
-(* Set random seed value. Any values are allowed, although
-   values not in [1..2^31-2] will be mapped into this range. *)
-PROCEDURE PutSeed*(newSeed: INTEGER);
-BEGIN newSeed := newSeed MOD randomModulo;
-  IF newSeed = 0 THEN randomSeed := 1
-  ELSE randomSeed := newSeed
-  END
-END PutSeed;
-
-PROCEDURE NextRND;
-CONST a = 16807;
-  q = 127773; (* m div a *)
-  r = 2836;   (* m mod a *)
-VAR lo, hi, test: INTEGER;
-BEGIN
-  hi := randomSeed DIV q;
-  lo := randomSeed MOD q;
-  test := a * lo - r * hi;
-  IF test > 0 THEN randomSeed := test
-  ELSE randomSeed := test + randomModulo
-  END
-END NextRND;
-
-(* Calculates a new number. range has to be included in
-   [1..2^31-2]. Result is a number from 0, 1, ... , range-1. *)
-PROCEDURE Random*(range: INTEGER): INTEGER;
-BEGIN NextRND ;
-RETURN randomSeed MOD range END Random;
-
-(* Calculates a number x with 0.0 <= x < 1.0. *)
-PROCEDURE Uniform*(): REAL;
-BEGIN NextRND ;
-RETURN (randomSeed - 1) * (1 / (randomModulo - 1)) END Uniform;
-
-PROCEDURE Randomize*;
-BEGIN PutSeed(Time())
-END Randomize;
-
-(* Init *)
-
-PROCEDURE Init*(): Bitmap;
-VAR flags: SET; success: BOOLEAN; w, h, nw, nh: INTEGER;
-    s: ARRAY 2000 OF CHAR;
-BEGIN screen := NIL; settings := initSettings;
-  IF SDL.Init({SDL.initVideo}) = 0 THEN
-    flags := {};
-    IF fullscreen IN settings THEN
-      flags := flags + SDL.windowFullscreenDesktop;
-      IF (scrW <= 0) OR (scrH <= 0) THEN
-        GetDesktopResolution(scrW, scrH);
-        scrW := SHORT(ENTIER(scrW / scaleX));
-        scrH := SHORT(ENTIER(scrH / scaleY))
-      ELSIF spread IN settings THEN
-        GetDesktopResolution(w, h);
-        w := SHORT(ENTIER(w / scaleX)); h := SHORT(ENTIER(h / scaleY));
-        IF sharpPixels IN settings THEN
-          nw := w DIV scrW; nh := h DIV scrH;
-          IF nw < nh THEN scrW := w DIV nw; scrH := h DIV nw
-          ELSE scrW := w DIV nh; scrH := h DIV nh
-          END
-        END;
-        IF w / h > scrW / scrH THEN scrW := w * scrH DIV h
-        ELSE scrH := h * scrW DIV w
-        END
-      END
-    ELSIF (scrW <= 0) OR (scrH <= 0) THEN scrW := 640; scrH := 400
-    END;
-    IF sizeStepX # 1 THEN scrW := scrW DIV sizeStepX * sizeStepX END;
-    IF sizeStepY # 1 THEN scrH := scrH DIV sizeStepY * sizeStepY END;
-    window := SDL.CreateWindow('',
-      SDL.windowPosUndefined, SDL.windowPosUndefined,
-      scrW, scrH, flags);
-    IF window # 0 THEN
-      IF software IN settings THEN flags := {SDL.rendererSoftware}
-      ELSE flags := {SDL.rendererAccelerated}
-      END;
-      INCL(flags, SDL.rendererPresentVsync);
-      renderer := SDL.CreateRenderer(window, -1, flags);
-      IF sharpPixels IN settings THEN
-        SDL.SetHint(SDL.hintRenderScaleQuality, '0')
-      ELSE SDL.SetHint(SDL.hintRenderScaleQuality, '1')
-      END;
-      ApplyScale;
-      screen := CreateBitmap(scrW, scrH);
-      screenTexture := 0;
-      UnsetRegion;
-      SDL.ShowCursor(0);
-      IF initMouse IN settings THEN InitMouseData END;
-      IF {noPng, noJpg} - settings # {} THEN flags := {};
-        IF ~(noPng IN settings) THEN INCL(flags, SDL.imgInitPng) END;
-        IF ~(noJpg IN settings) THEN INCL(flags, SDL.imgInitJpg) END;
-        IF flags - SDL.ImgInit(flags) # {} THEN
-          Out.String('Could not initialize image format support.'); Out.Ln;
-          GetError(s); Out.String(s); Out.Ln
-        END
-      END;
-      keyPressed := 0;
-      lastFlip := -1;
-      Randomize
-    END
-  END ;
-RETURN screen END Init;
-
-PROCEDURE Close*;
-BEGIN
-  IF screenTexture # 0 THEN
-    SDL.DestroyTexture(screenTexture);
-    screenTexture := 0
-  END;
-  IF renderer # 0 THEN
-    SDL.DestroyRenderer(renderer);
-    renderer := 0
-  END;
-  SDL.Quit
-END Close;
-
-BEGIN
-  scrW := 640; scrH := 400;
-  sizeStepX := 1; sizeStepY := 1;
-  initSettings := {fullscreen, spread, sharpPixels};
-  renderer := 0; buffer := NIL; wantFPS := 60;
-  mousePointer := NIL; lastBlitMouseOutside := FALSE;
-  mouseFocusX := 0; mouseFocusY := 0;
-  scaleX := 1; scaleY := 1;
-  events.first := 0; events.last := -1; events.len := 0;
-  randomSeed := 1; keyPressed := 0
-END Graph.
+MODULE Graph;
+(* Copyright 2017-2021 Arthur Yefimov
+
+This file is part of Free Oberon.
+
+Free Oberon is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Free Oberon is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT SDL := SDL2, SYSTEM, Platform, Out;
+
+CONST
+  (* Flip Flags *)
+  flipNone* = {};
+  flipH*    = 0;
+  flipV*    = 1;
+
+  (* Draw Mode Flags *)
+  drawSpriteNormal* = 0;
+  drawSpriteLit*    = 1;
+  drawSpriteTrans*  = 2;
+
+  (* Settings, see global varialbe settings *)
+  fullscreen*  = 0;
+  buffered*    = 1;
+  spread*      = 2;
+  sharpPixels* = 3;
+  software*    = 4;
+  initMouse*   = 8;
+  noPng* = 11;
+  noJpg* = 12;
+
+  (* Event Types *)
+  quit*        = 1;
+  windowEvent* = 2;
+  keyDown*     = 3;
+  keyUp*       = 4;
+  textInput*   = 5;
+  mouseMove*   = 6;
+  mouseDown*   = 7;
+  mouseUp*     = 8;
+  mouseWheel*  = 9;
+
+  (* Mouse Buttons *)
+  btnLeft*  = 0;
+  btnRight* = 1;
+  btnMid*   = 2;
+
+  (* Random Modulo *)
+  randomModulo* = 2147483647; (* =2^31-1 *)
+
+  (* Key Codes *)
+  kA*           = 4;
+  kB*           = 5;
+  kC*           = 6;
+  kD*           = 7;
+  kE*           = 8;
+  kF*           = 9;
+  kG*           = 10;
+  kH*           = 11;
+  kI*           = 12;
+  kJ*           = 13;
+  kK*           = 14;
+  kL*           = 15;
+  kM*           = 16;
+  kN*           = 17;
+  kO*           = 18;
+  kP*           = 19;
+  kQ*           = 20;
+  kR*           = 21;
+  kS*           = 22;
+  kT*           = 23;
+  kU*           = 24;
+  kV*           = 25;
+  kW*           = 26;
+  kX*           = 27;
+  kY*           = 28;
+  kZ*           = 29;
+  k1*           = 30;
+  k2*           = 31;
+  k3*           = 32;
+  k4*           = 33;
+  k5*           = 34;
+  k6*           = 35;
+  k7*           = 36;
+  k8*           = 37;
+  k9*           = 38;
+  k0*           = 39;
+  k1Pad*        = 89;
+  k2Pad*        = 90;
+  k3Pad*        = 91;
+  k4Pad*        = 92;
+  k5Pad*        = 93;
+  k6Pad*        = 94;
+  k7Pad*        = 95;
+  k8Pad*        = 96;
+  k9Pad*        = 97;
+  k0Pad*        = 98;
+  kF1*          = 58;
+  kF2*          = 59;
+  kF3*          = 60;
+  kF4*          = 61;
+  kF5*          = 62;
+  kF6*          = 63;
+  kF7*          = 64;
+  kF8*          = 65;
+  kF9*          = 66;
+  kF10*         = 67;
+  kF11*         = 68;
+  kF12*         = 69;
+  kEsc*         = 41;
+  kTilde*       = 53;
+  kMinus*       = 45;
+  kEquals*      = 46;
+  kBackspace*   = 42;
+  kTab*         = 43;
+  kOpenBrace*   = 47;
+  kCloseBrace*  = 48;
+  kEnter*       = 40;
+  kColon*       = 51;
+  kQuote*       = 52;
+  kBackslash*   = 49;
+  kBackslash2*  = 100;
+  kComma*       = 54;
+  kStop*        = 55;
+  kSlash*       = 56;
+  kSpace*       = 44;
+  kInsert*      = 73;
+  kDel*         = 76;
+  kHome*        = 74;
+  kEnd*         = 77;
+  kPgUp*        = 75;
+  kPgDn*        = 78;
+  kLeft*        = 80;
+  kRight*       = 79;
+  kUp*          = 82;
+  kDown*        = 81;
+  kSlashPad*    = 84;
+  kAsterisk*    = 85;
+  kMinusPad*    = 86;
+  kPlusPad*     = 87;
+  kDelPad*      = 99;
+  kEnterPad*    = 88;
+  kPrtScr*      = 70;
+  kPause*       = 72;
+
+  kModifiers*   = 115;
+
+  kLShift*      = 225;
+  kRShift*      = 229;
+  kLCtrl*       = 224;
+  kRCtrl*       = 228;
+  kAlt*         = 226;
+  kAltGr*       = 230;
+  kLWin*        = 227;
+  kRWin*        = 231;
+  kMenu*        = 123;
+  kScrLock*     = 124;
+  kNumLock*     = 125;
+  kCapsLock*    = 126;
+
+  kMax*         = 127;
+
+  (* Modifiers Set *)
+  mLShift*   = 0;
+  mRShift*   = 1;
+  mLCtrl*    = 6;
+  mRCtrl*    = 7;
+  mLAlt*     = 8;
+  mRAlt*     = 9;
+  mLGui*     = 10;
+  mRGui*     = 11;
+  mNum*      = 12;
+  mCaps*     = 13;
+  mMode*     = 14;
+  mReserved* = 15;
+  mCtrl*  = {mLCtrl, mRCtrl};
+  mShift* = {mLShift, mRShift};
+  mAlt*   = {mLAlt, mRAlt};
+  mGui*   = {mLGui, mRGui};
+
+TYPE
+  ADRINT = SYSTEM.ADRINT;
+  CHAR = SHORTCHAR;
+  SET32 = SET;
+
+  Bitmap* = POINTER TO BitmapDesc;
+  BitmapDesc* = RECORD
+    surface: SDL.Surface;
+    w*, h*: INTEGER
+  END;
+
+  Font* = POINTER TO FontDesc;
+  FontDesc* = RECORD
+    bmp*: Bitmap;
+    charW*, charH*: INTEGER;
+    charRows*, charsInRow*: INTEGER;
+    sprites*: POINTER TO ARRAY OF ARRAY OF SDL.Rect
+  END;
+
+  KeyArray = SDL.KeyArray;
+
+  Key* = RECORD
+    code*: INTEGER; (* Physical key code *)
+    sym*: INTEGER; (* Virtual key code *)
+    mod*: SET; (* Key modifiers *)
+    repeat*: BOOLEAN
+  END;
+
+  Region* = RECORD
+    x*, y*, w*, h*: INTEGER
+  END;
+
+  Event* = RECORD
+    type*: INTEGER;
+    key*: Key;
+    x*, y*: INTEGER;
+    xRel*, yRel*: INTEGER;
+    button*: INTEGER;
+    buttons*: SET; (* What mouse buttons are pressed *)
+    down*: BOOLEAN;
+    s*: ARRAY 32 OF CHAR;
+    ch*: INTEGER(*SHOULD BE 2-byte CHAR*)
+  END;
+
+  EventQueue* = RECORD
+    buf: ARRAY 256 OF Event;
+    first, last: INTEGER; (* Index of first and last element *)
+    len: INTEGER (* Amount of elements currently in queue *)
+  END;
+
+  CloseBtnProc* = PROCEDURE;
+
+VAR
+  window: SDL.Window;
+  renderer: SDL.Renderer;
+  screen: Bitmap;
+  screenTexture: SDL.Texture;
+  events: EventQueue;
+  keyPressed: INTEGER;
+
+  settings, initSettings: SET; (* See constants above *)
+  sizeStepX, sizeStepY: INTEGER;
+  scaleX, scaleY: REAL;
+  scrW, scrH: INTEGER;
+  wantFPS: INTEGER;
+  buffer: Bitmap;
+  lastFlip: INTEGER;
+  frames, framesT: INTEGER;
+
+  (* Flip Region *)
+  flipRegion: Region;
+
+  (* Mouse *)
+  mouseX, mouseY: INTEGER;
+  mouseFocusX, mouseFocusY: INTEGER;
+  lastBlitMouseOutside: BOOLEAN;
+  lastBlitMouseX, lastBlitMouseY: INTEGER;
+  needRedrawMouse: BOOLEAN; (* True if mouse has moved since last redraw *)
+  showMouse: BOOLEAN; (* Whether to show mouse pointer on screen *)
+  stdMousePointer: Bitmap;
+  mousePointer: Bitmap;
+  underMouse: Bitmap; (* Buffer to copy part of image under the mouse *)
+
+  randomSeed-: INTEGER;
+
+PROCEDURE -AAIncludeSDL2h0 '#include "SDL2.h0"';
+
+(* General *)
+
+PROCEDURE GetError*(VAR s: ARRAY OF CHAR);
+TYPE P = POINTER TO ARRAY 10240 OF CHAR;
+VAR p: P;
+BEGIN p := SYSTEM.VAL(P, SDL.GetError()); s := p^$
+END GetError;
+
+PROCEDURE Settings*(w, h: INTEGER; flags: SET);
+BEGIN scrW := w; scrH := h;
+  initSettings := flags;
+  showMouse := initMouse IN flags
+END Settings;
+
+PROCEDURE SetSizeStep*(w, h: INTEGER);
+BEGIN sizeStepX := w; sizeStepY := h
+END SetSizeStep;
+
+PROCEDURE ApplyScale;
+BEGIN
+  SDL.RenderSetLogicalSize(renderer,
+    SHORT(ENTIER(scrW * scaleX)), SHORT(ENTIER(scrH * scaleY)));
+END ApplyScale;
+
+PROCEDURE SetScale*(x, y: REAL);
+BEGIN scaleX := x; scaleY := y;
+  IF renderer # 0 THEN ApplyScale END
+END SetScale;
+
+PROCEDURE SetFPS*(fps: INTEGER);
+BEGIN IF fps <= 0 THEN fps := -1 END;
+  wantFPS := fps
+END SetFPS;
+
+PROCEDURE GetDesktopResolution*(VAR w, h: INTEGER);
+VAR mode: SDL.DisplayMode;
+BEGIN SDL.GetDesktopDisplayMode(0, mode);
+  w := mode.w; h := mode.h
+END GetDesktopResolution;
+
+(* Flip Region *)
+PROCEDURE SetRegion*(x, y, w, h: INTEGER);
+BEGIN
+  flipRegion.x := x; flipRegion.y := y;
+  flipRegion.w := w; flipRegion.h := h
+END SetRegion;
+
+PROCEDURE UnsetRegion*;
+BEGIN flipRegion.w := -1
+END UnsetRegion;
+
+PROCEDURE AddRegion*(x, y, w, h: INTEGER);
+BEGIN
+  IF flipRegion.w = -1 THEN (* No flip region yet *)
+    SetRegion(x, y, w, h) (* Just set it *)
+  ELSE (* Flip Region exists, add to it *)
+    IF x < flipRegion.x THEN flipRegion.x := x END;
+    IF y < flipRegion.y THEN flipRegion.y := y END;
+    IF x + w > flipRegion.x + flipRegion.w THEN
+      flipRegion.w := w + x - flipRegion.x END;
+    IF y + h > flipRegion.y + flipRegion.h THEN
+      flipRegion.h := h + y - flipRegion.y END
+  END
+END AddRegion;
+
+(* Drawing *)
+
+PROCEDURE MakeCol*(r, g, b: INTEGER): INTEGER;
+BEGIN
+  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, r) * {0..7});
+  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, g) * {0..7});
+  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, b) * {0..7}) ;
+RETURN SYSTEM.LSH(SYSTEM.LSH(0FF00H + b, 8) + g, 8) + r END MakeCol;
+
+PROCEDURE ColorToRGB*(color: INTEGER; VAR r, g, b: INTEGER);
+BEGIN
+  r := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, color) * {0..7});
+  g := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -8)) * {0..7});
+  b := SYSTEM.VAL(INTEGER, SYSTEM.VAL(SET32, SYSTEM.LSH(color, -16)) * {0..7})
+END ColorToRGB;
+
+PROCEDURE BmpCol*(bmp: Bitmap; r, g, b: INTEGER): INTEGER;
+BEGIN RETURN SDL.MapRGB(bmp.surface.format, SHORT(r), SHORT(g), SHORT(b))
+END BmpCol;
+
+PROCEDURE ClearToColor*(bmp: Bitmap; color: INTEGER);
+BEGIN SDL.FillRectNil(bmp.surface, color)
+END ClearToColor;
+
+PROCEDURE ClearBitmap*(bmp: Bitmap);
+BEGIN ClearToColor(bmp, MakeCol(0, 0, 0))
+END ClearBitmap;
+
+PROCEDURE ClearScreenToColor*(color: INTEGER);
+BEGIN ClearToColor(screen, color)
+END ClearScreenToColor;
+
+PROCEDURE ClearScreen*;
+BEGIN ClearToColor(screen, MakeCol(0, 0, 0))
+END ClearScreen;
+
+PROCEDURE LockBitmap*(bmp: Bitmap);
+BEGIN SDL.LockSurface(bmp.surface)
+END LockBitmap;
+
+PROCEDURE UnlockBitmap*(bmp: Bitmap);
+BEGIN SDL.UnlockSurface(bmp.surface)
+END UnlockBitmap;
+
+PROCEDURE PutPixelFast*(bmp: Bitmap; x, y, color: INTEGER);
+VAR n: ADRINT;
+BEGIN n := SYSTEM.VAL(ADRINT, bmp.surface.pixels);
+  INC(n, (y * bmp.w + x) * 4);
+  SYSTEM.PUT(n, color)
+END PutPixelFast;
+
+PROCEDURE PutPixel*(bmp: Bitmap; x, y, color: INTEGER);
+VAR n: ADRINT;
+BEGIN
+  IF (x >= 0) & (x < bmp.w) &
+     (y >= 0) & (y < bmp.h) THEN
+    SDL.LockSurface(bmp.surface);
+    n := SYSTEM.VAL(ADRINT, bmp.surface.pixels);
+    INC(n, (y * bmp.w + x) * 4);
+    SYSTEM.PUT(n, color);
+    SDL.UnlockSurface(bmp.surface)
+  END
+END PutPixel;
+
+PROCEDURE GetPixel*(bmp: Bitmap; x, y: INTEGER): INTEGER;
+VAR color: INTEGER;
+  n: ADRINT;
+BEGIN
+  IF (x >= 0) & (x < bmp.w) &
+     (y >= 0) & (y < bmp.h) THEN
+    SDL.LockSurface(bmp.surface);
+    n := SYSTEM.VAL(ADRINT, bmp.surface.pixels);
+    INC(n, (y * bmp.w + x) * 4);
+    SYSTEM.GET(n, color);
+    SDL.UnlockSurface(bmp.surface)
+  ELSE color := 0
+  END ;
+RETURN color END GetPixel;
+
+PROCEDURE HLine*(bmp: Bitmap; x1, y, x2, color: INTEGER);
+VAR rect: SDL.Rect; t: INTEGER;
+BEGIN
+  IF x1 > x2 THEN t := x1; x1 := x2; x2 := t END;
+  rect.x := x1; rect.y := y;
+  rect.w := x2 - x1 + 1; rect.h := 1;
+  SDL.FillRect(bmp.surface, rect, color)
+END HLine;
+
+PROCEDURE VLine*(bmp: Bitmap; x, y1, y2, color: INTEGER);
+VAR rect: SDL.Rect; t: INTEGER;
+BEGIN
+  IF y1 > y2 THEN t := y1; y1 := y2; y2 := t END;
+  rect.x := x; rect.y := y1;
+  rect.w := 1; rect.h := y2 - y1 + 1;
+  SDL.FillRect(bmp.surface, rect, color)
+END VLine;
+
+PROCEDURE Line*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
+VAR x, y, i, dx, dy, sx, sy, e: INTEGER; vert: BOOLEAN;
+BEGIN
+  IF x1 = x2 THEN VLine(bmp, x1, y1, y2, color)
+  ELSIF y1 = y2 THEN HLine(bmp, x1, y1, x2, color)
+  ELSE
+    SDL.LockSurface(bmp.surface);
+    dx := ABS(x1 - x2); dy := ABS(y1 - y2);
+    IF x2 > x1 THEN sx := 1 ELSE sx := -1 END;
+    IF y2 > y1 THEN sy := 1 ELSE sy := -1 END;
+    x := x1; y := y1; vert := dy > dx;
+    IF vert THEN i := dx; dx := dy; dy := i END;
+    e := 2 * dy - dx;
+    FOR i := 0 TO dx DO
+      IF (x >= 0) & (x < bmp.w) &
+         (y >= 0) & (y < bmp.h) THEN
+        PutPixelFast(bmp, x, y, color)
+      END;
+      IF e >= 0 THEN
+        IF vert THEN INC(x, sx) ELSE INC(y, sy) END;
+        DEC(e, 2 * dx)
+      END;
+      IF vert THEN INC(y, sy) ELSE INC(x, sx) END;
+      INC(e, 2 * dy)
+    END;
+    SDL.UnlockSurface(bmp.surface)
+  END
+END Line;
+
+PROCEDURE FastLine*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
+BEGIN
+  (*Al.FastLine(bmp.bmp, x1, y1, x2, y2, color)*)
+END FastLine;
+
+PROCEDURE Rect*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER); (*!FIXME*)
+VAR rect: SDL.Rect;
+BEGIN
+  rect.x := x1; rect.y := y1;
+  rect.w := 1; rect.h := y2 - y1 + 1;
+  SDL.FillRect(bmp.surface, rect, color);
+  rect.x := x2;
+  SDL.FillRect(bmp.surface, rect, color);
+  rect.x := x1; rect.w := x2 - x1 + 1; rect.h := 1;
+  SDL.FillRect(bmp.surface, rect, color);
+  rect.y := y2;
+  SDL.FillRect(bmp.surface, rect, color)
+END Rect;
+
+PROCEDURE RectFill*(bmp: Bitmap; x1, y1, x2, y2, color: INTEGER);
+VAR rect: SDL.Rect;
+BEGIN
+  rect.x := x1; rect.y := y1;
+  rect.w := x2 - x1 + 1; rect.h := y2 - y1 + 1;
+  SDL.FillRect(bmp.surface, rect, color)
+END RectFill;
+
+PROCEDURE Circle*(b: Bitmap; cx, cy, r, col: INTEGER);
+VAR x, y, d: INTEGER;
+BEGIN x := 0; y := r; d := 3 - 2 * r;
+  WHILE x <= y DO
+    PutPixel(b, cx + x, cy + y, col);
+    PutPixel(b, cx + y, cy + x, col);
+    PutPixel(b, cx + x, cy - y, col);
+    PutPixel(b, cx + y, cy - x, col);
+    PutPixel(b, cx - x, cy + y, col);
+    PutPixel(b, cx - y, cy + x, col);
+    PutPixel(b, cx - x, cy - y, col);
+    PutPixel(b, cx - y, cy - x, col);
+    IF d < 0 THEN d := d + 4 * x + 6
+    ELSE d := d + 4 * (x - y) + 10; DEC(y)
+    END;
+    INC(x)
+  END
+END Circle;
+
+PROCEDURE CircleFill*(b: Bitmap; cx, cy, r, col: INTEGER);
+VAR x, y, d: INTEGER;
+BEGIN x := 0; y := r; d := 3 - 2 * r;
+  WHILE x <= y DO
+    HLine(b, cx - x, cy + y, cx + x, col);
+    HLine(b, cx - y, cy + x, cx + y, col);
+    HLine(b, cx - x, cy - y, cx + x, col);
+    HLine(b, cx - y, cy - x, cx + y, col);
+    IF d < 0 THEN d := d + 4 * x + 6
+    ELSE d := d + 4 * (x - y) + 10; DEC(y)
+    END;
+    INC(x)
+  END
+END CircleFill;
+
+PROCEDURE Ellipse*(bmp: Bitmap; x, y, rx, ry, color: INTEGER);
+BEGIN
+END Ellipse;
+
+PROCEDURE EllipseFill*(bmp: Bitmap; x, y, rx, ry, color: INTEGER);
+BEGIN
+END EllipseFill;
+
+PROCEDURE FloodFill*(bmp: Bitmap; x, y, color: INTEGER);
+BEGIN
+END FloodFill;
+
+(* Bitmap *)
+
+PROCEDURE (bmp: Bitmap) Finalize*, NEW;
+BEGIN
+END Finalize;
+
+PROCEDURE CreateBitmap*(w, h: INTEGER): Bitmap;
+VAR bmp: Bitmap;
+  s: ARRAY 2560 OF CHAR;
+BEGIN NEW(bmp);
+  bmp.surface := SDL.CreateRGBSurface(0, w, h, 32,
+    000000FFH, 0000FF00H, 00FF0000H, -1000000H);
+  IF bmp.surface = NIL THEN
+    GetError(s); Out.String(s); Out.Ln
+  END;
+  bmp.w := w; bmp.h := h ;
+RETURN bmp END CreateBitmap;
+
+PROCEDURE DestroyBitmap*(bmp: Bitmap);
+BEGIN SDL.FreeSurface(bmp.surface)
+END DestroyBitmap;
+
+PROCEDURE LoadBitmap*(filename: ARRAY OF CHAR): Bitmap;
+VAR bmp: Bitmap;
+BEGIN NEW(bmp); bmp.surface := SDL.ImgLoad(filename);
+  IF bmp.surface = NIL THEN bmp := NIL
+  ELSE bmp.w := bmp.surface.w; bmp.h := bmp.surface.h END ;
+RETURN bmp END LoadBitmap;
+
+PROCEDURE SaveBmp*(bmp: Bitmap; filename: ARRAY OF CHAR): BOOLEAN;
+BEGIN
+  RETURN SDL.SaveBmpRW(bmp.surface, SDL.RWFromFile(filename, 'wb'), 1) = 0
+END SaveBmp;
+
+PROCEDURE SavePng*(bmp: Bitmap; filename: ARRAY OF CHAR): BOOLEAN;
+BEGIN
+RETURN SDL.ImgSavePng(bmp.surface, filename) = 0 END SavePng;
+
+PROCEDURE SaveJpg*(bmp: Bitmap; filename: ARRAY OF CHAR): BOOLEAN;
+BEGIN
+RETURN SDL.ImgSaveJpg(bmp.surface, filename) = 0 END SaveJpg;
+
+PROCEDURE Blit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy: INTEGER);
+VAR a, b: SDL.Rect;
+BEGIN a.x := sx; a.y := sy; a.w := sw; a.h := sh;
+  b.x := dx; b.y := dy;
+  SDL.BlitSurface(src.surface, a, dest.surface, b)
+END Blit;
+
+PROCEDURE BlitWhole*(src, dest: Bitmap; x, y: INTEGER);
+VAR b: SDL.Rect;
+BEGIN b.x := x; b.y := y;
+  SDL.BlitSurfaceNil(src.surface, dest.surface, b)
+END BlitWhole;
+
+PROCEDURE StretchBlit*(src, dest: Bitmap; sx, sy, sw, sh, dx, dy, dw, dh: INTEGER);
+VAR a, b: SDL.Rect;
+BEGIN
+  a.x := sx; a.y := sy; a.w := sw; a.h := sh;
+  b.x := dx; b.y := dy; b.w := dw; b.h := dh;
+  SDL.BlitScaled(src.surface, a, dest.surface, b)
+END StretchBlit;
+
+PROCEDURE SetAlpha*(bmp: Bitmap; alpha: INTEGER);
+BEGIN
+  IF SDL.SetSurfaceAlphaMod(bmp.surface, CHR(alpha)) = 0 THEN END
+END SetAlpha;
+
+PROCEDURE MaskedBlit*(src, dest: Bitmap; sx, sy, dx, dy, w, h: INTEGER);
+BEGIN
+  (*Al.MaskedBlit(src.bmp, dest.bmp, sx, sy, dx, dy, w, h)*)
+END MaskedBlit;
+
+PROCEDURE DrawSpriteEx*(dest, sprite: Bitmap; x, y, mode: INTEGER; flip: SET);
+BEGIN
+  (*Al.DrawSpriteEx(dest.bmp, sprite.bmp, x, y, mode, flip)*)
+END DrawSpriteEx;
+
+PROCEDURE DrawCharacterEx*(dest, sprite: Bitmap; x, y, color, bg: INTEGER);
+BEGIN
+  (*Al.DrawCharacterEx(dest.bmp, sprite.bmp, x, y, color, bg)*)
+END DrawCharacterEx;
+
+PROCEDURE SetColorKey*(bmp: Bitmap; color: INTEGER);
+BEGIN SDL.SetColorKey(bmp.surface, 1, color)
+END SetColorKey;
+
+(* Font *)
+
+PROCEDURE LoadFont*(filename: ARRAY OF CHAR; charW, charH: INTEGER): Font;
+VAR bmp: Bitmap; font: Font;
+    x, y, sx, sy, tmp: INTEGER;
+BEGIN bmp := LoadBitmap(filename);
+  IF bmp = NIL THEN font := NIL
+  ELSE
+    bmp.surface := SDL.ConvertSurface(bmp.surface,
+      screen.surface.format, 0);
+    SetColorKey(bmp, BmpCol(bmp, 0, 0, 0));
+    NEW(font); font.bmp := bmp;
+    font.charW := charW; font.charH := charH;
+    font.charsInRow := font.bmp.w DIV charW;
+    font.charRows := font.bmp.h DIV charH;
+    (*!FIXME remove sprites from here at all*)
+    NEW(font.sprites, font.charRows, font.charsInRow);
+    sy := 0;
+    FOR y := 0 TO font.charRows - 1 DO
+      sx := 0;
+      FOR x := 0 TO font.charsInRow - 1 DO
+        font.sprites[y, x].x := sx;
+        font.sprites[y, x].y := sy;
+        font.sprites[y, x].w := charW;
+        font.sprites[y, x].h := charH;
+        INC(sx, charW)
+      END;
+      INC(sy, charH)
+    END
+  END ;
+RETURN font END LoadFont;
+
+PROCEDURE DrawCharacter*(dest: Bitmap; font: Font;
+  x, y: INTEGER; ch: CHAR; fg: INTEGER);
+VAR fx, fy, r, g, b: INTEGER; dstRect: SDL.Rect;
+BEGIN dstRect.x := x; dstRect.y := y;
+  fx := ORD(ch) MOD font.charsInRow;
+  fy := ORD(ch) DIV font.charsInRow;
+  ColorToRGB(fg, r, g, b);
+  SDL.SetSurfaceColorMod(font.bmp.surface, r, g, b);
+  SDL.BlitSurface(font.bmp.surface, font.sprites[fy, fx],
+    screen.surface, dstRect)
+END DrawCharacter;
+
+PROCEDURE DrawString*(dest: Bitmap; font: Font;
+  x, y: INTEGER; s: ARRAY OF CHAR; fg: INTEGER);
+VAR i, cx: INTEGER;
+BEGIN i := 0; cx := x;
+  WHILE (s[i] # 0X) & (cx < dest.w) DO
+    DrawCharacter(dest, font, cx, y, s[i], fg);
+    INC(i); INC(cx, font.charW)
+  END
+END DrawString;
+
+PROCEDURE StartTextInput*;
+BEGIN SDL.StartTextInput
+END StartTextInput;
+
+PROCEDURE StopTextInput*;
+BEGIN SDL.StopTextInput
+END StopTextInput;
+
+PROCEDURE QueueEvent;
+BEGIN INC(events.len); INC(events.last);
+  IF events.last = LEN(events.buf) THEN events.last := 0 END
+END QueueEvent;
+
+PROCEDURE PumpKeyDown(VAR event: SDL.Event);
+VAR e: SDL.KeyboardEvent;
+  n: INTEGER; mod: SET;
+BEGIN
+  IF events.len < LEN(events.buf) THEN
+    e := SYSTEM.VAL(SDL.KeyboardEvent, SYSTEM.ADR(event));
+    n := e.keysym.mod; mod := SYSTEM.VAL(SET32, n);
+    QueueEvent;
+    events.buf[events.last].type := keyDown;
+    events.buf[events.last].key.code := e.keysym.scancode;
+    events.buf[events.last].key.sym := e.keysym.sym;
+    events.buf[events.last].key.mod := mod;
+    events.buf[events.last].key.repeat := e.repeat # 0;
+    INC(keyPressed)
+  END
+END PumpKeyDown;
+
+PROCEDURE DecodeChar(IN s: ARRAY OF CHAR): INTEGER;
+VAR i, x, c: INTEGER;
+BEGIN c := ORD(s[0]);
+  IF c > 80H THEN x := ORD(s[1]) MOD 64; (* Not 1 byte *)
+    IF c DIV 32 = 6 THEN (* 2 bytes *)
+      c := c MOD 32 * 64 + x
+    ELSIF c DIV 16 = 14 THEN (* 3 bytes *)
+      c := (c MOD 16 * 64 + x) * 64 + ORD(s[2]) MOD 64
+    ELSIF c DIV 8 = 30 THEN (* 4 bytes *)
+      c := ((c MOD 8 * 64 + x) * 64 + ORD(s[2]) MOD 64) * 64 + ORD(s[3]) MOD 64
+    ELSE c := 0
+    END
+  END ;
+RETURN c END DecodeChar;
+
+PROCEDURE PumpTextEvent(event: SDL.Event);
+VAR sym: INTEGER;
+  e: SDL.TextInputEvent;
+BEGIN
+  IF events.len < LEN(events.buf) THEN
+    e := SYSTEM.VAL(SDL.TextInputEvent, SYSTEM.ADR(event));
+    IF e.text[1] = 0X THEN (* ASCII character *)
+      sym := ORD(e.text[0])
+    ELSE (* Unicode character. Assuming 2 bytes *)
+      sym := ORD(e.text[1]);
+      (* UTF-8 cyrillic *)
+      IF (e.text[0] = 0D0X) OR (e.text[0] = 0D1X) THEN
+        IF e.text[0] = 0D0X THEN DEC(sym, 090H)
+        ELSE DEC(sym, 060H - 16)
+        END;
+        (* Convert to CP866 *)
+        IF sym = 65 THEN sym := 0F1H (* jo *)
+        ELSIF sym = -15 THEN sym := 0F0H (* JO *)
+        ELSIF sym < 48 THEN INC(sym, 80H) (* A..JA, a..p *)
+        ELSE INC(sym, 0E0H - 48) (* r..ja *)
+        END
+      END
+    END;
+    QueueEvent;
+    events.buf[events.last].type := textInput;
+    events.buf[events.last].s := e.text$;
+    events.buf[events.last].ch := DecodeChar(e.text)
+  END
+END PumpTextEvent;
+
+PROCEDURE UpdateMousePos(event: SDL.Event);
+VAR e: SDL.MouseMotionEvent; newX, newY: INTEGER;
+BEGIN
+  e := SYSTEM.VAL(SDL.MouseMotionEvent, SYSTEM.ADR(event));
+  newX := e.x; newY := e.y;
+  IF newX < 0 THEN newX := 0
+  ELSIF newX >= screen.w THEN newX := screen.w - 1 END;
+  IF newY < 0 THEN newY := 0
+  ELSIF newY >= screen.h THEN newY := screen.h - 1 END;
+  IF (newX # mouseX) OR (newY # mouseY) THEN
+    mouseX := newX;  mouseY := newY;
+    needRedrawMouse := TRUE
+  END
+END UpdateMousePos;
+
+(* Keyboard *)
+
+PROCEDURE GetKeyArray(): KeyArray;
+BEGIN
+RETURN SYSTEM.VAL(KeyArray, SDL.GetKeyboardStateNil()) END GetKeyArray;
+
+PROCEDURE KeyDown*(key: INTEGER): BOOLEAN;
+VAR keys: KeyArray;
+BEGIN keys := GetKeyArray() ;
+RETURN keys[key] END KeyDown;
+
+PROCEDURE AltPressed*(): BOOLEAN;
+VAR keys: KeyArray;
+BEGIN keys := GetKeyArray() ;
+RETURN keys[kAlt] OR keys[kAltGr] END AltPressed;
+
+PROCEDURE ShiftPressed*(): BOOLEAN;
+VAR keys: KeyArray;
+BEGIN keys := GetKeyArray() ;
+RETURN keys[kLShift] OR keys[kRShift] END ShiftPressed;
+
+PROCEDURE CtrlPressed*(): BOOLEAN;
+VAR keys: KeyArray;
+BEGIN keys := GetKeyArray() ;
+RETURN keys[kLCtrl] OR keys[kRCtrl] END CtrlPressed;
+
+(* Mouse *)
+
+PROCEDURE MouseOnScreen*(): BOOLEAN;
+VAR flags: SET;
+BEGIN flags := SDL.GetWindowFlags(window);
+RETURN SDL.windowMouseFocus IN flags END MouseOnScreen;
+
+PROCEDURE ShowMouse*(show: BOOLEAN);
+BEGIN showMouse := show
+END ShowMouse;
+
+PROCEDURE GetRealMousePos*(VAR x, y: INTEGER);
+BEGIN IF SDL.GetMouseState(x, y) = 0 THEN END
+END GetRealMousePos;
+
+PROCEDURE GetMousePos*(VAR x, y: INTEGER);
+BEGIN x := mouseX; y := mouseY
+END GetMousePos;
+
+PROCEDURE GetMouseButtons*(): SET;
+VAR x, y: INTEGER;
+BEGIN
+RETURN SYSTEM.VAL(SET32, SDL.GetMouseState(x, y)) END GetMouseButtons;
+
+PROCEDURE CreateStdMousePointer*;
+VAR b: Bitmap; fg, bg: INTEGER;
+BEGIN b := CreateBitmap(12, 19);
+  bg := MakeCol(255, 0, 255); fg := MakeCol(0, 0, 0);
+  ClearToColor(b, bg); SetColorKey(b, bg);
+  Line(b, 0, 0, 10, 10, fg); Line(b, 0, 0, 0, 14, fg);
+  Line(b, 0, 14, 3, 11, fg); Line(b, 10, 10, 6, 10, fg);
+  Line(b, 4, 12, 6, 17, fg); Line(b, 6, 11, 9, 17, fg);
+  Line(b, 7, 18, 8, 18, fg); bg := MakeCol(255, 255, 255);
+  VLine(b, 1, 2, 12, bg); VLine(b, 2, 3, 11, bg);
+  VLine(b, 3, 4, 10, bg); VLine(b, 4, 5, 11, bg);
+  VLine(b, 5, 6, 13, bg); VLine(b, 6, 7, 9, bg);
+  VLine(b, 7, 8, 9, bg); VLine(b, 8, 9, 9, bg);
+  VLine(b, 6, 12, 15, bg); VLine(b, 7, 14, 17, bg);
+  VLine(b, 8, 16, 17, bg);
+  stdMousePointer := b
+END CreateStdMousePointer;
+
+PROCEDURE SetMouseFocus*(x, y: INTEGER);
+BEGIN
+  mouseFocusX := x; mouseFocusY := y;
+  needRedrawMouse := TRUE
+END SetMouseFocus;
+
+PROCEDURE SetMousePointer*(bmp: Bitmap; x, y: INTEGER);
+BEGIN
+  IF bmp = NIL THEN
+    mousePointer := stdMousePointer;
+    x := 0; y := 0
+  ELSE mousePointer := bmp
+  END;
+  SetMouseFocus(x, y);
+  underMouse := CreateBitmap(mousePointer.w, mousePointer.h);
+  needRedrawMouse := TRUE
+END SetMousePointer;
+
+PROCEDURE GetMousePointer*(): Bitmap;
+BEGIN
+RETURN mousePointer END GetMousePointer;
+
+PROCEDURE SetStdMousePointer*;
+BEGIN SetMousePointer(stdMousePointer, 0, 0)
+END SetStdMousePointer;
+
+PROCEDURE InitMouseData;
+BEGIN CreateStdMousePointer; SetStdMousePointer
+END InitMouseData;
+
+(* Misc *)
+PROCEDURE SetWindowTitle*(title: ARRAY OF CHAR);
+BEGIN SDL.SetWindowTitle(window, title)
+END SetWindowTitle;
+
+PROCEDURE SwitchToWindowed*;
+BEGIN
+  IF fullscreen IN settings THEN
+    SDL.SetWindowSize(window, screen.w, screen.h);
+    IF SDL.SetWindowFullscreen(window, {}) = 0 THEN
+      EXCL(settings, fullscreen)
+    END
+  END
+END SwitchToWindowed;
+
+PROCEDURE SwitchToFullscreen*;
+BEGIN
+  IF ~(fullscreen IN settings) THEN
+    IF SDL.SetWindowFullscreen(window, SDL.windowFullscreenDesktop) = 0 THEN
+      INCL(settings, fullscreen)
+    END
+  END
+END SwitchToFullscreen;
+
+PROCEDURE ToggleFullscreen*;
+BEGIN
+  IF fullscreen IN settings THEN SwitchToWindowed
+  ELSE SwitchToFullscreen
+  END
+END ToggleFullscreen;
+
+PROCEDURE Delay*(n: INTEGER);
+BEGIN SDL.Delay(n)
+END Delay;
+
+PROCEDURE HandleMouseButton(VAR event: SDL.Event);
+VAR e: SDL.MouseButtonEvent;
+BEGIN
+END HandleMouseButton;
+
+PROCEDURE PumpQuit;
+BEGIN
+  IF events.len < LEN(events.buf) THEN
+    QueueEvent;
+    events.buf[events.last].type := quit
+  END
+END PumpQuit;
+
+PROCEDURE PumpMouseMove(VAR event: SDL.Event);
+VAR e: SDL.MouseMotionEvent;
+  newX, newY: INTEGER;
+BEGIN
+  e := SYSTEM.VAL(SDL.MouseMotionEvent, SYSTEM.ADR(event));
+  newX := e.x; newY := e.y;
+  IF newX < 0 THEN newX := 0
+  ELSIF newX >= screen.w THEN newX := screen.w - 1
+  END;
+  IF newY < 0 THEN newY := 0
+  ELSIF newY >= screen.h THEN newY := screen.h - 1
+  END;
+  IF (newX # mouseX) OR (newY # mouseY) THEN
+    mouseX := newX; mouseY := newY;
+    needRedrawMouse := TRUE;
+    IF events.len < LEN(events.buf) THEN
+      QueueEvent;
+      events.buf[events.last].type := mouseMove;
+      events.buf[events.last].x := SHORT(ENTIER(newX / scaleX));
+      events.buf[events.last].y := SHORT(ENTIER(newY / scaleY));
+      events.buf[events.last].xRel := e.xRel;
+      events.buf[events.last].yRel := e.yRel;
+      events.buf[events.last].buttons := SYSTEM.VAL(SET32, e.state)
+    END
+  END
+END PumpMouseMove;
+
+PROCEDURE PumpMouseButton(VAR event: SDL.Event; type: INTEGER);
+VAR e: SDL.MouseButtonEvent;
+BEGIN
+  e := SYSTEM.VAL(SDL.MouseButtonEvent, SYSTEM.ADR(event));
+  IF events.len < LEN(events.buf) THEN
+    QueueEvent;
+    events.buf[events.last].type := type;
+    events.buf[events.last].button := e.button - 1;
+    events.buf[events.last].down := e.state # 0;
+    IF e.x < 0 THEN e.x := 0
+    ELSIF e.x >= screen.w THEN e.x := screen.w - 1
+    END;
+    IF e.y < 0 THEN e.y := 0
+    ELSIF e.y >= screen.h THEN e.y := screen.h - 1
+    END;
+    events.buf[events.last].x := SHORT(ENTIER(e.x / scaleX));
+    events.buf[events.last].y := SHORT(ENTIER(e.y / scaleY))
+  END
+END PumpMouseButton;
+
+PROCEDURE RepeatFlip*;
+BEGIN
+  IF screenTexture # 0 THEN
+    SDL.SetRenderDrawColor(renderer, 0, 0, 0, 255);
+    SDL.RenderClear(renderer);
+    SDL.RenderCopyNil(renderer, screenTexture);
+    SDL.RenderPresent(renderer)
+  END
+END RepeatFlip;
+
+PROCEDURE WaitEvents*(timeout: INTEGER);
+VAR event: SDL.Event; n: INTEGER;
+BEGIN
+  n := SDL.PollEvent(event);
+  IF (n # 0) OR (events.len = 0) THEN
+    IF n = 0 THEN
+      IF timeout > 0 THEN n := SDL.WaitEventTimeout(event, timeout)
+      ELSIF timeout < 0 THEN n := SDL.WaitEvent(event)
+      END
+    END;
+    IF n # 0 THEN
+      REPEAT
+        IF event.type = SDL.mouseMotion THEN
+          PumpMouseMove(event)
+        ELSIF event.type = SDL.mouseButtonDown THEN
+          PumpMouseButton(event, mouseDown)
+        ELSIF event.type = SDL.mouseButtonUp THEN
+          PumpMouseButton(event, mouseUp)
+        ELSIF event.type = SDL.keyDown THEN
+          PumpKeyDown(event)
+        ELSIF event.type = SDL.textInput THEN
+          PumpTextEvent(event)
+        ELSIF event.type = SDL.quit THEN
+          PumpQuit
+        END
+      UNTIL SDL.PollEvent(event) = 0
+    END
+  END
+END WaitEvents;
+
+PROCEDURE PollEvent*(VAR event: Event): BOOLEAN;
+VAR hasEvent: BOOLEAN;
+BEGIN
+  IF events.len > 0 THEN
+    event := events.buf[events.first];
+    IF event.type = keyDown THEN DEC(keyPressed) END;
+    DEC(events.len); INC(events.first);
+    IF events.first = LEN(events.buf) THEN events.first := 0 END;
+    hasEvent := TRUE
+  ELSE hasEvent := FALSE
+  END ;
+RETURN hasEvent END PollEvent;
+
+PROCEDURE KeyPressed*(): BOOLEAN;
+BEGIN WaitEvents(0) ;
+RETURN keyPressed > 0 END KeyPressed;
+
+PROCEDURE ReadKey*(): CHAR;
+VAR event: Event; done: BOOLEAN; ch: CHAR;
+BEGIN done := FALSE;
+  REPEAT
+    WaitEvents(-1);
+    WHILE PollEvent(event) DO
+      CASE event.type OF
+        keyDown: ch := CHR(event.key.sym); done := TRUE
+      | quit: ch := 0X; done := TRUE
+      ELSE
+      END
+    END
+  UNTIL done ;
+RETURN ch END ReadKey;
+
+PROCEDURE Pause*;
+BEGIN IF ReadKey() = 0X THEN END
+END Pause;
+
+PROCEDURE WindowShown*(): BOOLEAN;
+VAR flags: SET;
+BEGIN flags := SDL.GetWindowFlags(window) ;
+RETURN SDL.windowShown IN flags END WindowShown;
+
+PROCEDURE GetTicks*(): INTEGER;
+BEGIN
+RETURN SDL.GetTicks() END GetTicks;
+
+PROCEDURE Flip*;
+VAR mx, my: INTEGER; (* Mouse bitmap X Y *)
+    blitMouse: BOOLEAN;
+    dt: INTEGER; (* Delta time *)
+
+  PROCEDURE PrepareMouse;
+  BEGIN
+    mx := mouseX - mouseFocusX;
+    my := mouseY - mouseFocusY;
+    (* Save image under mouse from buffer *)
+    Blit(screen, underMouse, mx, my,
+      underMouse.w, underMouse.h, 0, 0);
+    (* Blit mouse pointer onto buffer *)
+    Blit(mousePointer, screen, 0, 0,
+      mousePointer.w, mousePointer.h, mx, my)
+  END PrepareMouse;
+
+  PROCEDURE CleanMouse;
+  BEGIN (* Restore image under mouse in buffer *)
+    Blit(underMouse, screen, 0, 0,
+      underMouse.w, underMouse.h, mx, my);
+    needRedrawMouse := FALSE
+  END CleanMouse;
+
+BEGIN
+  IF wantFPS # -1 THEN
+    IF lastFlip # -1 THEN
+      dt := 1000 DIV wantFPS - (GetTicks() - lastFlip);
+      IF (dt > 0) & (dt < 1000) THEN Delay(dt) END
+    END;
+    lastFlip := GetTicks()
+  END;
+  IF WindowShown() THEN
+    mx := 0; my := 0;
+    blitMouse := showMouse & MouseOnScreen();
+    IF blitMouse THEN PrepareMouse END;
+
+    (* Blit buffer on screen *)
+    SDL.SetRenderDrawColor(renderer, 0, 0, 0, 255);
+    SDL.RenderClear(renderer);
+    IF screenTexture # 0 THEN
+      SDL.DestroyTexture(screenTexture);
+      screenTexture := 0
+    END;
+    screenTexture := SDL.CreateTextureFromSurface(renderer, screen.surface);
+    SDL.RenderCopyNil(renderer, screenTexture);
+    SDL.RenderPresent(renderer);
+
+    IF blitMouse THEN CleanMouse END
+  END
+END Flip;
+
+(* Random *)
+
+PROCEDURE Time(): INTEGER;
+BEGIN
+RETURN SHORT(Platform.Time()) END Time;
+
+(* Set random seed value. Any values are allowed, although
+   values not in [1..2^31-2] will be mapped into this range. *)
+PROCEDURE PutSeed*(newSeed: INTEGER);
+BEGIN newSeed := newSeed MOD randomModulo;
+  IF newSeed = 0 THEN randomSeed := 1
+  ELSE randomSeed := newSeed
+  END
+END PutSeed;
+
+PROCEDURE NextRND;
+CONST a = 16807;
+  q = 127773; (* m div a *)
+  r = 2836;   (* m mod a *)
+VAR lo, hi, test: INTEGER;
+BEGIN
+  hi := randomSeed DIV q;
+  lo := randomSeed MOD q;
+  test := a * lo - r * hi;
+  IF test > 0 THEN randomSeed := test
+  ELSE randomSeed := test + randomModulo
+  END
+END NextRND;
+
+(* Calculates a new number. range has to be included in
+   [1..2^31-2]. Result is a number from 0, 1, ... , range-1. *)
+PROCEDURE Random*(range: INTEGER): INTEGER;
+BEGIN NextRND ;
+RETURN randomSeed MOD range END Random;
+
+(* Calculates a number x with 0.0 <= x < 1.0. *)
+PROCEDURE Uniform*(): REAL;
+BEGIN NextRND ;
+RETURN (randomSeed - 1) * (1 / (randomModulo - 1)) END Uniform;
+
+PROCEDURE Randomize*;
+BEGIN PutSeed(Time())
+END Randomize;
+
+(* Init *)
+
+PROCEDURE Init*(): Bitmap;
+VAR flags: SET; success: BOOLEAN; w, h, nw, nh: INTEGER;
+    s: ARRAY 2000 OF CHAR;
+BEGIN screen := NIL; settings := initSettings;
+  IF SDL.Init({SDL.initVideo}) = 0 THEN
+    flags := {};
+    IF fullscreen IN settings THEN
+      flags := flags + SDL.windowFullscreenDesktop;
+      IF (scrW <= 0) OR (scrH <= 0) THEN
+        GetDesktopResolution(scrW, scrH);
+        scrW := SHORT(ENTIER(scrW / scaleX));
+        scrH := SHORT(ENTIER(scrH / scaleY))
+      ELSIF spread IN settings THEN
+        GetDesktopResolution(w, h);
+        w := SHORT(ENTIER(w / scaleX)); h := SHORT(ENTIER(h / scaleY));
+        IF sharpPixels IN settings THEN
+          nw := w DIV scrW; nh := h DIV scrH;
+          IF nw < nh THEN scrW := w DIV nw; scrH := h DIV nw
+          ELSE scrW := w DIV nh; scrH := h DIV nh
+          END
+        END;
+        IF w / h > scrW / scrH THEN scrW := w * scrH DIV h
+        ELSE scrH := h * scrW DIV w
+        END
+      END
+    ELSIF (scrW <= 0) OR (scrH <= 0) THEN scrW := 640; scrH := 400
+    END;
+    IF sizeStepX # 1 THEN scrW := scrW DIV sizeStepX * sizeStepX END;
+    IF sizeStepY # 1 THEN scrH := scrH DIV sizeStepY * sizeStepY END;
+    window := SDL.CreateWindow('',
+      SDL.windowPosUndefined, SDL.windowPosUndefined,
+      scrW, scrH, flags);
+    IF window # 0 THEN
+      IF software IN settings THEN flags := {SDL.rendererSoftware}
+      ELSE flags := {SDL.rendererAccelerated}
+      END;
+      INCL(flags, SDL.rendererPresentVsync);
+      renderer := SDL.CreateRenderer(window, -1, flags);
+      IF sharpPixels IN settings THEN
+        SDL.SetHint(SDL.hintRenderScaleQuality, '0')
+      ELSE SDL.SetHint(SDL.hintRenderScaleQuality, '1')
+      END;
+      ApplyScale;
+      screen := CreateBitmap(scrW, scrH);
+      screenTexture := 0;
+      UnsetRegion;
+      SDL.ShowCursor(0);
+      IF initMouse IN settings THEN InitMouseData END;
+      IF {noPng, noJpg} - settings # {} THEN flags := {};
+        IF ~(noPng IN settings) THEN INCL(flags, SDL.imgInitPng) END;
+        IF ~(noJpg IN settings) THEN INCL(flags, SDL.imgInitJpg) END;
+        IF flags - SDL.ImgInit(flags) # {} THEN
+          Out.String('Could not initialize image format support.'); Out.Ln;
+          GetError(s); Out.String(s); Out.Ln
+        END
+      END;
+      keyPressed := 0;
+      lastFlip := -1;
+      Randomize
+    END
+  END ;
+RETURN screen END Init;
+
+PROCEDURE Close*;
+BEGIN
+  IF screenTexture # 0 THEN
+    SDL.DestroyTexture(screenTexture);
+    screenTexture := 0
+  END;
+  IF renderer # 0 THEN
+    SDL.DestroyRenderer(renderer);
+    renderer := 0
+  END;
+  SDL.Quit
+END Close;
+
+BEGIN
+  scrW := 640; scrH := 400;
+  sizeStepX := 1; sizeStepY := 1;
+  initSettings := {fullscreen, spread, sharpPixels};
+  renderer := 0; buffer := NIL; wantFPS := 60;
+  mousePointer := NIL; lastBlitMouseOutside := FALSE;
+  mouseFocusX := 0; mouseFocusY := 0;
+  scaleX := 1; scaleY := 1;
+  events.first := 0; events.last := -1; events.len := 0;
+  randomSeed := 1; keyPressed := 0
+END Graph.

+ 3 - 3
src/OV.Mod

@@ -575,7 +575,7 @@ TYPE
 
 
   ClickHandler* = PROCEDURE (c: Control);
   ClickHandler* = PROCEDURE (c: Control);
   ChangeHandler* = ClickHandler;
   ChangeHandler* = ClickHandler;
-  
+
   ControlDesc* = EXTENSIBLE RECORD
   ControlDesc* = EXTENSIBLE RECORD
     app*: App;
     app*: App;
     parent*, children*: Control;
     parent*, children*: Control;
@@ -823,7 +823,7 @@ PROCEDURE PutMarkedString*(x, y: INTEGER; s: ARRAY OF CHAR; fg, fg2, bg, limit:
 VAR i, c: INTEGER;
 VAR i, c: INTEGER;
   mark: BOOLEAN;
   mark: BOOLEAN;
 BEGIN
 BEGIN
-  IF limit = 0 THEN limit := T.charsX END; 
+  IF limit = 0 THEN limit := T.charsX END;
   i := 0; mark := FALSE;
   i := 0; mark := FALSE;
   WHILE (i < LEN(s)) & (s[i] # 0X) & (x <= limit) DO
   WHILE (i < LEN(s)) & (s[i] # 0X) & (x <= limit) DO
     IF s[i] = '&' THEN mark := TRUE
     IF s[i] = '&' THEN mark := TRUE
@@ -1083,7 +1083,7 @@ BEGIN
     T.PutChar(x - 1, y, 0C3X, 0, 7);
     T.PutChar(x - 1, y, 0C3X, 0, 7);
     T.PutChar(x + w + 2, y, 0B4X, 0, 7)
     T.PutChar(x + w + 2, y, 0B4X, 0, 7)
   ELSE
   ELSE
-    IF (m.status = selected) OR (m.status = open) THEN bg := 2 
+    IF (m.status = selected) OR (m.status = open) THEN bg := 2
     ELSE bg := 7
     ELSE bg := 7
     END;
     END;
     T.PutChar(x, y, ' ', 0, bg); INC(x);
     T.PutChar(x, y, ' ', 0, bg); INC(x);

+ 53 - 53
src/Term.Mod

@@ -1,53 +1,53 @@
-MODULE Term;
-(* Copyright 2017-2021 Arthur Yefimov
-
-This file is part of Free Oberon.
-
-Free Oberon is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Free Oberon is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
-*)
-IMPORT SYSTEM;
-
-TYPE CHAR = SHORTCHAR;
-
-PROCEDURE -AAIncludeTermh* '#include "term/term.h"';
-
-PROCEDURE -StartProcess*
-  (cmd: ARRAY OF CHAR): BOOLEAN
-  "StartProcess(cmd)";
-
-PROCEDURE -StartProcessIn*
-  (cmd, dir: ARRAY OF CHAR): BOOLEAN
-  "StartProcessIn(cmd, dir)";
-
-PROCEDURE -ProcessFinished*(VAR err: INTEGER): BOOLEAN
-  "ProcessFinished(err)";
-
-PROCEDURE -WriteToProcess*
-  (buf: ARRAY OF CHAR; len: INTEGER)
-  "WriteToProcess(buf, len)";
-
-PROCEDURE -ReadFromProcess*
-  (VAR buf: ARRAY OF CHAR; VAR len: INTEGER; limit: INTEGER)
-  "ReadFromProcess(buf, len, limit)";
-
-PROCEDURE -RunProcess*
-  (cmd: ARRAY OF CHAR; VAR buf: ARRAY OF CHAR;
-  limit: INTEGER; VAR len, err: INTEGER): INTEGER
-  "(int)RunProcess((char *)cmd, (char *)buf, limit, len, err)";
-
-PROCEDURE -SearchPath*
-  (filename: ARRAY OF CHAR; VAR result: ARRAY OF CHAR): LONGINT
-  "(int)MySearchPath((char *)filename, (char *)result, result__len)";
-
-END Term.
+MODULE Term;
+(* Copyright 2017-2021 Arthur Yefimov
+
+This file is part of Free Oberon.
+
+Free Oberon is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Free Oberon is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT SYSTEM;
+
+TYPE CHAR = SHORTCHAR;
+
+PROCEDURE -AAIncludeTermh* '#include "term/term.h"';
+
+PROCEDURE -StartProcess*
+  (cmd: ARRAY OF CHAR): BOOLEAN
+  "StartProcess(cmd)";
+
+PROCEDURE -StartProcessIn*
+  (cmd, dir: ARRAY OF CHAR): BOOLEAN
+  "StartProcessIn(cmd, dir)";
+
+PROCEDURE -ProcessFinished*(VAR err: INTEGER): BOOLEAN
+  "ProcessFinished(err)";
+
+PROCEDURE -WriteToProcess*
+  (buf: ARRAY OF CHAR; len: INTEGER)
+  "WriteToProcess(buf, len)";
+
+PROCEDURE -ReadFromProcess*
+  (VAR buf: ARRAY OF CHAR; VAR len: INTEGER; limit: INTEGER)
+  "ReadFromProcess(buf, len, limit)";
+
+PROCEDURE -RunProcess*
+  (cmd: ARRAY OF CHAR; VAR buf: ARRAY OF CHAR;
+  limit: INTEGER; VAR len, err: INTEGER): INTEGER
+  "(int)RunProcess((char *)cmd, (char *)buf, limit, len, err)";
+
+PROCEDURE -SearchPath*
+  (filename: ARRAY OF CHAR; VAR result: ARRAY OF CHAR): LONGINT
+  "(int)MySearchPath((char *)filename, (char *)result, result__len)";
+
+END Term.

+ 356 - 356
src/Terminal.Mod

@@ -1,356 +1,356 @@
-MODULE Terminal;
-(* Copyright 2017-2021 Arthur Yefimov
-
-This file is part of Free Oberon.
-
-Free Oberon is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Free Oberon is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
-*)
-IMPORT G := Graph, Out;
-CONST
-  charW* = 8; charH* = 16;
-  cursorTicks* = 10; (* Ticks before cursor flashes *)
-
-TYPE
-  CHAR = SHORTCHAR;
-  ScreenChar* = RECORD
-    ch*: CHAR;
-    fg*, bg*: INTEGER; (* Цвет текста и цвет фона*)
-    updated*: BOOLEAN
-  END;
-  ScreenChars* = POINTER TO ARRAY OF ARRAY OF ScreenChar;
-
-VAR
-  screen*: G.Bitmap;
-  charsX-, charsY-: INTEGER;
-  font: G.Font;
-
-  chars: ScreenChars;
-  cursorX-, cursorY-: INTEGER;
-  cursorOn-, cursorShown: BOOLEAN;
-  insertCursor: BOOLEAN;
-  cursorTick: INTEGER; (* To show and hide cursor all the time *)
-  mouseX-, mouseY-: INTEGER; (* In chars *)
-  needRedraw: BOOLEAN;
-
-  isFullscreen-: BOOLEAN;
-  pixelPerfect-: BOOLEAN; (* TRUE if G.Settings width or height was zero *)
-
-PROCEDURE Redraw*;
-BEGIN needRedraw := TRUE
-END Redraw;
-
-PROCEDURE SetScale;
-BEGIN
-  IF isFullscreen & ~pixelPerfect THEN G.SetScale(5/6, 1)
-  ELSE G.SetScale(1, 1)
-  END
-END SetScale;
-
-PROCEDURE ToggleFullscreen*;
-BEGIN
-  IF isFullscreen THEN G.SwitchToWindowed ELSE G.SwitchToFullscreen END;
-  isFullscreen := ~isFullscreen;
-  SetScale; Redraw
-END ToggleFullscreen;
-
-PROCEDURE ExpandColor*(color: INTEGER): INTEGER;
-VAR r, g, b: INTEGER;
-BEGIN
-  CASE color OF
-     0: b :=   0; g :=   0; r :=   0
-  |  1: b :=  90; g :=   0; r :=   0
-  |  2: b :=   0; g := 176; r :=   0
-  |  3: b := 176; g := 176; r :=   0
-  |  4: b :=   0; g :=   0; r := 176
-  |  5: b := 176; g :=   0; r := 176
-  |  6: b :=   0; g :=  85; r := 176
-  |  7: b := 176; g := 176; r := 176
-  |  8: b :=  85; g :=  85; r :=  85
-  |  9: b := 255; g :=  85; r :=  85
-  | 10: b :=  85; g := 255; r :=  85
-  | 11: b := 255; g := 255; r :=  85
-  | 12: b :=  85; g :=  85; r := 255
-  | 13: b := 255; g :=  85; r := 255
-  | 14: b :=  85; g := 255; r := 255
-  | 15: b := 255; g := 255; r := 255
-  ELSE  b :=  85; g :=   0; r := 255
-  END ;
-RETURN G.MakeCol(r, g, b) END ExpandColor;
-
-PROCEDURE InvertColor*(color: INTEGER): INTEGER;
-BEGIN
-  ASSERT((color >= 0) & (color < 16), 59);
-  IF color >= 8 THEN color := color - 8 END; (* Darken *)
-  color := 7 - color ; (* Invert *)
-RETURN color END InvertColor;
-
-PROCEDURE DrawChar*(ch: CHAR; x, y, fg, bg: INTEGER);
-VAR color: INTEGER;
-BEGIN
-  G.RectFill(screen, x, y, x + charW - 1,
-    y + charH - 1, ExpandColor(bg));
-  IF ch # ' ' THEN
-    G.DrawCharacter(screen, font, x, y, ch, ExpandColor(fg))
-  END
-END DrawChar;
-
-PROCEDURE DrawMouse;
-VAR x, y, bg, fg: INTEGER; ch: CHAR;
-BEGIN
-  IF (mouseX >= 0) & (mouseX < charsX) &
-     (mouseY >= 0) & (mouseY < charsY) THEN
-    bg := InvertColor(chars[mouseY, mouseX].bg);
-    fg := InvertColor(chars[mouseY, mouseX].fg);
-    ch := chars[mouseY, mouseX].ch
-  ELSE bg := 6; fg := 0; ch := ' ' END;
-  x := mouseX * charW; y := mouseY * charH;
-  DrawChar(ch, x, y, fg, bg)
-END DrawMouse;
-
-(* Draws characters that have been changed, and the mouse.
-   Returns TRUE if anything has been drawn. *)
-PROCEDURE Draw*(): BOOLEAN;
-VAR x, y, color: INTEGER;
-  drawn: BOOLEAN;
-BEGIN
-  drawn := needRedraw;
-  IF needRedraw THEN
-    needRedraw := FALSE;
-    (* Chars *)
-    FOR y := 0 TO charsY - 1 DO
-      FOR x := 0 TO charsX - 1 DO
-        IF chars[y, x].updated THEN
-          chars[y, x].updated := FALSE;
-          DrawChar(chars[y, x].ch, x * charW, y * charH,
-            chars[y, x].fg, chars[y, x].bg)
-        END
-      END
-    END;
-    (* Text Cursor *)
-    IF cursorShown THEN
-      color := ExpandColor(chars[cursorY, cursorX].fg);
-      x := cursorX * charW; y := cursorY * charH;
-      IF insertCursor THEN
-        G.RectFill(screen, x, y, x + charW - 1, y + charH - 1, color) (*!FIXME*)
-      ELSE
-        INC(y, charH);
-        G.RectFill(screen, x, y - 2, x + charW - 1, y - 1, color)
-      END
-    END;
-    DrawMouse
-  END ;
-RETURN drawn END Draw;
-
-PROCEDURE Act*;
-BEGIN
-  IF cursorOn THEN (* Cursor blink: *)
-    IF cursorTick >= cursorTicks THEN
-      needRedraw := TRUE;
-      IF cursorShown THEN chars[cursorY, cursorX].updated := TRUE END;
-      cursorTick := 0; cursorShown := ~cursorShown
-    ELSE INC(cursorTick)
-    END
-  END
-END Act;
-
-PROCEDURE ResetCursorBlink*;
-BEGIN
-  IF cursorOn THEN
-    needRedraw := TRUE;
-    chars[cursorY, cursorX].updated := TRUE;
-    cursorTick := 0; cursorShown := TRUE
-  END
-END ResetCursorBlink;
-
-PROCEDURE ShowCursor*(show: BOOLEAN);
-BEGIN
-  IF cursorOn # show THEN
-    cursorOn := show; cursorShown := show;
-    chars[cursorY, cursorX].updated := TRUE;
-    needRedraw := TRUE
-  END
-END ShowCursor;
-
-PROCEDURE GoToXY*(x, y: INTEGER);
-BEGIN needRedraw := TRUE;
-  IF x < 0 THEN x := 0 ELSIF x >= charsX THEN x := charsX - 1 END;
-  IF y < 0 THEN y := 0 ELSIF y >= charsY THEN y := charsY - 1 END;
-  chars[cursorY, cursorX].updated := TRUE;
-  cursorX := x; cursorY := y
-END GoToXY;
-
-PROCEDURE MouseXY*(x, y: INTEGER);
-BEGIN needRedraw := TRUE;
-  chars[mouseY, mouseX].updated := TRUE;
-  mouseX := x; mouseY := y;
-  chars[mouseY, mouseX].updated := TRUE
-END MouseXY;
-
-PROCEDURE ResizeScreen;
-BEGIN NEW(chars, charsY, charsX)
-END ResizeScreen;
-
-PROCEDURE SetCharColor*(x, y: INTEGER; fg, bg: INTEGER);
-BEGIN
-  IF (x >= 0) & (y >= 0) & (x < charsX) & (y < charsY) &
-     ((chars[y, x].fg # fg) OR
-      (chars[y, x].bg # bg)) THEN
-    chars[y, x].fg := fg;
-    chars[y, x].bg := bg;
-    chars[y, x].updated := TRUE;
-    needRedraw := TRUE
-  END
-END SetCharColor;
-
-PROCEDURE PutChar*(x, y: INTEGER; ch: CHAR; fg, bg: INTEGER);
-BEGIN
-  IF (x >= 0) & (y >= 0) & (x < charsX) & (y < charsY) &
-     ((chars[y, x].ch # ch) OR
-      (chars[y, x].fg # fg) OR
-      (chars[y, x].bg # bg) OR
-      (cursorX = x) & (cursorY = y)) THEN
-    chars[y, x].ch := ch;
-    chars[y, x].fg := fg;
-    chars[y, x].bg := bg;
-    chars[y, x].updated := TRUE;
-    needRedraw := TRUE
-  END
-END PutChar;
-
-PROCEDURE PutString*(x, y: INTEGER; s: ARRAY OF CHAR; fg, bg, limit: INTEGER);
-VAR i: INTEGER;
-BEGIN
-  IF limit = 0 THEN limit := charsX END; 
-  i := 0;
-  WHILE (i < LEN(s)) & (s[i] # 0X) & (x <= limit) DO
-    PutChar(x, y, s[i], fg, bg);
-    INC(i); INC(x)
-  END
-END PutString;
-
-PROCEDURE CharFill*(x, y, w, h: INTEGER; ch: CHAR; fg, bg: INTEGER);
-VAR X, Y: INTEGER;
-BEGIN
-  FOR Y := y TO y + h - 1 DO
-    FOR X := x TO x + w - 1 DO
-      PutChar(X, Y, ch, fg, bg)
-    END
-  END
-END CharFill;
-
-PROCEDURE ClearScreen*;
-VAR x, y: INTEGER;
-BEGIN needRedraw := TRUE;
-  FOR y := 0 TO charsY - 1 DO
-    FOR x := 0 TO charsX - 1 DO
-      IF (chars[y, x].ch # ' ') OR
-         (chars[y, x].fg # 7) OR
-         (chars[y, x].bg # 0) THEN
-        chars[y, x].ch := ' ';
-        chars[y, x].fg := 7;
-        chars[y, x].bg := 0;
-        chars[y, x].updated := TRUE
-      END
-    END
-  END
-END ClearScreen;
-
-PROCEDURE ScrollScreen(lines: INTEGER);
-VAR x, y: INTEGER;
-BEGIN needRedraw := TRUE;
-  FOR y := 0 TO charsY - 1 - lines DO
-    FOR x := 0 TO charsX - 1 DO
-      chars[y, x] := chars[y + lines, x];
-      chars[y, x].updated := TRUE
-    END
-  END;
-  CharFill(0, charsY - lines, charsX, lines, ' ', 7, 0) (*!FIXME colors*)
-END ScrollScreen;
-
-PROCEDURE Ln*;
-BEGIN needRedraw := TRUE;
-  chars[cursorY, cursorX].updated := TRUE;
-  cursorX := 0;
-  IF cursorY = charsY - 1 THEN ScrollScreen(1)
-  ELSE INC(cursorY) END
-END Ln;
-
-PROCEDURE Backspace*;
-BEGIN
-  needRedraw := TRUE;
-  chars[cursorY, cursorX].updated := TRUE;
-  IF cursorX # 0 THEN DEC(cursorX)
-  ELSIF cursorY # 0 THEN cursorX := charsX - 1; DEC(cursorY)
-  END;
-  chars[cursorY, cursorX].ch := ' ';
-  chars[cursorY, cursorX].updated := TRUE
-END Backspace;
-
-PROCEDURE Write*(ch: CHAR);
-BEGIN needRedraw := TRUE;
-  IF ch = 0AX THEN Ln
-  ELSIF ch # 0DX THEN
-    PutChar(cursorX, cursorY, ch, 7, 0);
-    IF cursorX = charsX - 1 THEN Ln
-    ELSE INC(cursorX) END
-  END
-END Write;
-
-PROCEDURE WriteString*(s: ARRAY OF CHAR);
-VAR i: INTEGER;
-BEGIN i := 0; needRedraw := TRUE;
-  IF cursorShown THEN chars[cursorY, cursorX].updated := TRUE END;
-  WHILE s[i] # 0X DO Write(s[i]); INC(i) END
-END WriteString;
-
-PROCEDURE LoadMedia(): BOOLEAN;
-CONST fontFile = 'data/images/font.bmp';
-BEGIN font := G.LoadFont(fontFile, charW, charH);
-  IF font = NIL THEN Out.String('Could not load font file "');
-    Out.String(fontFile); Out.String('".'); Out.Ln
-  END ;
-RETURN font # NIL END LoadMedia;
-
-PROCEDURE Init*(fullscreen, software: BOOLEAN; w, h: INTEGER): BOOLEAN;
-VAR success: BOOLEAN; settings: SET;
-BEGIN success := FALSE; isFullscreen := fullscreen;
-  settings := {G.buffered, G.initMouse, G.spread, G.noJpg};
-  IF fullscreen THEN INCL(settings, G.fullscreen) END;
-  IF software THEN INCL(settings, G.software) END;
-  IF w < 0 THEN w := 107 ELSIF (w # 0) & (w < 10) THEN w := 20 END;
-  IF h < 0 THEN h := 25 ELSIF (h # 0) & (h < 10) THEN h := 10 END;
-  pixelPerfect := (w = 0) OR (h = 0);
-  G.Settings(w * 8, h * 16, settings);
-  SetScale;
-  G.SetSizeStep(charW, charH);
-  screen := G.Init();
-  IF screen # NIL THEN
-    G.SetWindowTitle('Free Oberon');
-    G.ShowMouse(FALSE);
-    charsX := screen.w DIV charW;
-    charsY := screen.h DIV charH;
-    IF LoadMedia() THEN
-      success := TRUE;
-      needRedraw := TRUE;
-      insertCursor := FALSE;
-      cursorOn := FALSE;
-      cursorShown := FALSE;
-      cursorX := 1; cursorY := 2;
-      mouseX := 0; mouseY := 0;
-      ResizeScreen
-    END
-  END ;
-RETURN success END Init;
-
-END Terminal.
+MODULE Terminal;
+(* Copyright 2017-2021 Arthur Yefimov
+
+This file is part of Free Oberon.
+
+Free Oberon is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Free Oberon is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT G := Graph, Out;
+CONST
+  charW* = 8; charH* = 16;
+  cursorTicks* = 10; (* Ticks before cursor flashes *)
+
+TYPE
+  CHAR = SHORTCHAR;
+  ScreenChar* = RECORD
+    ch*: CHAR;
+    fg*, bg*: INTEGER; (* Цвет текста и цвет фона*)
+    updated*: BOOLEAN
+  END;
+  ScreenChars* = POINTER TO ARRAY OF ARRAY OF ScreenChar;
+
+VAR
+  screen*: G.Bitmap;
+  charsX-, charsY-: INTEGER;
+  font: G.Font;
+
+  chars: ScreenChars;
+  cursorX-, cursorY-: INTEGER;
+  cursorOn-, cursorShown: BOOLEAN;
+  insertCursor: BOOLEAN;
+  cursorTick: INTEGER; (* To show and hide cursor all the time *)
+  mouseX-, mouseY-: INTEGER; (* In chars *)
+  needRedraw: BOOLEAN;
+
+  isFullscreen-: BOOLEAN;
+  pixelPerfect-: BOOLEAN; (* TRUE if G.Settings width or height was zero *)
+
+PROCEDURE Redraw*;
+BEGIN needRedraw := TRUE
+END Redraw;
+
+PROCEDURE SetScale;
+BEGIN
+  IF isFullscreen & ~pixelPerfect THEN G.SetScale(5/6, 1)
+  ELSE G.SetScale(1, 1)
+  END
+END SetScale;
+
+PROCEDURE ToggleFullscreen*;
+BEGIN
+  IF isFullscreen THEN G.SwitchToWindowed ELSE G.SwitchToFullscreen END;
+  isFullscreen := ~isFullscreen;
+  SetScale; Redraw
+END ToggleFullscreen;
+
+PROCEDURE ExpandColor*(color: INTEGER): INTEGER;
+VAR r, g, b: INTEGER;
+BEGIN
+  CASE color OF
+     0: b :=   0; g :=   0; r :=   0
+  |  1: b :=  90; g :=   0; r :=   0
+  |  2: b :=   0; g := 176; r :=   0
+  |  3: b := 176; g := 176; r :=   0
+  |  4: b :=   0; g :=   0; r := 176
+  |  5: b := 176; g :=   0; r := 176
+  |  6: b :=   0; g :=  85; r := 176
+  |  7: b := 176; g := 176; r := 176
+  |  8: b :=  85; g :=  85; r :=  85
+  |  9: b := 255; g :=  85; r :=  85
+  | 10: b :=  85; g := 255; r :=  85
+  | 11: b := 255; g := 255; r :=  85
+  | 12: b :=  85; g :=  85; r := 255
+  | 13: b := 255; g :=  85; r := 255
+  | 14: b :=  85; g := 255; r := 255
+  | 15: b := 255; g := 255; r := 255
+  ELSE  b :=  85; g :=   0; r := 255
+  END ;
+RETURN G.MakeCol(r, g, b) END ExpandColor;
+
+PROCEDURE InvertColor*(color: INTEGER): INTEGER;
+BEGIN
+  ASSERT((color >= 0) & (color < 16), 59);
+  IF color >= 8 THEN color := color - 8 END; (* Darken *)
+  color := 7 - color ; (* Invert *)
+RETURN color END InvertColor;
+
+PROCEDURE DrawChar*(ch: CHAR; x, y, fg, bg: INTEGER);
+VAR color: INTEGER;
+BEGIN
+  G.RectFill(screen, x, y, x + charW - 1,
+    y + charH - 1, ExpandColor(bg));
+  IF ch # ' ' THEN
+    G.DrawCharacter(screen, font, x, y, ch, ExpandColor(fg))
+  END
+END DrawChar;
+
+PROCEDURE DrawMouse;
+VAR x, y, bg, fg: INTEGER; ch: CHAR;
+BEGIN
+  IF (mouseX >= 0) & (mouseX < charsX) &
+     (mouseY >= 0) & (mouseY < charsY) THEN
+    bg := InvertColor(chars[mouseY, mouseX].bg);
+    fg := InvertColor(chars[mouseY, mouseX].fg);
+    ch := chars[mouseY, mouseX].ch
+  ELSE bg := 6; fg := 0; ch := ' ' END;
+  x := mouseX * charW; y := mouseY * charH;
+  DrawChar(ch, x, y, fg, bg)
+END DrawMouse;
+
+(* Draws characters that have been changed, and the mouse.
+   Returns TRUE if anything has been drawn. *)
+PROCEDURE Draw*(): BOOLEAN;
+VAR x, y, color: INTEGER;
+  drawn: BOOLEAN;
+BEGIN
+  drawn := needRedraw;
+  IF needRedraw THEN
+    needRedraw := FALSE;
+    (* Chars *)
+    FOR y := 0 TO charsY - 1 DO
+      FOR x := 0 TO charsX - 1 DO
+        IF chars[y, x].updated THEN
+          chars[y, x].updated := FALSE;
+          DrawChar(chars[y, x].ch, x * charW, y * charH,
+            chars[y, x].fg, chars[y, x].bg)
+        END
+      END
+    END;
+    (* Text Cursor *)
+    IF cursorShown THEN
+      color := ExpandColor(chars[cursorY, cursorX].fg);
+      x := cursorX * charW; y := cursorY * charH;
+      IF insertCursor THEN
+        G.RectFill(screen, x, y, x + charW - 1, y + charH - 1, color) (*!FIXME*)
+      ELSE
+        INC(y, charH);
+        G.RectFill(screen, x, y - 2, x + charW - 1, y - 1, color)
+      END
+    END;
+    DrawMouse
+  END ;
+RETURN drawn END Draw;
+
+PROCEDURE Act*;
+BEGIN
+  IF cursorOn THEN (* Cursor blink: *)
+    IF cursorTick >= cursorTicks THEN
+      needRedraw := TRUE;
+      IF cursorShown THEN chars[cursorY, cursorX].updated := TRUE END;
+      cursorTick := 0; cursorShown := ~cursorShown
+    ELSE INC(cursorTick)
+    END
+  END
+END Act;
+
+PROCEDURE ResetCursorBlink*;
+BEGIN
+  IF cursorOn THEN
+    needRedraw := TRUE;
+    chars[cursorY, cursorX].updated := TRUE;
+    cursorTick := 0; cursorShown := TRUE
+  END
+END ResetCursorBlink;
+
+PROCEDURE ShowCursor*(show: BOOLEAN);
+BEGIN
+  IF cursorOn # show THEN
+    cursorOn := show; cursorShown := show;
+    chars[cursorY, cursorX].updated := TRUE;
+    needRedraw := TRUE
+  END
+END ShowCursor;
+
+PROCEDURE GoToXY*(x, y: INTEGER);
+BEGIN needRedraw := TRUE;
+  IF x < 0 THEN x := 0 ELSIF x >= charsX THEN x := charsX - 1 END;
+  IF y < 0 THEN y := 0 ELSIF y >= charsY THEN y := charsY - 1 END;
+  chars[cursorY, cursorX].updated := TRUE;
+  cursorX := x; cursorY := y
+END GoToXY;
+
+PROCEDURE MouseXY*(x, y: INTEGER);
+BEGIN needRedraw := TRUE;
+  chars[mouseY, mouseX].updated := TRUE;
+  mouseX := x; mouseY := y;
+  chars[mouseY, mouseX].updated := TRUE
+END MouseXY;
+
+PROCEDURE ResizeScreen;
+BEGIN NEW(chars, charsY, charsX)
+END ResizeScreen;
+
+PROCEDURE SetCharColor*(x, y: INTEGER; fg, bg: INTEGER);
+BEGIN
+  IF (x >= 0) & (y >= 0) & (x < charsX) & (y < charsY) &
+     ((chars[y, x].fg # fg) OR
+      (chars[y, x].bg # bg)) THEN
+    chars[y, x].fg := fg;
+    chars[y, x].bg := bg;
+    chars[y, x].updated := TRUE;
+    needRedraw := TRUE
+  END
+END SetCharColor;
+
+PROCEDURE PutChar*(x, y: INTEGER; ch: CHAR; fg, bg: INTEGER);
+BEGIN
+  IF (x >= 0) & (y >= 0) & (x < charsX) & (y < charsY) &
+     ((chars[y, x].ch # ch) OR
+      (chars[y, x].fg # fg) OR
+      (chars[y, x].bg # bg) OR
+      (cursorX = x) & (cursorY = y)) THEN
+    chars[y, x].ch := ch;
+    chars[y, x].fg := fg;
+    chars[y, x].bg := bg;
+    chars[y, x].updated := TRUE;
+    needRedraw := TRUE
+  END
+END PutChar;
+
+PROCEDURE PutString*(x, y: INTEGER; s: ARRAY OF CHAR; fg, bg, limit: INTEGER);
+VAR i: INTEGER;
+BEGIN
+  IF limit = 0 THEN limit := charsX END;
+  i := 0;
+  WHILE (i < LEN(s)) & (s[i] # 0X) & (x <= limit) DO
+    PutChar(x, y, s[i], fg, bg);
+    INC(i); INC(x)
+  END
+END PutString;
+
+PROCEDURE CharFill*(x, y, w, h: INTEGER; ch: CHAR; fg, bg: INTEGER);
+VAR X, Y: INTEGER;
+BEGIN
+  FOR Y := y TO y + h - 1 DO
+    FOR X := x TO x + w - 1 DO
+      PutChar(X, Y, ch, fg, bg)
+    END
+  END
+END CharFill;
+
+PROCEDURE ClearScreen*;
+VAR x, y: INTEGER;
+BEGIN needRedraw := TRUE;
+  FOR y := 0 TO charsY - 1 DO
+    FOR x := 0 TO charsX - 1 DO
+      IF (chars[y, x].ch # ' ') OR
+         (chars[y, x].fg # 7) OR
+         (chars[y, x].bg # 0) THEN
+        chars[y, x].ch := ' ';
+        chars[y, x].fg := 7;
+        chars[y, x].bg := 0;
+        chars[y, x].updated := TRUE
+      END
+    END
+  END
+END ClearScreen;
+
+PROCEDURE ScrollScreen(lines: INTEGER);
+VAR x, y: INTEGER;
+BEGIN needRedraw := TRUE;
+  FOR y := 0 TO charsY - 1 - lines DO
+    FOR x := 0 TO charsX - 1 DO
+      chars[y, x] := chars[y + lines, x];
+      chars[y, x].updated := TRUE
+    END
+  END;
+  CharFill(0, charsY - lines, charsX, lines, ' ', 7, 0) (*!FIXME colors*)
+END ScrollScreen;
+
+PROCEDURE Ln*;
+BEGIN needRedraw := TRUE;
+  chars[cursorY, cursorX].updated := TRUE;
+  cursorX := 0;
+  IF cursorY = charsY - 1 THEN ScrollScreen(1)
+  ELSE INC(cursorY) END
+END Ln;
+
+PROCEDURE Backspace*;
+BEGIN
+  needRedraw := TRUE;
+  chars[cursorY, cursorX].updated := TRUE;
+  IF cursorX # 0 THEN DEC(cursorX)
+  ELSIF cursorY # 0 THEN cursorX := charsX - 1; DEC(cursorY)
+  END;
+  chars[cursorY, cursorX].ch := ' ';
+  chars[cursorY, cursorX].updated := TRUE
+END Backspace;
+
+PROCEDURE Write*(ch: CHAR);
+BEGIN needRedraw := TRUE;
+  IF ch = 0AX THEN Ln
+  ELSIF ch # 0DX THEN
+    PutChar(cursorX, cursorY, ch, 7, 0);
+    IF cursorX = charsX - 1 THEN Ln
+    ELSE INC(cursorX) END
+  END
+END Write;
+
+PROCEDURE WriteString*(s: ARRAY OF CHAR);
+VAR i: INTEGER;
+BEGIN i := 0; needRedraw := TRUE;
+  IF cursorShown THEN chars[cursorY, cursorX].updated := TRUE END;
+  WHILE s[i] # 0X DO Write(s[i]); INC(i) END
+END WriteString;
+
+PROCEDURE LoadMedia(): BOOLEAN;
+CONST fontFile = 'data/images/font.bmp';
+BEGIN font := G.LoadFont(fontFile, charW, charH);
+  IF font = NIL THEN Out.String('Could not load font file "');
+    Out.String(fontFile); Out.String('".'); Out.Ln
+  END ;
+RETURN font # NIL END LoadMedia;
+
+PROCEDURE Init*(fullscreen, software: BOOLEAN; w, h: INTEGER): BOOLEAN;
+VAR success: BOOLEAN; settings: SET;
+BEGIN success := FALSE; isFullscreen := fullscreen;
+  settings := {G.buffered, G.initMouse, G.spread, G.noJpg};
+  IF fullscreen THEN INCL(settings, G.fullscreen) END;
+  IF software THEN INCL(settings, G.software) END;
+  IF w < 0 THEN w := 107 ELSIF (w # 0) & (w < 10) THEN w := 20 END;
+  IF h < 0 THEN h := 25 ELSIF (h # 0) & (h < 10) THEN h := 10 END;
+  pixelPerfect := (w = 0) OR (h = 0);
+  G.Settings(w * 8, h * 16, settings);
+  SetScale;
+  G.SetSizeStep(charW, charH);
+  screen := G.Init();
+  IF screen # NIL THEN
+    G.SetWindowTitle('Free Oberon');
+    G.ShowMouse(FALSE);
+    charsX := screen.w DIV charW;
+    charsY := screen.h DIV charH;
+    IF LoadMedia() THEN
+      success := TRUE;
+      needRedraw := TRUE;
+      insertCursor := FALSE;
+      cursorOn := FALSE;
+      cursorShown := FALSE;
+      cursorX := 1; cursorY := 2;
+      mouseX := 0; mouseY := 0;
+      ResizeScreen
+    END
+  END ;
+RETURN success END Init;
+
+END Terminal.