Преглед изворни кода

Merge branch 'main' of github.com:kekcleader/FreeOberon

Arthur Yefimov пре 2 година
родитељ
комит
8beb46d30f
13 измењених фајлова са 1279 додато и 38 уклоњено
  1. 0 1
      .gitignore
  2. 16 0
      Programs/TestSound.Mod
  3. 44 10
      src/Allegro5.Mod
  4. 13 7
      src/Builder.Mod
  5. 1 1
      src/Editor.Mod
  6. 8 0
      src/FreeOberon.Mod
  7. 46 7
      src/Graph.Mod
  8. 8 0
      src/Makefile
  9. 1061 0
      src/Programs/FreeOberon.Mod
  10. 63 0
      src/Sound.Mod
  11. 7 8
      src/TermBox.Mod
  12. 6 2
      src/make.bat
  13. 6 2
      src/make.sh

+ 0 - 1
.gitignore

@@ -6,7 +6,6 @@ Autodoc.exe
 Autodoc
 Autodoc
 !Autodoc/
 !Autodoc/
 _Build
 _Build
-/src/Makefile
 /src/*.o
 /src/*.o
 /src/*.h
 /src/*.h
 /src/*.oh
 /src/*.oh

+ 16 - 0
Programs/TestSound.Mod

@@ -0,0 +1,16 @@
+MODULE TestSound;
+IMPORT G := Graph, Sound, Out;
+VAR c: G.Color;
+BEGIN
+  G.Settings(320, 200, {G.window, G.center});
+  G.Init;
+  Sound.Init;
+  IF ~Sound.Done THEN Out.String('SOUND INIT FAILED.'); Out.Ln END;
+  G.MakeCol(c, 240, 10, 0);
+  G.ClearToColor(c);
+  G.Flip;
+  Sound.Sound(412);
+  G.Delay(1000);
+  Sound.Close;
+  G.Close
+END TestSound.

+ 44 - 10
src/Allegro5.Mod

@@ -2,7 +2,7 @@ MODULE [foreign] Allegro5;
 IMPORT SYSTEM;
 IMPORT SYSTEM;
 
 
 CONST
 CONST
-  (** Values of al_set_new_display_flags *)
+  (** Values of al_set_new_display_flags **)
   windowed*                = 0;
   windowed*                = 0;
   fullscreen*              = 1;
   fullscreen*              = 1;
   opengl*                  = 2;
   opengl*                  = 2;
@@ -19,12 +19,12 @@ CONST
   maximized*               = 13;
   maximized*               = 13;
   openglEsProfile*         = 14;
   openglEsProfile*         = 14;
 
 
-  (** Values of parameter importance in al_set_new_display_option *)
+  (** Values of parameter importance in al_set_new_display_option **)
   dontcare* = 0;
   dontcare* = 0;
   require*  = 1;
   require*  = 1;
   suggest*  = 2;
   suggest*  = 2;
 
 
-  (** Bitmap Flags *)
+  (** Bitmap Flags **)
   memoryBitmap*         = 0;
   memoryBitmap*         = 0;
   keepBitmapFormat*     = 1;
   keepBitmapFormat*     = 1;
   forceLocking*         = 2;
   forceLocking*         = 2;
@@ -38,7 +38,7 @@ CONST
   videoBitmap*          = 10;
   videoBitmap*          = 10;
   convertBitmap*        = 12;
   convertBitmap*        = 12;
 
 
-  (** Values of parameter option in al_set_new_display_option *)
+  (** Values of parameter option in al_set_new_display_option **)
   redSize*               = 0;
   redSize*               = 0;
   greenSize*             = 1;
   greenSize*             = 1;
   blueSize*              = 2;
   blueSize*              = 2;
@@ -72,11 +72,38 @@ CONST
   supportSeparateAlpha*  = 30;
   supportSeparateAlpha*  = 30;
   autoConvertBitmaps*    = 31;
   autoConvertBitmaps*    = 31;
   supportedOrientations* = 32;
   supportedOrientations* = 32;
-  openglMajorVersion*    = 33;
+    openglMajorVersion*    = 33;
   openglMinorVersion*    = 34;
   openglMinorVersion*    = 34;
   displayOptionsCount*   = 35;
   displayOptionsCount*   = 35;
 
 
-  (** Values of Event.type *)
+  (** Audio Constants **)
+  audioDepthInt8*    = 0;
+  audioDepthInt16*   = 1;
+  audioDepthInt24    = 2;
+  audioDepthFloat32* = 3;
+
+  audioDepthUint8*   = 8;
+  audioDepthUint16*  = 9;
+  audioDepthUint24   = 10;
+
+  channelConf1*  = 10H;
+  channelConf2*  = 20H;
+  channelConf3*  = 30H;
+  channelConf4*  = 40H;
+  channelConf51* = 51H;
+  channelConf61* = 61H;
+  channelConf71* = 71H;
+
+  playmodeOnce*      = 100H;
+  playmodeLoop*      = 101H;
+  playmodeBidir*     = 102H;
+  playmodeLoopOnce*  = 105H;
+
+  mixerQualityPoint*  = 110H;
+  mixerQualityLinear* = 111H;
+  mixerQualityCubic*  = 112H;
+
+  (** Values of Event.type **)
   eventJoystickAxis*          =  1;
   eventJoystickAxis*          =  1;
   eventJoystickButtonDown*    =  2;
   eventJoystickButtonDown*    =  2;
   eventJoystickButtonUp*      =  3;
   eventJoystickButtonUp*      =  3;
@@ -114,11 +141,11 @@ CONST
   eventDisplayConnected*      = 60;
   eventDisplayConnected*      = 60;
   eventDisplayDisconnected*   = 61;
   eventDisplayDisconnected*   = 61;
 
 
-  (** Flip flags for al_draw_bitmap *)
+  (** Flip flags for al_draw_bitmap **)
   flipHorizontal* = 1;
   flipHorizontal* = 1;
   flipVertical* = 2;
   flipVertical* = 2;
 
 
-  (** Key codes *)
+  (** Key codes **)
   keyA* = 1;
   keyA* = 1;
   keyB* = 2;
   keyB* = 2;
   keyC* = 3;
   keyC* = 3;
@@ -294,7 +321,7 @@ CONST
   keymodAccent3*    = 14;
   keymodAccent3*    = 14;
   keymodAccent4*    = 15;
   keymodAccent4*    = 15;
 
 
-  (* Other *)
+  (** - **)
   intMax* = 2147483647;
   intMax* = 2147483647;
 
 
 TYPE
 TYPE
@@ -514,13 +541,20 @@ PROCEDURE put_blended_pixel* ["al_put_blended_pixel"] (x, y: INTEGER; c: Color);
 
 
 PROCEDURE init_primitives_addon* ["al_init_primitives_addon"] (): BOOLEAN;
 PROCEDURE init_primitives_addon* ["al_init_primitives_addon"] (): BOOLEAN;
 PROCEDURE init_image_addon* ["al_init_image_addon"] (): BOOLEAN;
 PROCEDURE init_image_addon* ["al_init_image_addon"] (): BOOLEAN;
-PROCEDURE init_audio_addon* ["al_init_audio_addon"] (): BOOLEAN;
 PROCEDURE init_acodec_addon* ["al_init_acodec_addon"] (): BOOLEAN;
 PROCEDURE init_acodec_addon* ["al_init_acodec_addon"] (): BOOLEAN;
 PROCEDURE init_font_addon* ["al_init_font_addon"] (): BOOLEAN;
 PROCEDURE init_font_addon* ["al_init_font_addon"] (): BOOLEAN;
 PROCEDURE init_native_dialog_addon* ["al_init_native_dialog_addon"] (): BOOLEAN;
 PROCEDURE init_native_dialog_addon* ["al_init_native_dialog_addon"] (): BOOLEAN;
 
 
+PROCEDURE install_audio* ["al_install_audio"] (): BOOLEAN;
+PROCEDURE reserve_samples* ["al_reserve_samples"] (n: INTEGER): BOOLEAN;
+PROCEDURE create_audio_stream* ["al_create_audio_stream"] (fragmentCount, samples, freq, depth, chanConf: INTEGER): ADRINT;
+PROCEDURE attach_audio_stream_to_mixer* ["al_attach_audio_stream_to_mixer"] (stream, mixer: ADRINT): BOOLEAN;
+PROCEDURE get_default_mixer* ["al_get_default_mixer"] (): ADRINT;
+PROCEDURE destroy_audio_stream* ["al_destroy_audio_stream"] (stream: ADRINT);
+
 PROCEDURE draw_line* ["al_draw_line"] (x1, y1, x2, y2: REAL; color: Color; thickness: REAL);
 PROCEDURE draw_line* ["al_draw_line"] (x1, y1, x2, y2: REAL; color: Color; thickness: REAL);
 PROCEDURE draw_triangle* ["al_draw_triangle"] (x1, y1, x2, y2, x3, y3: REAL; color: Color; thickness: REAL);
 PROCEDURE draw_triangle* ["al_draw_triangle"] (x1, y1, x2, y2, x3, y3: REAL; color: Color; thickness: REAL);
+PROCEDURE draw_filled_triangle* ["al_draw_filled_triangle"] (x1, y1, x2, y2, x3, y3: REAL; color: Color);
 PROCEDURE draw_rectangle* ["al_draw_rectangle"] (x1, y1, x2, y2: REAL; color: Color; thickness: REAL);
 PROCEDURE draw_rectangle* ["al_draw_rectangle"] (x1, y1, x2, y2: REAL; color: Color; thickness: REAL);
 PROCEDURE draw_filled_rectangle* ["al_draw_filled_rectangle"] (x1, y1, x2, y2: REAL; color: Color);
 PROCEDURE draw_filled_rectangle* ["al_draw_filled_rectangle"] (x1, y1, x2, y2: REAL; color: Color);
 PROCEDURE draw_rounded_rectangle* ["al_draw_rounded_rectangle"] (x1, y1, x2, y2, rx, ry: REAL; color: Color; thickness: REAL);
 PROCEDURE draw_rounded_rectangle* ["al_draw_rounded_rectangle"] (x1, y1, x2, y2, rx, ry: REAL; color: Color; thickness: REAL);

+ 13 - 7
src/Builder.Mod

@@ -322,6 +322,7 @@ PROCEDURE ResetSysModules*;
     Add('Graph'); p := sysModules;
     Add('Graph'); p := sysModules;
     IF Config.isWindows THEN
     IF Config.isWindows THEN
       p.libs := '-lallegro -lallegro_primitives -lallegro_image';
       p.libs := '-lallegro -lallegro_primitives -lallegro_image';
+      Strings.Append(' -lallegro_audio -lallegro_acodec', p.libs);
       Strings.Append(' "-Wl,-subsystem,windows"', p.libs)
       Strings.Append(' "-Wl,-subsystem,windows"', p.libs)
     ELSE
     ELSE
       p.libs := '$(pkg-config allegro_primitives-5 allegro_image-5';
       p.libs := '$(pkg-config allegro_primitives-5 allegro_image-5';
@@ -332,11 +333,12 @@ PROCEDURE ResetSysModules*;
   END AddGraph;
   END AddGraph;
 
 
 BEGIN sysModules := NIL;
 BEGIN sysModules := NIL;
-  Add('SYSTEM');   Add('Texts');    Add('Files');   Add('Strings');
-  Add('In');       Add('Out');      Add('Math');    Add('MathL');
-  Add('Modules');  Add('Platform'); Add('Oberon');  Add('Reals');
-  Add('VT100');    AddGraph;        Add('TermBox'); Add('Term');
-  Add('Allegro5'); Add('Dir');      Add('Int');     Add('Random')
+  Add('SYSTEM');   Add('Texts');     Add('Files');   Add('Strings');
+  Add('In');       Add('Out');       Add('Math');    Add('MathL');
+  Add('Modules');  Add('Platform');  Add('Oberon');  Add('Reals');
+  Add('VT100');    AddGraph;         Add('Sound');   Add('TermBox');
+  Add('Term');     Add('Allegro5');  Add('Dir');     Add('Int');
+  Add('Random')
 END ResetSysModules;
 END ResetSysModules;
 
 
 PROCEDURE GetWord(IN s: ARRAY OF CHAR; VAR i: INTEGER; OUT w: ARRAY OF CHAR);
 PROCEDURE GetWord(IN s: ARRAY OF CHAR; VAR i: INTEGER; OUT w: ARRAY OF CHAR);
@@ -589,8 +591,12 @@ BEGIN L := NIL; res := 0(*OK*); foreign := FALSE;
   NEW(mod); mod.s := modname$; mod.fname := fname$;
   NEW(mod); mod.s := modname$; mod.fname := fname$;
   mod.foreign := FALSE; mod.next := NIL;
   mod.foreign := FALSE; mod.next := NIL;
   IF IsSysModule(modname) THEN
   IF IsSysModule(modname) THEN
-    NEW(p); p.next := NIL; p.s := modname$; p.fname[0] := 0X;
-    p.foreign := FALSE; p.system := TRUE; GetSysLibs(modname, p.libs);
+    NEW(p); p.next := NIL;
+    IF (modname = 'TermBox') OR (modname = 'Sound') THEN p.s := 'Graph'
+    ELSE p.s := modname$
+    END;
+    p.fname[0] := 0X;
+    p.foreign := FALSE; p.system := TRUE; GetSysLibs(p.s, p.libs);
     AddUniqueToList(p, L)
     AddUniqueToList(p, L)
   ELSE
   ELSE
     list := GetModuleInfo(mod, errLine, errCol, res);
     list := GetModuleInfo(mod, errLine, errCol, res);

+ 1 - 1
src/Editor.Mod

@@ -459,7 +459,7 @@ BEGIN
       KW('ELSE') OR KW('OF') OR KW('WITH') OR KW('LONGSET') OR
       KW('ELSE') OR KW('OF') OR KW('WITH') OR KW('LONGSET') OR
       KW('UNTIL') OR KW('CONST') OR KW('MOD') OR KW('FLOOR') OR KW('LSL') OR
       KW('UNTIL') OR KW('CONST') OR KW('MOD') OR KW('FLOOR') OR KW('LSL') OR
       KW('ASR') OR KW('ROR') OR KW('ASSERT') OR KW('BYTE') OR
       KW('ASR') OR KW('ROR') OR KW('ASSERT') OR KW('BYTE') OR
-      KW('SHORTCHAR') OR KW('SYSTEM')
+      KW('SHORTCHAR') OR KW('SYSTEM') OR KW('FLT')
       OR KW('МОДУЛЬ') OR KW('ИМПОРТ') OR KW('ПЕРЕМЕННЫЕ') OR KW('МАССИВ') OR
       OR KW('МОДУЛЬ') OR KW('ИМПОРТ') OR KW('ПЕРЕМЕННЫЕ') OR KW('МАССИВ') OR
       KW('ИЗ') OR KW('ЛИТЕРА') OR KW('ЦЕЛОЕ') OR KW('НАЧАЛО') OR KW('НОВЫЙ') OR
       KW('ИЗ') OR KW('ЛИТЕРА') OR KW('ЦЕЛОЕ') OR KW('НАЧАЛО') OR KW('НОВЫЙ') OR
       KW('ЕСЛИ') OR KW('ТОГДА') OR KW('ИНАЧЕ') OR KW('АЕСЛИ') OR KW('КОНЕЦ')
       KW('ЕСЛИ') OR KW('ТОГДА') OR KW('ИНАЧЕ') OR KW('АЕСЛИ') OR KW('КОНЕЦ')

+ 8 - 0
src/FreeOberon.Mod

@@ -1039,6 +1039,7 @@ PROCEDURE Init(): BOOLEAN;
 VAR success, fs, sw, mx: BOOLEAN;
 VAR success, fs, sw, mx: BOOLEAN;
   w, h: INTEGER;
   w, h: INTEGER;
   lang: ARRAY 6 OF CHAR;
   lang: ARRAY 6 OF CHAR;
+  iconFile, fontFile: ARRAY 256 OF CHAR;
   s: FoStrings.String;
   s: FoStrings.String;
   fnames: Fnames;
   fnames: Fnames;
   opt: SET;
   opt: SET;
@@ -1053,6 +1054,13 @@ BEGIN
   FoStrings.SetLang(lang);
   FoStrings.SetLang(lang);
   FoStrings.Get('titleFreeOberon', s);
   FoStrings.Get('titleFreeOberon', s);
   T.SetTitle(s);
   T.SetTitle(s);
+
+  Env.GetAppDir(iconFile); Strings.Copy(iconFile, fontFile);
+  Strings.Append('Data/Images/Icon.png', iconFile);
+  Strings.Append('Data/Fonts/Main', fontFile);
+  T.SetIconFile(iconFile);
+  T.SetFontFile(fontFile);
+
   T.Init;
   T.Init;
   IF T.Done THEN
   IF T.Done THEN
     InitIDE;
     InitIDE;

+ 46 - 7
src/Graph.Mod

@@ -277,6 +277,7 @@ TYPE
 
 
 VAR
 VAR
   Done*: BOOLEAN;
   Done*: BOOLEAN;
+  initialized*: BOOLEAN;
 
 
   settings: SET; (** See list of constants Settings above *)
   settings: SET; (** See list of constants Settings above *)
   wantW, wantH: INTEGER; (** Assigned in procedure Settings *)
   wantW, wantH: INTEGER; (** Assigned in procedure Settings *)
@@ -679,6 +680,39 @@ PROCEDURE VLine*(x, y1, y2: INTEGER; color: Color);
 BEGIN LineF(FLT(x), FLT(y1), FLT(x), FLT(y2), color)
 BEGIN LineF(FLT(x), FLT(y1), FLT(x), FLT(y2), color)
 END VLine;
 END VLine;
 
 
+PROCEDURE FillTriangleF*(x1, y1, x2, y2, x3, y3: REAL; color: Color);
+BEGIN Al.draw_filled_triangle(x1, y1, x2, y2, x3, y3, SYSTEM.VAL(Al.Color, color))
+END FillTriangleF;
+
+PROCEDURE FillTriangle*(x1, y1, x2, y2, x3, y3: INTEGER; color: Color);
+BEGIN
+  Al.draw_filled_triangle(FLT(x1) + 0.5, FLT(y1) + 0.5,
+    FLT(x2) + 0.5, FLT(y2) + 0.5, FLT(x3) + 0.5, FLT(y3) + 0.5, SYSTEM.VAL(Al.Color, color))
+END FillTriangle;
+
+PROCEDURE ThickTriangleF*(x1, y1, x2, y2, x3, y3: REAL; color: Color; thickness: REAL);
+BEGIN Al.draw_triangle(x1, y1, x2, y2, x3, y3, SYSTEM.VAL(Al.Color, color), thickness)
+END ThickTriangleF;
+
+PROCEDURE ThickTriangle*(x1, y1, x2, y2, x3, y3: INTEGER; color: Color;
+    thickness: INTEGER);
+BEGIN
+  Al.draw_triangle(FLT(x1) + 0.5, FLT(y1) + 0.5,
+    FLT(x2) + 0.5, FLT(y2) + 0.5, FLT(x3) + 0.5, FLT(y3) + 0.5,
+    SYSTEM.VAL(Al.Color, color), FLT(thickness))
+END ThickTriangle;
+
+PROCEDURE TriangleF*(x1, y1, x2, y2, x3, y3: REAL; color: Color);
+BEGIN Al.draw_triangle(x1, y1, x2, y2, x3, y3, SYSTEM.VAL(Al.Color, color), 1.0)
+END TriangleF;
+
+PROCEDURE Triangle*(x1, y1, x2, y2, x3, y3: INTEGER; color: Color);
+BEGIN
+  Al.draw_triangle(FLT(x1) + 0.5, FLT(y1) + 0.5,
+    FLT(x2) + 0.5, FLT(y2) + 0.5, FLT(x3) + 0.5, FLT(y3) + 0.5,
+    SYSTEM.VAL(Al.Color, color), 1.0)
+END Triangle;
+
 PROCEDURE FillRectF*(x1, y1, x2, y2: REAL; color: Color);
 PROCEDURE FillRectF*(x1, y1, x2, y2: REAL; color: Color);
 BEGIN Al.draw_filled_rectangle(x1, y1, x2, y2, SYSTEM.VAL(Al.Color, color))
 BEGIN Al.draw_filled_rectangle(x1, y1, x2, y2, SYSTEM.VAL(Al.Color, color))
 END FillRectF;
 END FillRectF;
@@ -1428,6 +1462,7 @@ BEGIN
   IF ~Platform.Windows THEN (* Workaround for an Allegro bug on X11 *)
   IF ~Platform.Windows THEN (* Workaround for an Allegro bug on X11 *)
     FOR i := 1 TO 6 DO (* Maybe "TO 1" is enough *)
     FOR i := 1 TO 6 DO (* Maybe "TO 1" is enough *)
       Al.free_with_context(a, 1429, 'Graph.Mod', 'GetClipboardText');
       Al.free_with_context(a, 1429, 'Graph.Mod', 'GetClipboardText');
+      Delay(1);
       a := Al.get_clipboard_text(win.display)
       a := Al.get_clipboard_text(win.display)
     END
     END
   END;
   END;
@@ -1500,21 +1535,23 @@ BEGIN ok := TRUE;
       Error('Could not install keyboard.'); ok := FALSE
       Error('Could not install keyboard.'); ok := FALSE
     END;
     END;
 
 
-    IF ~(noMouse IN settings) & ~Al.install_mouse() THEN
+    IF ok & ~(noMouse IN settings) & ~Al.install_mouse() THEN
       Error('Could not install mouse.'); ok := FALSE
       Error('Could not install mouse.'); ok := FALSE
     END;
     END;
 
 
-    IF ~Al.init_primitives_addon() THEN
+    IF ok & ~Al.init_primitives_addon() THEN
       Error('Could not init primitives addon.'); ok := FALSE
       Error('Could not init primitives addon.'); ok := FALSE
     END;
     END;
 
 
-    IF ~Al.init_image_addon() THEN
+    IF ok & ~Al.init_image_addon() THEN
       Error('Could not init image addon.'); ok := FALSE
       Error('Could not init image addon.'); ok := FALSE
     END;
     END;
 
 
-    queue := Al.create_event_queue();
-    IF queue = NIL THEN
-      Error('Could not create queue.'); ok := FALSE
+    IF ok THEN
+      queue := Al.create_event_queue();
+      IF queue = NIL THEN
+        Error('Could not create queue.'); ok := FALSE
+      END
     END;
     END;
 
 
     IF ok THEN
     IF ok THEN
@@ -1524,7 +1561,8 @@ BEGIN ok := TRUE;
       Al.register_event_source(queue, Al.get_keyboard_event_source());
       Al.register_event_source(queue, Al.get_keyboard_event_source());
       IF ~(noMouse IN settings) THEN
       IF ~(noMouse IN settings) THEN
         Al.register_event_source(queue, Al.get_mouse_event_source())
         Al.register_event_source(queue, Al.get_mouse_event_source())
-      END
+      END;
+      initialized := TRUE
     END;
     END;
 
 
     IF ~(manual IN settings) THEN
     IF ~(manual IN settings) THEN
@@ -1554,6 +1592,7 @@ BEGIN
 END Close;
 END Close;
 
 
 BEGIN Done := FALSE;
 BEGIN Done := FALSE;
+  initialized := FALSE;
   MakeCol(black, 0, 0, 0);
   MakeCol(black, 0, 0, 0);
   ResetDefaults
   ResetDefaults
 END Graph.
 END Graph.

+ 8 - 0
src/Makefile

@@ -0,0 +1,8 @@
+all:
+	./make.sh
+
+run: all
+	@cd .. && ./FreeOberon --window TestSound
+
+.phony:
+	run

+ 1061 - 0
src/Programs/FreeOberon.Mod

@@ -0,0 +1,1061 @@
+MODULE FreeOberon; (* COPY *)
+(* Copyright 2017-2023 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 Free Oberon.  If not, see <http://www.gnu.org/licenses/>.
+*)
+IMPORT T := TermBox, Files, Args, Utf8, Builder, Env, Debug, Graph,
+       OV, Editor, Term, FoStrings, Config, Strings, Int, Out, Kernel;
+(** Free Oberon IDE and Compiler. Part of Free Oberon IDE internal code *)
+CONST
+  (* Direction of Selection *)
+  dirLeft  = 0;
+  dirRight = 1;
+  dirUp    = 2;
+  dirDown  = 3;
+
+  (* States *)
+  stateEditor   = 0;
+  stateTerminal = 1;
+
+  (* Character Classes *)
+  charOther       = 0; (*!FIXME Remove these constants *)
+  charAlpha       = 1;
+  charDigit       = 2;
+  charMinusPlus   = 3;
+  charQuote       = 4;
+  charOpenBracket = 5;
+
+  (* Token Classes *)
+  tokenOther   = 0;
+  tokenKeyword = 1;
+  tokenNumber  = 2;
+  tokenString  = 3;
+  tokenComment = 4;
+
+  (* Defaults *)
+  defW = 106;
+  defH = 25;
+  defLang = 'en';
+
+TYPE
+  Fnames = ARRAY 32, 256 OF CHAR;
+
+  LanguageDialog* = POINTER TO LanguageDialogDesc;
+  LanguageDialogDesc = RECORD(OV.WindowDesc)
+    btnRu*, btnEn*: OV.Button
+  END;
+
+VAR
+  progBuf: ARRAY 16300 OF SHORTCHAR; (* For interaction with running program *)
+  inputBuf: ARRAY 16300 OF CHAR; (* Holds entered chars before Enter pressed *)
+  inputBufLen: INTEGER;
+  programFinished: BOOLEAN;
+  lastFileDialogDir: ARRAY 256 OF CHAR; (* Directory path for file dialog *)
+  app: OV.App;
+
+  curX, curY: INTEGER; (* Text cursor position *)
+  curFg, curBg: INTEGER; (* Current foreground and background of proc. Write *)
+  terminalNeedRedraw: BOOLEAN; (* Used when a compiled program is running *)
+  terminalMouseShown: BOOLEAN; (* Same *)
+
+  lastW, lastH: INTEGER; (* Last screen size *)
+
+(* Language Dialog *)
+
+PROCEDURE LanguageButtonClick*(c: OV.Control);
+VAR lang: ARRAY 10 OF CHAR;
+BEGIN
+  IF c.caption = 'English' THEN lang := 'en' ELSE lang := 'ru' END;
+  IF lang # FoStrings.lang THEN FoStrings.SetLang(lang) END;
+  OV.CloseCurWindow(c)
+END LanguageButtonClick;
+
+PROCEDURE InitLanguageDialog*(c: LanguageDialog);
+VAR lbl: OV.Label;
+  s: FoStrings.String;
+BEGIN OV.InitWindow(c);
+  c.modal := TRUE; c.w := 37; c.h := 8; OV.CenterWindow(c);
+
+  FoStrings.Get('labelChooseLanguage', s);
+  lbl := OV.NewLabel(s);
+  lbl.align := OV.center;
+  lbl.do.resize(lbl, 1, 2, c.w - 2, 1);
+  OV.Add(c, lbl);
+
+  (* Ru button *)
+  c.btnRu := OV.NewButton('Русский');
+  c.btnRu.onClick := LanguageButtonClick;
+  c.btnRu.do.resize(c.btnRu, 5, 4, 11, 1);
+  OV.Add(c, c.btnRu);
+
+  (* En button *)
+  c.btnEn := OV.NewButton('English');
+  c.btnEn.onClick := LanguageButtonClick;
+  c.btnEn.do.resize(c.btnEn, 20, 4, 11, 1);
+  OV.Add(c, c.btnEn)
+END InitLanguageDialog;
+
+PROCEDURE NewLanguageDialog*(): LanguageDialog;
+VAR c: LanguageDialog;
+BEGIN NEW(c); InitLanguageDialog(c) ;
+RETURN c END NewLanguageDialog;
+
+(* General *)
+
+PROCEDURE CountLines(s: ARRAY OF CHAR; width: INTEGER): INTEGER;
+VAR i, x, lines: INTEGER;
+BEGIN
+  i := 0; x:= 0; lines := 1;
+  WHILE s[i] # 0X DO
+    IF s[i] = 0AX THEN
+      INC(lines); x := 0
+    ELSIF s[i] # 0DX THEN
+      IF x = width - 1 THEN INC(lines); x := 0
+      ELSE INC(x)
+      END
+    END;
+    INC(i)
+  END ;
+RETURN lines END CountLines;
+
+PROCEDURE ShowError(s: ARRAY OF CHAR);
+BEGIN Editor.SetMsg(app.windows(Editor.Editor), s)
+END ShowError;
+
+PROCEDURE FileNew(c: OV.Control);
+VAR e: Editor.Editor;
+  p, br: OV.Control;
+  count: INTEGER;
+  tW, tH: INTEGER;
+BEGIN e := Editor.NewEditor();
+  p := app.windows; br := p; count := 0;
+  WHILE p # NIL DO INC(count);
+    IF p.next = br THEN p := NIL ELSE p := p.next END
+  END;
+  IF app.windows # NIL THEN
+    e.x := app.windows.x + 1; e.y := app.windows.y + 1;
+    e.w := app.windows.w; e.h := app.windows.h;
+    T.Size(tW, tH);
+    IF e.x + e.w >= tW THEN e.w := tW - e.x END;
+    IF e.y + e.h >= tH - 1 THEN e.h := tH - e.y - 1 END;
+    IF (e.w < 10) OR (e.h < 3) THEN
+      e.x := 0; e.y := 1; e.w := tW; e.h := tH - 2
+    END
+  END;
+  e.caption := 'NONAME??.Mod';
+  e.caption[6] := CHR(ORD('0') + count DIV 10 MOD 10);
+  e.caption[7] := CHR(ORD('0') + count MOD 10);
+  OV.AddWindow(app, e)
+END FileNew;
+
+PROCEDURE FnameToCaption(IN fname: ARRAY OF CHAR; VAR caption: ARRAY OF CHAR);
+BEGIN
+  IF Strings.Pos(Config.stdPath, fname, 0) = 0 THEN
+    Strings.Extract(fname, Strings.Length(Config.stdPath),
+      LEN(caption), caption)
+  ELSE caption := fname$
+  END
+END FnameToCaption;
+
+PROCEDURE DoOpenFile(IN fname: ARRAY OF CHAR);
+VAR e: Editor.Editor; newWin: BOOLEAN;
+BEGIN
+  IF (app.windows # NIL) & (app.windows IS Editor.Editor) THEN
+    e := app.windows(Editor.Editor)
+  ELSE e := NIL
+  END;
+  newWin := (e = NIL) OR ~Editor.IsEmpty(e);
+  IF newWin THEN e := Editor.NewEditor() END;
+  IF e.text.LoadFromFile(fname) THEN
+    e.fname := fname$;
+    FnameToCaption(fname, e.caption);
+    IF newWin THEN OV.AddWindow(app, e) END
+  ELSE (*!FIXME*)
+  END
+END DoOpenFile;
+
+PROCEDURE FocusOrOpenFile(fname: ARRAY OF CHAR);
+VAR e, f: Editor.Editor;
+BEGIN
+  e := app.windows(Editor.Editor); f := e;
+  WHILE (e # NIL) & (e.fname # fname) DO
+    IF e.next = f THEN e := NIL ELSE e := e.next(Editor.Editor) END
+  END;
+  IF e = NIL THEN DoOpenFile(fname) ELSE app.windows := e; OV.SetFocus(e) END;
+  OV.DrawApp(app)
+END FocusOrOpenFile;
+
+PROCEDURE ScrollScreen;
+VAR x, y, tW, tH: INTEGER;
+  ch: CHAR;
+  fg, bg: INTEGER;
+BEGIN
+  T.Size(tW, tH);
+  FOR y := 0 TO tH - 2 DO
+    FOR x := 0 TO tW - 1 DO
+      T.GetCell(x, y + 1, ch, fg, bg);
+      T.SetCell(x, y, ch, fg, bg)
+    END
+  END;
+  FOR x := 0 TO tW - 1 DO
+    T.SetCell(x, tH - 1, ' ', curFg, curBg)
+  END;
+  terminalNeedRedraw := TRUE
+END ScrollScreen;
+
+PROCEDURE Ln;
+VAR tW, tH: INTEGER;
+BEGIN T.Size(tW, tH); curX := 0;
+  IF curY >= tH - 1 THEN ScrollScreen; curY := tH - 1
+  ELSE INC(curY); terminalNeedRedraw := TRUE
+  END;
+END Ln;
+
+PROCEDURE Write(ch: CHAR);
+VAR tW, tH: INTEGER;
+BEGIN
+  IF ch = 0AX THEN Ln
+  ELSIF ~Config.isWindows OR (ch # 0DX) THEN
+    T.SetCell(curX, curY, ch, curFg, curBg);
+    T.Size(tW, tH);
+    IF curX >= tW - 1 THEN
+      curX := 0;
+      IF curY >= tH - 1 THEN ScrollScreen; curY := tH - 1 ELSE INC(curY) END
+    ELSE INC(curX)
+    END;
+    terminalNeedRedraw := TRUE
+  END;
+  T.SetCursor(curX, curY)
+END Write;
+
+PROCEDURE WriteString(s: ARRAY OF CHAR);
+VAR i: INTEGER;
+BEGIN i := 0;
+  WHILE s[i] # 0X DO Write(s[i]); INC(i) END
+END WriteString;
+
+PROCEDURE Backspace;
+VAR tW, tH: INTEGER;
+BEGIN
+  IF curX = 0 THEN T.Size(tW, tH);
+    IF curY # 0 THEN curX := tW - 1; DEC(curY) END
+  ELSE DEC(curX);
+  END;
+  T.SetCell(curX, curY, ' ', curFg, curBg);
+  T.SetCursor(curX, curY);
+  terminalNeedRedraw := TRUE
+END Backspace;
+
+PROCEDURE PollProgram;
+VAR len, i: INTEGER;
+    err: INTEGER;
+    s, sN: FoStrings.String;
+
+  PROCEDURE WriteProgBuf;
+  VAR ch: SHORTCHAR; i: INTEGER;
+    z: ARRAY 16300 OF CHAR;
+  BEGIN
+    IF len < LEN(progBuf) THEN ch := progBuf[len]; progBuf[len] := 0X END;
+    Utf8.Decode(progBuf, z);
+    i := 0; WHILE z[i] # 0X DO Write(z[i]); INC(i) END;
+    IF len < LEN(progBuf) THEN progBuf[len] := ch END
+  END WriteProgBuf;
+
+  PROCEDURE Read(tillEnd: BOOLEAN);
+  VAR loopLimit: INTEGER;
+  BEGIN
+    loopLimit := 20;
+    REPEAT
+      Term.ReadFromProcess(progBuf, len, LEN(progBuf));
+      IF len > 0 THEN
+        IF inputBufLen > 0 THEN
+          FOR i := 0 TO inputBufLen - 1 DO Backspace END;
+          inputBufLen := 0
+        END;
+        WriteProgBuf
+      END;
+      DEC(loopLimit)
+    UNTIL (len <= 0) OR (loopLimit <= 0) & ~tillEnd
+  END Read;
+BEGIN
+  IF ~programFinished THEN
+    IF Term.ProcessFinished(err) THEN
+      Read(TRUE); (* Read everything until pipe is empty *)
+      programFinished := TRUE;
+      IF err = 0 THEN s := ' '; FoStrings.Append('pressAnyKeyToReturnToIde', s)
+      ELSE s := ' '; FoStrings.Append('runtimeError', s);
+        Strings.Append(' ', s); Int.Append(err, s)
+      END;
+      WriteString(s)
+    ELSE
+      Read(FALSE) (* Attempt several reads *)
+    END
+  END
+END PollProgram;
+
+PROCEDURE WriteToProcess(s: ARRAY OF CHAR; len: INTEGER);
+VAR buf: ARRAY 2048 OF SHORTCHAR;
+  q: ARRAY 5 OF SHORTCHAR;
+  i, j, L, bufLen: INTEGER;
+BEGIN bufLen := 0; i := 0;
+  WHILE i < len DO
+    Utf8.EncodeChar(s[i], q, L); j := 0;
+    WHILE j # L DO buf[bufLen] := q[j]; INC(bufLen); INC(j) END;
+    INC(i)
+  END;
+  Term.WriteToProcess(buf, bufLen)
+END WriteToProcess;
+
+PROCEDURE HandleTerminalTextInput(ch: CHAR);
+BEGIN
+  IF (ch # 0X) & (inputBufLen < LEN(inputBuf)) THEN
+    inputBuf[inputBufLen] := ch; INC(inputBufLen); Write(ch)
+  END
+END HandleTerminalTextInput;
+
+PROCEDURE KillProgram;
+BEGIN
+  programFinished := TRUE;
+  (*!TODO Kill program *)
+END KillProgram;
+
+PROCEDURE HandleTerminalKeyDown(VAR E: T.Event; VAR quit: BOOLEAN);
+VAR code: INTEGER; ch: CHAR; buf: ARRAY 2 OF SHORTCHAR;
+BEGIN
+  IF programFinished THEN
+    quit := TRUE
+  ELSE
+    CASE E.key OF
+      T.kEnter, T.kEnterPad:
+      Ln;
+      WriteToProcess(inputBuf, inputBufLen);
+      inputBufLen := 0; buf[0] := 0AX;
+      Term.WriteToProcess(buf, 1)
+    | T.kBackspace:
+      IF inputBufLen > 0 THEN
+        DEC(inputBufLen); Backspace
+      END
+    | T.kPause:
+      IF T.mCtrl IN E.mod THEN
+        KillProgram;
+        quit := TRUE
+      END
+    ELSE
+      HandleTerminalTextInput(E.ch)
+    END
+  END
+END HandleTerminalKeyDown;
+
+PROCEDURE RunTerminal;
+VAR E: T.Event; quit: BOOLEAN;
+BEGIN quit := FALSE;
+  T.Clear;
+  terminalNeedRedraw := TRUE;
+  terminalMouseShown := FALSE; T.HideMouse;
+  REPEAT
+    PollProgram;
+    IF terminalNeedRedraw THEN T.Flush; terminalNeedRedraw := FALSE END;
+    T.WaitEvent(E);
+    IF E.type = T.timer THEN
+    ELSIF E.type = T.key THEN HandleTerminalKeyDown(E, quit)
+    ELSIF E.type = T.quit THEN KillProgram
+    ELSIF E.type = T.mouse THEN
+      IF E.button # 0 THEN T.ShowMouse END
+    END;
+    IF terminalNeedRedraw THEN T.Flush; terminalNeedRedraw := FALSE END
+  UNTIL quit;
+  IF ~terminalMouseShown THEN T.ShowMouse END
+END RunTerminal;
+
+PROCEDURE RunProgram(IN prg: ARRAY OF CHAR);
+VAR dir, err: ARRAY 256 OF CHAR;
+  s, d: ARRAY 2048 OF SHORTCHAR;
+  i: INTEGER;
+  tW, tH: INTEGER;
+BEGIN dir := prg$; curX := 0; curY := 0; curFg := 7; curBg := 0;
+  T.SetCursor(0, 0); T.Size(tW, tH);
+  i := 0; WHILE dir[i] # 0X DO INC(i) END;
+  WHILE (i # -1) & (dir[i] # '/') & (dir[i] # '\') DO DEC(i) END;
+  INC(i); dir[i] := 0X; Utf8.Encode(prg, s); Utf8.Encode(dir, d);
+  IF Term.StartProcessIn(s, d) THEN programFinished := FALSE; RunTerminal
+  ELSE FoStrings.GetErrorStr(423, err); ShowError(err)
+  END
+END RunProgram;
+
+(** Puts in dir part of string s, before the last '/'.
+    If there is no '/', dir is ''. *)
+PROCEDURE DirName(IN s: ARRAY OF CHAR; OUT dir: ARRAY OF CHAR);
+VAR i: INTEGER;
+BEGIN
+  i := 0; WHILE s[i] # 0X DO INC(i) END;
+  WHILE (i >= 0) & (s[i] # '/') DO DEC(i) END;
+  dir[i + 1] := 0X;
+  WHILE i >= 0 DO dir[i] := s[i]; DEC(i) END
+END DirName;
+
+PROCEDURE OpenFileOkClick(c: OV.Control; fname: ARRAY OF CHAR);
+BEGIN
+  DirName(fname, lastFileDialogDir);
+  DoOpenFile(fname)
+END OpenFileOkClick;
+
+PROCEDURE DoSaveFile(c: OV.Control; fname: ARRAY OF CHAR);
+VAR w: OV.Window; e: Editor.Editor;
+BEGIN
+  Debug.StrVal('DoSaveFile fname = ', fname);
+  IF fname[0] # 0X THEN w := c.app.windows;
+    IF (w # NIL) & (w IS Editor.Editor) THEN e := w(Editor.Editor);
+      IF e.text.SaveToFile(fname) THEN
+        e.fname := fname$;
+        FnameToCaption(fname, e.caption)
+      END
+    END
+  END
+END DoSaveFile;
+
+PROCEDURE FileOpen(c: OV.Control);
+VAR w: Editor.FileDialog;
+BEGIN
+  w := Editor.NewFileDialog(Editor.open, lastFileDialogDir);
+  w.onFileOk := OpenFileOkClick;
+  OV.AddWindow(app, w)
+END FileOpen;
+
+PROCEDURE FileReload(c: OV.Control);
+VAR e: Editor.Editor;
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor.Editor) THEN
+    e := c.app.windows(Editor.Editor);
+    IF e.fname[0] # 0X THEN
+      IF e.text.LoadFromFile(e.fname) THEN (*!FIXME*) END
+    END
+  END
+END FileReload;
+
+PROCEDURE FileSaveAs(c: OV.Control);
+VAR d: Editor.FileDialog;
+  w: OV.Window; e: Editor.Editor;
+BEGIN d := Editor.NewFileDialog(Editor.save, lastFileDialogDir);
+  d.onFileOk := DoSaveFile;
+  w := c.app.windows;
+  IF (w # NIL) & (w IS Editor.Editor) THEN e := w(Editor.Editor);
+    IF e.fname[0] # 0X THEN Editor.FileDialogSetFname(d, e.fname) END
+  END;
+  OV.AddWindow(app, d)
+END FileSaveAs;
+
+PROCEDURE FileSave(c: OV.Control);
+VAR w: OV.Window;
+BEGIN w := c.app.windows;
+  IF (w # NIL) & (w IS Editor.Editor) THEN
+    IF w(Editor.Editor).fname[0] = 0X THEN FileSaveAs(c)
+    ELSE DoSaveFile(c, w(Editor.Editor).fname)
+    END
+  END
+END FileSave;
+
+PROCEDURE SearchFind(c: OV.Control);
+VAR w, e: OV.Window;
+BEGIN e := c.app.windows;
+  IF (e # NIL) & (e IS Editor.Editor) THEN
+    w := Editor.NewSearchDialog(e(Editor.Editor));
+    OV.AddWindow(app, w)
+  END
+END SearchFind;
+
+PROCEDURE SearchAgain(c: OV.Control);
+VAR e: OV.Window;
+BEGIN e := c.app.windows;
+  IF (e # NIL) & (e IS Editor.Editor) THEN
+    Editor.SearchNext(e(Editor.Editor))
+  END
+END SearchAgain;
+
+PROCEDURE OptionsLanguage(c: OV.Control);
+VAR w: OV.Window;
+BEGIN
+  w := NewLanguageDialog();
+  OV.AddWindow(app, w)
+END OptionsLanguage;
+
+PROCEDURE BuildErrorCallback(IN fname: ARRAY OF CHAR;
+    col, line, error: INTEGER; IN msg: ARRAY OF CHAR);
+VAR e: Editor.Editor;
+BEGIN
+  IF fname[0] # 0X THEN
+    FocusOrOpenFile(fname);
+    e := app.windows(Editor.Editor);
+    IF (col >= 1) & (line >= 1) THEN
+      IF (col = 1) & (line # 1) THEN
+        e.text.MoveToLineCol(line - 1, 256, e.h - 2)
+      ELSE e.text.MoveToLineCol(line, col, e.h - 2)
+      END
+    END;
+    Editor.PrintText(app.windows(Editor.Editor))
+  END;
+  ShowError(msg)
+END BuildErrorCallback;
+
+PROCEDURE OnBuild(c: OV.Control);
+VAR w: OV.Window;
+  foreign: BOOLEAN;
+  mainFname, modname, exename, errFname, s: ARRAY 256 OF CHAR;
+  errLine, errCol, res: INTEGER;
+  modules: Builder.Module;
+  e: Editor.Editor;
+BEGIN w := c.app.windows;
+  IF (w # NIL) & (w IS Editor.Editor) THEN
+    IF Editor.TextChanged(w(Editor.Editor)) THEN FileSave(c) END;
+    IF w(Editor.Editor).fname[0] # 0X THEN
+      mainFname := w(Editor.Editor).fname$;
+      Debug.StrVal('File name of main module: ', mainFname);
+      Builder.SetWorkDir(mainFname);
+      Debug.StrVal('Work directory: ', Builder.workDir);
+      Builder.GetModuleName(mainFname, modname);
+      Debug.StrVal('Module name: ', modname);
+      modules := Builder.UsedModuleList(modname, mainFname,
+        errFname, errLine, errCol, foreign, res);
+      IF foreign THEN res := 402 END;
+      Debug.IntVal('Result of UserModuleList: ', res);
+      IF res = 0 THEN
+        IF Builder.CompileAll(modules, exename, FALSE, BuildErrorCallback)
+        THEN RunProgram(exename)
+        END
+      ELSE
+        FocusOrOpenFile(errFname);
+        e := app.windows(Editor.Editor);
+        e.text.MoveToLineCol(errLine, errCol, e.h - 2);
+        FoStrings.MakeErrorStr(res, s);
+        ShowError(s)
+      END
+    END
+  END
+END OnBuild;
+
+PROCEDURE HelpAbout(c: OV.Control);
+CONST W = 37; H = 13;
+VAR w: OV.Window; L: OV.Label; b: OV.Button;
+  s: FoStrings.String;
+  Y: INTEGER;
+  tW, tH: INTEGER;
+BEGIN w := OV.NewWindow(); w.modal := TRUE;
+  FoStrings.Get('titleAbout', w.caption);
+  T.Size(tW, tH);
+  w.do.resize(w, (tW - W) DIV 2, (tH - H) DIV 2, W, H);
+  Y := 2;
+
+  FoStrings.Get('titleFreeOberon', s);
+  L := OV.NewLabel(s); L.align := OV.center;
+  L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
+
+  FoStrings.Get('version', s); Strings.Append(' ', s);
+  Strings.Append(Config.version, s);
+  L := OV.NewLabel(s); L.align := OV.center;
+  L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
+
+  FoStrings.Get('copyright', s); Strings.Append(' 2017-', s);
+  Int.Append(Config.year, s); Strings.Append(' ', s);
+  FoStrings.Append('copyrightBy', s);
+  
+  L := OV.NewLabel(s); L.align := OV.center;
+  L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
+
+  FoStrings.Get('authorName', s); Strings.Append(', free.oberon.org', s);
+  L := OV.NewLabel(s); L.align := OV.center;
+  L.do.resize(L, 1, Y, W - 2, 1); OV.Add(w, L); INC(Y, 2);
+
+  FoStrings.Get('btnOk', s);
+  b := OV.NewButton(s); b.default := TRUE;
+  b.do.resize(b, (W - 8) DIV 2, Y, 8, 1); OV.Add(w, b); INC(Y, 2);
+  b.onClick := OV.CloseCurWindow;
+
+  OV.AddWindow(app, w);
+  OV.SetFocus(b)
+END HelpAbout;
+
+PROCEDURE TileWindows*(c: OV.Control);
+VAR W, E: OV.Control;
+  count, cols, rows, i, col, x, y, w, h, w2, h2: INTEGER;
+  aw, ah, dw, dh: INTEGER; (* Accumulator, delta *)
+  tW, tH: INTEGER;
+BEGIN E := app.windows; count := 0;
+  T.Size(tW, tH);
+  IF E # NIL THEN W := E.next;
+    WHILE W # NIL DO
+      INC(count);
+      IF W = E THEN W := NIL ELSE W := W.next END
+    END;
+    IF count < 4 THEN rows := 1
+    ELSIF count < 9 THEN rows := 2
+    ELSE rows := 3
+    END;
+    cols := count DIV rows; col := 1;
+
+    x := 0; y := 1;
+    w := tW DIV cols; w2 := w;
+    dw := tW MOD cols;
+    h := (tH - 2) DIV rows;
+    dh := (tH - 2) MOD rows;
+    IF h < 2 THEN h := 2; dh := 0 END;
+    aw := dw; ah := 0;
+    W := E.next; i := 0;
+    WHILE W # NIL DO
+      INC(ah, dh);
+      IF ah < rows THEN h2 := h ELSE h2 := h + 1; DEC(ah, rows) END;
+      OV.WindowResize(W, x, y, w2, h2);
+      IF W = E THEN W := NIL ELSE W := W.next END;
+      INC(y, h2); INC(i);
+      IF (i = rows) & (col < cols) THEN (* New column *)
+        i := 0; INC(col); INC(x, w2); y := 1; ah := 0;
+        INC(aw, dw);
+        IF aw < cols THEN w2 := w ELSE w2 := w + 1; DEC(aw, cols) END;
+        IF col = cols THEN (* Last column *)
+          rows := count - rows * (cols - 1);
+          w := tW - x;
+          h := (tH - 2) DIV rows;
+          dh := (tH - 2) MOD rows;
+          IF h < 2 THEN h := 2; dh := 0 END
+        END
+      END
+    END
+  END
+END TileWindows;
+
+PROCEDURE CascadeWindows*(c: OV.Control);
+VAR W, E: OV.Control;
+  x, y, w, h: INTEGER;
+  tW, tH: INTEGER;
+BEGIN E := app.windows;
+  T.Size(tW, tH);
+  x := 0; y := 1; w := tW; h := tH - 2;
+  IF E # NIL THEN W := E.next;
+    WHILE W # NIL DO
+      OV.WindowResize(W, x, y, w, h);
+      INC(x); INC(y); DEC(w); DEC(h);
+      IF (w < 10) OR (h < 3) THEN
+        x := 0; y := 1; w := tW; h := tH - 2
+      END;
+      IF W = E THEN W := NIL ELSE W := W.next END
+    END
+  END
+END CascadeWindows;
+
+PROCEDURE FixClipboardText(VAR s: ARRAY OF CHAR);
+CONST CR = 0DX; LF = 0AX;
+VAR i, j: INTEGER;
+BEGIN
+  i := 0; WHILE (i # LEN(s)) & (s[i] # 0X) & (s[i] # CR) DO INC(i) END;
+  j := i;
+  WHILE (i # LEN(s)) & (s[i] # 0X) DO
+    IF s[i] = CR THEN INC(i);
+      IF i # LEN(s) THEN
+        IF s[i] # LF THEN s[j] := LF; INC(j) END
+      END
+    ELSE s[j] := s[i]; INC(i); INC(j)
+    END
+  END;
+  s[j] := 0X
+END FixClipboardText;
+
+PROCEDURE EditPasteClipboard*(c: OV.Control);
+VAR e: Editor.Editor;
+  s: ARRAY 50000 OF CHAR;
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor.Editor) THEN
+    Graph.GetClipboardText(T.GetWindow(), s);
+    FixClipboardText(s);
+    Editor.PasteText(app, s)
+  END
+END EditPasteClipboard;
+
+PROCEDURE EditCopyClipboard*(c: OV.Control);
+VAR s: ARRAY 50000 OF CHAR;
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor.Editor) THEN
+    c.app.windows(Editor.Editor).text.CopySelection(s);
+    Graph.SetClipboardText(T.GetWindow(), s)
+  END
+END EditCopyClipboard;
+
+PROCEDURE EditCutClipboard*(c: OV.Control);
+BEGIN
+  IF (c.app.windows # NIL) & (c.app.windows IS Editor.Editor) THEN
+    EditCopyClipboard(c);
+    Editor.EditClear(c.app.windows(Editor.Editor))
+  END
+END EditCutClipboard;
+
+PROCEDURE OnResize(w, h: INTEGER);
+VAR o, br: OV.Window;
+BEGIN
+  o := app.windows; br := o;
+  REPEAT
+    IF (o.w = lastW) & (o.h = lastH - 2) & (o.x = 0) & (o.y = 1) THEN
+      o.x := 0; o.y := 1; o.w := w; o.h := h - 2
+    END;
+    o := o.next(OV.Window)
+  UNTIL o = br;
+  lastW := w; lastH := h
+END OnResize;
+
+PROCEDURE InitIDE;
+VAR w: OV.Window;
+  m, m2: OV.Menu;
+  s, q: FoStrings.String;
+BEGIN
+  app := OV.NewApp();
+  T.Size(lastW, lastH);
+  OV.SetOnResize(app, OnResize);
+  FileNew(app.menu);
+
+  FoStrings.Get('menuFile', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuNew', s);
+  OV.Add(m, OV.NewMenu(s, 'Shift+F3', OV.hShiftF3, FileNew));
+  FoStrings.Get('menuOpen', s);
+  OV.Add(m, OV.NewMenu(s, 'F3', OV.hF3, FileOpen));
+  FoStrings.Get('menuReload', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, FileReload));
+  FoStrings.Get('menuSave', s);
+  OV.Add(m, OV.NewMenu(s, 'F2', OV.hF2, FileSave));
+  FoStrings.Get('menuSaveAs', s);
+  OV.Add(m, OV.NewMenu(s, 'Shift+F2', OV.hShiftF2, FileSaveAs));
+  (*FoStrings.Get('menuSaveAll', s);*)
+  (*OV.Add(m, OV.NewMenu(s, '', 0, NIL));*)
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuExit', s);
+  OV.Add(m, OV.NewMenu(s, 'Alt+X', OV.hAltX, OV.QuitApp));
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuEdit', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuUndo', s);
+  FoStrings.Get('actionDelText', q);
+  m2 := OV.NewMenu(s, q, OV.hAltBackspace, NIL); m2.status := OV.disabled;
+  OV.Add(m, m2);
+  FoStrings.Get('menuRedo', s);
+  m2 := OV.NewMenu(s, '', 0, NIL); m2.status := OV.disabled;
+  OV.Add(m, m2);
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuCut', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+X', OV.hCtrlX, EditCutClipboard));
+  FoStrings.Get('menuCopy', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+C', OV.hCtrlC, EditCopyClipboard));
+  FoStrings.Get('menuPaste', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+V', OV.hCtrlV, EditPasteClipboard));
+  FoStrings.Get('menuClear', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+Del', OV.hCtrlDel, Editor.EditClear));
+  FoStrings.Get('menuSelectAll', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+A', OV.hCtrlA, Editor.EditSelectAll));
+  FoStrings.Get('menuUnselect', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, Editor.EditUnselect));
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuCutInternal', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+Shift+X', OV.hCtrlShiftX, Editor.EditCut));
+  FoStrings.Get('menuCopyInternal', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+Shift+C', OV.hCtrlShiftC, Editor.EditCopy));
+  FoStrings.Get('menuPasteInternal', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+Shift+V', OV.hCtrlShiftV, Editor.EditPaste));
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuSearch', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuFind', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+F', OV.hCtrlF, SearchFind));
+  FoStrings.Get('menuReplace', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuSearchAgain', s);
+  OV.Add(m, OV.NewMenu(s, 'F12', OV.hF12, SearchAgain));
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuGoToLineNumber', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuFindProcedure', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuRun', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuRunRun', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+F9', OV.hCtrlF9, OnBuild));
+  FoStrings.Get('menuRunDirectory', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuParameters', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuCompile', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  OV.Add(m, OV.NewMenu(s, 'Alt+F9', OV.hAltF9, OnBuild));
+  FoStrings.Get('menuMake', s);
+  OV.Add(m, OV.NewMenu(s, 'Shift+F9', OV.hShiftF9, OnBuild));
+  FoStrings.Get('menuMakeAndRun', s);
+  OV.Add(m, OV.NewMenu(s, 'F9', OV.hF9, OnBuild));
+  FoStrings.Get('menuBuild', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, OnBuild));
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuDebug', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuOutput', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuTools', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuMessages', s);
+  OV.Add(m, OV.NewMenu(s, 'F11', OV.hF11, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuCalculator', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuAsciiTable', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuOptions', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuMode', s);
+  FoStrings.Get('menuNormalMode', q);
+  OV.Add(m, OV.NewMenu(s, q, 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuLanguage', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, OptionsLanguage));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuCompiler', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuMemorySizes', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuLinker', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuDirectories', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuTools', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuEnvironment', s);
+  m2 := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuPreferences', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  FoStrings.Get('menuEditor', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  FoStrings.Get('menuCodeComplete', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  FoStrings.Get('menuCodeTemplates', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  FoStrings.Get('menuDesktop', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  FoStrings.Get('menuKeyboardAndMouse', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  FoStrings.Get('menuLearnKeys', s);
+  OV.Add(m2, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m2.children.prev.status := OV.disabled;
+  OV.Add(m, m2);
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuOpenOptions', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuSaveOptions', s);
+  OV.Add(m, OV.NewMenu(s, 'fo.ini', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuSaveOptionsAs', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuWindow', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuTile', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, TileWindows));
+  FoStrings.Get('menuCascade', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, CascadeWindows));
+  FoStrings.Get('menuCloseAll', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, OV.CloseAllWindows));
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuSizeMove', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+F5', OV.hCtrlF5, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuZoom', s);
+  OV.Add(m, OV.NewMenu(s, 'F5', OV.hF5, OV.ZoomCurWindow));
+  FoStrings.Get('menuNextWindow', s);
+  OV.Add(m, OV.NewMenu(s, 'F6', OV.hF6, OV.NextWindow));
+  FoStrings.Get('menuPreviousWindow', s);
+  OV.Add(m, OV.NewMenu(s, 'Shift+F6', OV.hShiftF6, OV.PrevWindow));
+  FoStrings.Get('menuCloseWindow', s);
+  OV.Add(m, OV.NewMenu(s, 'Alt+F3', OV.hAltF3, OV.CloseCurWindow));
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuListWindows', s);
+  OV.Add(m, OV.NewMenu(s, 'Alt+0', OV.hAlt0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuRefreshDisplay', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, OV.RefreshDisplay));
+  OV.AddMenu(app, m);
+  FoStrings.Get('menuHelp', s);
+  m := OV.NewMenu(s, '', 0, NIL);
+  FoStrings.Get('menuContents', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuIndex', s);
+  OV.Add(m, OV.NewMenu(s, 'Shift+F1', OV.hShiftF1, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuTopicSearch', s);
+  OV.Add(m, OV.NewMenu(s, 'Ctrl+F1', OV.hCtrlF1, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuPreviousTopic', s);
+  OV.Add(m, OV.NewMenu(s, 'Alt+F1', OV.hAltF1, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuUsingHelp', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  FoStrings.Get('menuHelpFiles', s);
+  OV.Add(m, OV.NewMenu(s, '', 0, NIL));
+  (*!TODO*) m.children.prev.status := OV.disabled;
+  OV.Add(m, OV.NewMenu('-', '', 0, NIL));
+  FoStrings.Get('menuAbout', s);
+  OV.Add(m, OV.NewMenu(s, '', OV.hF1, HelpAbout));
+  OV.AddMenu(app, m);
+
+  FoStrings.Get('btnHelp', s);
+  OV.AddStatusbar(app, OV.NewQuickBtn(s, 'F1', 0, HelpAbout));
+  FoStrings.Get('btnSave', s);
+  OV.AddStatusbar(app, OV.NewQuickBtn(s, 'F2', 0, FileSave));
+  FoStrings.Get('btnOpen', s);
+  OV.AddStatusbar(app, OV.NewQuickBtn(s, 'F3', 0, FileOpen));
+  FoStrings.Get('btnCompileAndRun', s);
+  OV.AddStatusbar(app, OV.NewQuickBtn(s, 'F9', 0, OnBuild));
+  FoStrings.Get('btnLocalMenu', s);
+  OV.AddStatusbar(app, OV.NewQuickBtn(s, 'Alt+F10', 0, NIL))
+END InitIDE;
+
+PROCEDURE OpenFiles(VAR fnames: Fnames);
+VAR i: INTEGER;
+BEGIN i := 0;
+  WHILE (i < LEN(fnames)) & (fnames[i] # '') DO
+    DoOpenFile(fnames[i]); INC(i)
+  END;
+  IF i # 0 THEN OV.NextWindow(app.windows) END
+END OpenFiles;
+
+PROCEDURE ParseFileNameArg(VAR s: ARRAY OF CHAR);
+VAR L: INTEGER;
+  found: BOOLEAN;
+BEGIN
+  (* Replace all \ with / and set L to length of s *)
+  L := 0; found := FALSE;
+  WHILE s[L] # 0X DO
+    IF (s[L] = '\') OR (s[L] = '/') THEN s[L] := '/'; found := TRUE END;
+    INC(L)
+  END;
+
+  IF ~found THEN
+    IF (L < 4) OR (Strings.Pos('.Mod', s, L - 4) = -1) THEN
+      Strings.Append('.Mod', s)
+    END;
+    (*!FIXME first check if file not exists: *)
+    Strings.Insert(Config.stdPath, 0, s)
+  END
+END ParseFileNameArg;
+
+PROCEDURE ParseSize(IN s: ARRAY OF CHAR; VAR w, h: INTEGER);
+VAR i: INTEGER;
+  x: ARRAY 30 OF CHAR;
+BEGIN i := 0; w := 0; h := 0;
+  WHILE (s[i] # 0X) & (s[i] # 'x') DO INC(i) END;
+  Strings.Extract(s, 0, i, x); w := Int.Val(x);
+  Strings.Extract(s, i + 1, 30, x); h := Int.Val(x)
+END ParseSize;
+
+PROCEDURE IdentifyLanguage(VAR lang: ARRAY OF CHAR);
+BEGIN
+  Env.GetLang(lang); lang[2] := 0X;
+  IF ~FoStrings.LangExists(lang) THEN lang := defLang END
+END IdentifyLanguage;
+
+PROCEDURE ParseArgs(VAR fs, sw: BOOLEAN; VAR w, h: INTEGER;
+    VAR lang: ARRAY OF CHAR; VAR fnames: Fnames);
+VAR i, nofnames, count: INTEGER;
+  s: ARRAY 2048 OF CHAR;
+BEGIN fs := Config.startInFullscreen; sw := FALSE; i := 1;
+  nofnames := 0; w := defW; h := defH; lang := ''; count := Args.Count();
+  WHILE i <= count DO Args.Get(i, s);
+    IF s = '--window' THEN fs := FALSE
+    ELSIF s = '--fullscreen' THEN fs := TRUE
+    ELSIF s = '--software' THEN sw := TRUE
+    ELSIF s = '--debug' THEN Config.SetDebug(TRUE)
+    ELSIF s = '--size' THEN
+      IF i # count THEN INC(i); Args.Get(i, s); ParseSize(s, w, h) END
+    ELSIF s = '--lang' THEN
+      IF i # count THEN
+        INC(i); Args.Get(i, lang);
+        IF ~FoStrings.LangExists(lang) THEN Out.String('Language "');
+          Out.String(lang); Out.String('" does not exist.'); Out.Ln;
+          lang := ''
+        END
+      END
+    ELSIF nofnames < LEN(fnames) THEN
+      ParseFileNameArg(s);
+      fnames[nofnames] := s$;
+      INC(nofnames)
+    ELSE Out.String('Too many files.'); Out.Ln
+    END;
+    INC(i)
+  END;
+  IF nofnames < LEN(fnames) THEN fnames[nofnames][0] := 0X END;
+  IF lang = '' THEN IdentifyLanguage(lang) END
+END ParseArgs;
+
+PROCEDURE Init(): BOOLEAN;
+VAR success, fs, sw: BOOLEAN;
+  w, h: INTEGER;
+  lang: ARRAY 6 OF CHAR;
+  s: FoStrings.String;
+  fnames: Fnames;
+  opt: SET;
+BEGIN
+  success := FALSE;
+  lastFileDialogDir[0] := 0X;
+  ParseArgs(fs, sw, w, h, lang, fnames);
+  opt := {T.resizable, T.center};
+  IF fs THEN INCL(opt, T.fullscreen) ELSE INCL(opt, T.window) END;
+  T.Settings(w, h, opt);
+  FoStrings.SetLang(lang);
+  FoStrings.Get('titleFreeOberon', s);
+  T.SetTitle(s);
+  T.Init;
+  IF T.Done THEN
+    InitIDE;
+    OpenFiles(fnames);
+    success := TRUE
+  ELSE Out.String('Terminal init failed.'); Out.Ln
+  END ;
+RETURN success END Init;
+
+BEGIN
+  IF ~Init() THEN Out.String('Could not initialize.'); Out.Ln
+  ELSE OV.RunApp(app)
+  END;
+  T.Close
+END FreeOberon.

+ 63 - 0
src/Sound.Mod

@@ -0,0 +1,63 @@
+MODULE Sound;
+IMPORT Out, G := Graph, Al := Allegro5, SYSTEM, Platform;
+
+TYPE
+  Stream* = POINTER TO StreamDesc;
+  StreamDesc* = RECORD
+    str: Al.ADRINT;
+  END;
+
+VAR
+  Done*: BOOLEAN;
+
+  stream: Stream;
+
+PROCEDURE Error(s: ARRAY OF CHAR);
+BEGIN Out.String(s); Out.Ln
+END Error;
+
+PROCEDURE Sound*(freq: INTEGER);
+BEGIN
+
+END Sound;
+
+PROCEDURE NoSound*;
+BEGIN
+
+END NoSound;
+
+PROCEDURE Init*;
+VAR ok: BOOLEAN;
+BEGIN
+  Done := FALSE;
+  IF ~G.initialized THEN G.Init END;
+  IF G.initialized THEN
+    ok := TRUE;
+    IF ~Al.install_audio() THEN
+      Error('Could not install audio.'); ok := FALSE
+    END;
+    IF ok & ~Al.init_acodec_addon() THEN
+      Error('Could not init acodec addon.'); ok := FALSE
+    END;
+    IF ok & ~Al.reserve_samples(16(*!FIXME*)) THEN
+      Error('Could not reserve audio samples.'); ok := FALSE
+    END;
+    IF ok THEN
+      NEW(stream);
+      stream.str := Al.create_audio_stream(8, 1024(*!FIXME*), 22050,
+        Al.audioDepthUint8, Al.channelConf1);
+      IF stream.str # 0 THEN
+        Done := TRUE
+      END
+    END
+  END
+END Init;
+
+PROCEDURE Close*;
+BEGIN
+  Al.destroy_audio_stream(stream.str);
+END Close;
+
+BEGIN
+  Done := FALSE
+END Sound.

+ 7 - 8
src/TermBox.Mod

@@ -1,6 +1,6 @@
 MODULE TermBox;
 MODULE TermBox;
 (** Termbox is a module for creating cross-platform text-based interfaces *)
 (** Termbox is a module for creating cross-platform text-based interfaces *)
-IMPORT G := Graph, Strings, Int, Out, Env, Platform;
+IMPORT G := Graph, Strings, Int, Out, Platform;
 
 
 CONST
 CONST
   partW = 512;
   partW = 512;
@@ -926,19 +926,18 @@ BEGIN
   settings := flags
   settings := flags
 END Settings;
 END Settings;
 
 
-(** Sets the terminal icon (if Graph is used) *)
-PROCEDURE SetIcon*(s: ARRAY OF CHAR);
+(** Sets the terminal icon file name (if Graph is used) *)
+PROCEDURE SetIconFile*(s: ARRAY OF CHAR);
 BEGIN iconFile := s
 BEGIN iconFile := s
-END SetIcon;
+END SetIconFile;
 
 
-(** Sets the font file for the terminal (if Graph is used) *)
+(** Sets the font file name for the terminal (if Graph is used) *)
 PROCEDURE SetFontFile*(s: ARRAY OF CHAR);
 PROCEDURE SetFontFile*(s: ARRAY OF CHAR);
 BEGIN fontFile := s
 BEGIN fontFile := s
 END SetFontFile;
 END SetFontFile;
 
 
 BEGIN wantW := stdW; wantH := stdH; wantScaleX := 0.0; wantScaleY := 0.0;
 BEGIN wantW := stdW; wantH := stdH; wantScaleX := 0.0; wantScaleY := 0.0;
   settings := {fullscreen}; Done := FALSE;
   settings := {fullscreen}; Done := FALSE;
-  Env.GetAppDir(iconFile); Strings.Copy(iconFile, fontFile);
-  Strings.Append('Data/Images/Icon.png', iconFile);
-  Strings.Append('Data/Fonts/Main', fontFile)
+  iconFile := '../Data/Images/Icon.png';
+  fontFile := '../Data/Fonts/Main';
 END TermBox.
 END TermBox.

+ 6 - 2
src/make.bat

@@ -54,6 +54,8 @@ ECHO ON
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
 %OFR% -7w Graph.Mod
 %OFR% -7w Graph.Mod
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
+%OFR% -7w Sound.Mod
+@IF ERRORLEVEL 1 GOTO ERR
 %OFR% -7w TermBox.Mod
 %OFR% -7w TermBox.Mod
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
 %OFR% -Cw Term.Mod
 %OFR% -Cw Term.Mod
@@ -105,17 +107,19 @@ windres resources.rc resources.o
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
 %CCFULL% -c Graph.c
 %CCFULL% -c Graph.c
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
+%CCFULL% -c Sound.c
+@IF ERRORLEVEL 1 GOTO ERR
 %CCFULL% -c TermBox.c
 %CCFULL% -c TermBox.c
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
 
 
 %AR% -crs ..\Data\bin\FreeOberon.a ^
 %AR% -crs ..\Data\bin\FreeOberon.a ^
   Utf8.o Strings.o Reals.o Int.o Time.o In.o Out.o Args.o Env.o ^
   Utf8.o Strings.o Reals.o Int.o Time.o In.o Out.o Args.o Env.o ^
   Files.o Texts.o Random.o ^
   Files.o Texts.o Random.o ^
-  StrList.o Dir.o Graph.o TermBox.o
+  StrList.o Dir.o Graph.o Sound.o TermBox.o
 @IF ERRORLEVEL 1 GOTO ERR
 @IF ERRORLEVEL 1 GOTO ERR
 
 
 %CCFULL% -o ..\%PROG1%.exe resources.o ^
 %CCFULL% -o ..\%PROG1%.exe resources.o ^
-  Graph.c TermBox.c ^
+  Graph.c Sound.c TermBox.c ^
   Config.c Func.c Debug.c term\term_win32.c ^
   Config.c Func.c Debug.c term\term_win32.c ^
   Term.c OV.c FoStrings.c EditorText.c Editor.c Builder.c ^
   Term.c OV.c FoStrings.c EditorText.c Editor.c Builder.c ^
   FreeOberon.c ^
   FreeOberon.c ^

+ 6 - 2
src/make.sh

@@ -52,6 +52,8 @@ $OFR -7w Allegro5.Mod &&
 
 
 $OFR -7w Graph.Mod &&
 $OFR -7w Graph.Mod &&
 
 
+$OFR -7w Sound.Mod &&
+
 $OFR -7w TermBox.Mod &&
 $OFR -7w TermBox.Mod &&
 
 
 $OFR -Cw Term.Mod &&
 $OFR -Cw Term.Mod &&
@@ -103,17 +105,19 @@ $CCFULL -c Dir.c &&
 
 
 $CCFULL -c Graph.c &&
 $CCFULL -c Graph.c &&
 
 
+$CCFULL -c Sound.c &&
+
 $CCFULL -c TermBox.c &&
 $CCFULL -c TermBox.c &&
 
 
 
 
 $AR -crs ../Data/bin/libFreeOberon.a \
 $AR -crs ../Data/bin/libFreeOberon.a \
   Utf8.o Strings.o Reals.o Int.o Time.o In.o Out.o Args.o Env.o \
   Utf8.o Strings.o Reals.o Int.o Time.o In.o Out.o Args.o Env.o \
   Files.o Texts.o Random.o \
   Files.o Texts.o Random.o \
-  StrList.o Dir.o Graph.o TermBox.o &&
+  StrList.o Dir.o Graph.o Sound.o TermBox.o &&
 
 
 
 
 $CCFULL -o ../$PROG1 \
 $CCFULL -o ../$PROG1 \
-  Graph.c TermBox.c \
+  Graph.c Sound.c TermBox.c \
   Term.c term/term_linux.c \
   Term.c term/term_linux.c \
   Config.c Func.c Debug.c OV.c FoStrings.c EditorText.c Editor.c Builder.c \
   Config.c Func.c Debug.c OV.c FoStrings.c EditorText.c Editor.c Builder.c \
   FreeOberon.c \
   FreeOberon.c \