Procházet zdrojové kódy

Add generic dialogs, message boxes, SIGWINCH and docs/examples to tvision-m2

Dialogs and message boxes:
- Dialog/DlgLabel/DlgInput/DlgCheck/DlgRadio/DlgButton/DlgRun plus
  DlgTextAt/DlgValue: a centered modal form window; DlgRun returns the
  pressed button tag (cmCancel on Esc, cmClose from the close box).
- MessageBox(title, text, kind) with mbOK/mbOKCancel/mbYesNo; Message is
  now a shorthand.
- Radio buttons are self-exclusive now (SelectRadio), so neither dialogs
  nor applications manage the group.

Resize:
- tvtty.def gains a libc `signal` binding; tv installs a SIGWINCH handler
  and Resized() reports the change without polling.

Docs and examples:
- API.md (full reference) and TUTORIAL.md (empty app -> windows, commands,
  menus, dialogs, lists).
- examples/ empty, window, dialog, menus with a Makefile.
Eric Streit před 3 týdny
rodič
revize
85ba777d9c

+ 135 - 0
tvision-m2/API.md

@@ -0,0 +1,135 @@
+# tvision-m2 - API reference
+
+All names are in module `tv`.  Import it qualified (`IMPORT tv;`) or with
+`FROM tv IMPORT ...`.  Every drawing call draws into an off-screen cell
+buffer; nothing reaches the terminal until `Present` (or `Finish`).
+
+## Lifecycle
+
+| procedure | description |
+|---|---|
+| `Init() : BOOLEAN` | enter raw mode + alternate screen, install SIGWINCH handler, create the desktop.  FALSE if stdin is not a tty. |
+| `Done` | leave the alternate screen and restore the terminal. |
+| `Cols() / Rows() : INTEGER` | current screen size. |
+| `Resized() : BOOLEAN` | TRUE if the terminal size changed or SIGWINCH fired since the last call (also re-clears the screen). Call it in your loop. |
+| `Finish` | `DrawTree` + `Present` (redraw everything and flush). |
+| `DrawTree` | draw the desktop, all views and the menu bar into the cell buffer. |
+| `Present` | flush the diff to the terminal. |
+
+## Colours and attributes
+
+16 colours: `Black Blue Green Cyan Red Magenta Brown LightGray DarkGray
+LightBlue LightGreen LightCyan LightRed LightMagenta Yellow White`.
+`A(fg, bg : INTEGER; blink : BOOLEAN) : Attr` builds an attribute.
+Frame styles: `SingleFrame`, `DoubleFrame`.
+
+## Low-level screen
+
+`Clear(attr)`, `Fill(x,y,w,h, cp, attr)`, `DrawCh(x,y,cp,attr)`,
+`DrawText(x,y, s, attr)`, `DrawBox(r, style, attr)`, `DrawShadow(r, attr)`,
+`SetCursor(x,y)`, `HideCursor`.  `cp` is a Unicode code point (`CARDINAL`);
+ASCII text can use `VAL(CARDINAL, ORD(ch))`.
+
+## Text helpers
+
+`TextLen(s) : INTEGER`, `CopyText(dest, s)`, `AppendText(dest, s)`,
+`PadText(dest, s, width)`, `IntToText(dest, n, width)`.
+
+## Events
+
+```
+TYPE Event = RECORD
+       kind : EventKind;        (* evNone evKey evMouse *)
+       key  : INTEGER;          (* kChar kUp kDown kLeft kRight kHome kEnd
+                                   kPgUp kPgDn kIns kDel kEnter kEsc kTab
+                                   kBack kSpace kF1..kF12 kCtrlC kNone *)
+       ch   : CARDINAL;         (* code point for kChar *)
+       ctrl : BOOLEAN;          (* e.g. Shift-Tab gives kTab with ctrl=TRUE *)
+       mx, my : INTEGER;        (* mouse position, 0-based *)
+       mbtn : INTEGER;          (* 1 left, 2 middle, 3 right; held button during motion *)
+       mpressed, mreleased : BOOLEAN;
+       mwheel : INTEGER;        (* -1 up, +1 down *)
+     END;
+```
+
+`ReadEvent(e) : BOOLEAN` waits up to ~0.1 s and returns FALSE on timeout.
+
+`HandleEvent(e) : INTEGER` dispatches one event to the menu bar, the modal
+dialog (if any) or the desktop, and returns a command: `cmNone`, `cmClose`,
+or the tag of the activated control/menu item.  `Sender() : ViewPtr` returns
+the view that produced it (`NIL` for menus).
+
+Built-in commands: `cmNone cmClose cmOK cmCancel cmYes cmNo`.
+
+## View tree
+
+```
+TYPE VKind = (VGroup, VWindow, VText, VFrame, VInput, VButton,
+              VCheck, VRadio, VList, VScroll);
+     View = RECORD ... END;  ViewPtr = POINTER TO View;
+```
+
+| procedure | description |
+|---|---|
+| `NewView(kind, x,y,w,h) : ViewPtr` | allocate a view (absolute screen coordinates). |
+| `AddView(parent, child)` | append a child to a group/window. |
+| `DelView(v)` | remove a view and its children. |
+| `SetText(v, s)` / `SetTag(v, n)` | set text / command tag. |
+| `Desk() : ViewPtr` | the desktop group; add top-level windows here. |
+| `BringToFront(v)` | raise within its parent (z-order). |
+| `FocusView(v)` / `FocusNext(backwards)` | set/advance keyboard focus. |
+| `MoveViewBy(v, dx, dy)` | move a view and its descendants. |
+
+Fields are public: `v^.rect`, `v^.text`, `v^.flags`, `v^.value`,
+`v^.minv`, `v^.maxv`, `v^.tag`, `v^.list`, `v^.focus`.  Flags:
+`vfDisabled`, `vfSelected`, `vfFramed`.
+
+Widget kinds: `VText` (label), `VFrame` (box), `VInput`, `VButton`,
+`VCheck`, `VRadio`, `VList` (needs `v^.list : ListDataPtr`), `VScroll`
+(`v^.value/minv/maxv`), `VWindow`, `VGroup`.
+
+```
+TYPE ListData = RECORD
+         items : ARRAY [0..255] OF ARRAY [0..63] OF CHAR;
+         count : INTEGER;
+     END;  ListDataPtr = POINTER TO ListData;
+```
+
+## Menus
+
+`MenuBarInit`, `MenuAdd(title)`, `MenuItemAdd(text, tag)`, `MenuSep`,
+`MenuBar(show : BOOLEAN)`.  The menu bar is drawn on row 0; selecting an
+item makes `HandleEvent` return its tag.  Mouse: click a title, click an
+item.  Keyboard: `F10`, arrows, Enter/Space, Esc.
+
+## Dialogs
+
+Build a centred modal window, add controls, then run it:
+
+```
+tv.Dialog("Options", 44, 12);
+iName := tv.DlgInput("Name:", "Modula-2");   (* -> field index *)
+cOn   := tv.DlgCheck("Enabled", TRUE);
+rFast := tv.DlgRadio("Fast", TRUE);          (* radios auto-exclusive *)
+rSafe := tv.DlgRadio("Safe", FALSE);
+tv.DlgButton("OK", tv.cmOK);
+tv.DlgButton("Cancel", tv.cmCancel);
+cmd := tv.DlgRun;                             (* blocks; returns the tag *)
+tv.DlgTextAt(iName, s);                       (* read an input *)
+n := tv.DlgValue(cOn);                        (* 1 if selected *)
+```
+
+`DlgRun` returns the pressed button's tag, `cmCancel` on Esc, `cmClose` from
+the close box.
+
+## Message boxes
+
+`MessageBox(title, text, kind) : INTEGER` where `kind` is `mbOK`,
+`mbOKCancel` or `mbYesNo`; returns `cmOK`, `cmCancel`, `cmYes` or `cmNo`.
+`Message(title, text)` is shorthand for `MessageBox(..., mbOK)`.
+
+## Building against the library
+
+    gm2 -fiso -c tvtty.def
+    gm2 -fiso -I. -c tv.mod
+    gm2 -fiso -I. tv.o myapp.mod -o myapp

+ 10 - 3
tvision-m2/README.md

@@ -26,7 +26,10 @@ the terminal in raw mode and to query its size (`termios`, `ioctl`, `read`,
     gm2 -fiso -I. tv.o demo.mod -o demo
     ./demo
 
-or simply `make`.  Run it from a real terminal; `Esc` exits.
+    make -C examples
+
+or simply `make`.  Run it from a real terminal; `Esc` exits.  See
+`TUTORIAL.md` to write your own and `API.md` for the full reference.
 
 ## What works
 
@@ -48,7 +51,10 @@ or simply `make`.  Run it from a real terminal; `Esc` exits.
 - Mouse: xterm SGR reporting - click to focus/press, drag window titles,
   click list rows, wheel-scroll lists.
 - Diffed redraw (only changed cells are written).
-- Modal dialog helper (`Message`), used by the demo's OK button.
+- Generic modal dialog builder (`Dialog`/`DlgInput`/`DlgCheck`/`DlgRadio`/
+  `DlgButton`/`DlgRun`/`DlgTextAt`/`DlgValue`) and `MessageBox` (OK /
+  OK-Cancel / Yes-No), plus the `Message` shorthand.
+- SIGWINCH-driven resize detection (`Resized`), no polling required.
 - Menu bar with drop-down menus: click a title (or press `F10`), navigate
   with Left/Right/Up/Down, select with Enter/Space or a mouse click;
   item separators (`MenuSep`) are supported.  The selected item's tag is
@@ -56,8 +62,9 @@ or simply `make`.  Run it from a real terminal; `Esc` exits.
 
 ## Roadmap ideas
 
-- Modal `Dialog` with arbitrary controls; form validation.
+- Form validation and a file/directory dialog.
 - Menu item hotkeys (Alt+letter) and check/radio menu items.
+- A tigr graphics backend so the same UI can run in a window.
 - Nested clipping groups and a desktop with many windows / tiling.
 - More widgets: multi-line editor, tabs, combo box, progress bar.
 - `SIGWINCH`-driven relayout (today the app polls the size).

+ 8 - 2
tvision-m2/SUMMARY.md

@@ -40,7 +40,12 @@ from the captured terminal stream).
   F1-F12, Enter, Space, Backspace, Esc, Ctrl-C.
 - Mouse: xterm SGR reporting (mode 1002/1006) - click to focus/press, drag
   titles, click list rows, wheel scroll.
-- Modal `Message` dialog with its own event loop.
+- Generic modal dialog builder (`Dialog`/`Dlg*`/`DlgRun`) with inputs,
+  check boxes (self-exclusive radio groups) and tagged buttons; `MessageBox`
+  (OK / OK-Cancel / Yes-No) and the `Message` shorthand.
+- SIGWINCH-driven resize detection via a libc `signal` handler.
+- Example programs (`empty`, `window`, `dialog`, `menus`); API reference
+  (`API.md`) and tutorial (`TUTORIAL.md`).
 - Menu bar with drop-down menus: click or `F10` to open, Left/Right switch
   menus, Up/Down move, Enter/Space or click selects (returns the item tag);
   separators supported.
@@ -59,8 +64,9 @@ Driving the demo through a pty and reconstructing the screen:
 
 ## Known limitations / next steps
 
-- `Message` is the only dialog helper; no generic form dialog.
 - Menu items have no hotkeys; no check/radio menu items.
+- No form validation or file/directory dialog.
+- No tigr graphics backend yet.
 - Windows are not resizable; no hover highlight.
 - Terminal size is polled (no `SIGWINCH`-driven relayout).
 - Nested clipping groups beyond windows are untested.

+ 158 - 0
tvision-m2/TUTORIAL.md

@@ -0,0 +1,158 @@
+# tvision-m2 - tutorial
+
+Build the library once:
+
+    cd tvision-m2
+    gm2 -fiso -c tvtty.def
+    gm2 -fiso -I. -c tv.mod
+
+Each program is then:
+
+    gm2 -fiso -I. tv.o myapp.mod -o myapp
+    ./myapp          # run from a real terminal
+
+## 1. The smallest program
+
+An empty blue desktop with a status line; `Esc` quits.
+
+```modula2
+MODULE hello;
+IMPORT tv;
+VAR e : tv.Event; quit : BOOLEAN; st : tv.ViewPtr;
+BEGIN
+    IF NOT tv.Init() THEN HALT END;
+    st := tv.NewView(tv.VText, 0, tv.Rows()-1, tv.Cols(), 1);
+    tv.SetText(st, " Hello - press Esc");
+    tv.AddView(tv.Desk(), st);
+    tv.Finish;
+    quit := FALSE;
+    WHILE NOT quit DO
+        IF tv.Resized() THEN tv.Finish END;
+        IF tv.ReadEvent(e) THEN
+            IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN quit := TRUE END;
+            IF NOT quit THEN tv.Finish END
+        END
+    END;
+    tv.Done
+END hello.
+```
+
+The shape of every program: `Init`; add views to `Desk()`; loop calling
+`Resized` and `ReadEvent`; redraw with `Finish`; `Done` on exit.
+
+## 2. A window with widgets
+
+Views use absolute screen coordinates.  `NewView(kind, x, y, w, h)` creates
+one; `AddView(parent, child)` attaches it.
+
+```modula2
+w := tv.NewView(tv.VWindow, 3, 2, 50, 12);
+tv.SetText(w, "Editor");                     (* window title *)
+
+t := tv.NewView(tv.VText, 5, 4, 20, 1);
+tv.SetText(t, "Name:"); tv.AddView(w, t);
+
+input := tv.NewView(tv.VInput, 12, 4, 34, 1);
+tv.SetText(input, "Modula-2"); tv.AddView(w, input);
+
+b := tv.NewView(tv.VButton, 12, 10, 8, 1);
+tv.SetText(b, "OK"); tv.SetTag(b, tv.cmOK); tv.AddView(w, b);
+
+tv.AddView(tv.Desk(), w);
+tv.FocusView(input);
+```
+
+A `VWindow` clips its children, can be dragged by its title row and closed
+with the ` X ` box.  `Tab`/arrows move focus; inputs are edited with the
+usual keys.
+
+## 3. Reacting to commands
+
+`HandleEvent` returns `cmNone`, `cmClose`, or the tag you gave a control:
+
+```modula2
+cmd := tv.HandleEvent(e);
+IF cmd = tv.cmClose THEN
+    IF tv.Sender() # NIL THEN tv.DelView(tv.Sender()) END
+ELSIF cmd = tv.cmOK THEN
+    tv.Message("Info", "OK pressed")
+END;
+```
+
+Use custom tags >= 100 to distinguish your own buttons.
+
+## 4. Menus
+
+```modula2
+CONST mQuit = 101; mAbout = 102;
+
+tv.MenuBarInit;
+tv.MenuAdd("File");
+tv.MenuItemAdd("Open", 100);
+tv.MenuSep;
+tv.MenuItemAdd("Quit", mQuit);
+tv.MenuAdd("Help");
+tv.MenuItemAdd("About", mAbout);
+```
+
+The bar appears on row 0.  Clicking a title opens the drop-down; `F10`,
+arrows, Enter and Esc work too.  Selecting an item makes `HandleEvent`
+return its tag:
+
+```modula2
+IF cmd = mQuit THEN quit := TRUE
+ELSIF cmd = mAbout THEN
+    IF tv.MessageBox("About", "my app", tv.mbOK) = tv.cmOK THEN END
+END;
+```
+
+## 5. Dialogs
+
+```modula2
+VAR iName, cOn, cmd : INTEGER; s : ARRAY [0..127] OF CHAR;
+...
+tv.Dialog("Options", 44, 12);
+iName := tv.DlgInput("Name:", "Modula-2");
+cOn   := tv.DlgCheck("Enabled", TRUE);
+tv.DlgButton("OK", tv.cmOK);
+tv.DlgButton("Cancel", tv.cmCancel);
+cmd := tv.DlgRun;                 (* blocks until a button / Esc *)
+IF cmd = tv.cmOK THEN
+    tv.DlgTextAt(iName, s);       (* read the field *)
+    IF tv.DlgValue(cOn) = 1 THEN tv.Message("Info", "Enabled") END
+END;
+```
+
+Radios added with `DlgRadio` are automatically exclusive.  For simple
+"notice" dialogs use `Message` / `MessageBox`.
+
+## 6. Lists and mouse
+
+`VList` shows a caller-owned `ListData`:
+
+```modula2
+VAR data : tv.ListData;
+data.items[0] := "Alpha"; data.items[1] := "Bravo"; data.count := 2;
+list := tv.NewView(tv.VList, 5, 4, 20, 6);
+list^.list := CAST(tv.ListDataPtr, ADR(data));
+tv.AddView(w, list);
+```
+
+The mouse works automatically: click to focus/press buttons, drag window
+titles, click list rows and scroll with the wheel.  You only need
+`HandleEvent`; the toolkit does the hit-testing.
+
+## 7. More examples
+
+See `examples/` (`empty`, `window`, `dialog`, `menus`) and build them with
+`make`.  See `API.md` for the full reference.
+
+## Pitfalls (GNU Modula-2)
+
+- Pointer fields need `^` (`v^.rect`).
+- Value-returning no-arg procedures need `()` in a `.def`
+  (`Cols() : INTEGER`).
+- `INTEGER` compared with `HIGH(array)` needs `VAL(INTEGER, HIGH(a))`.
+- A `BEGIN ... END` block cannot be a `CASE` branch - use a helper.
+- `Present` is diffed; if you draw outside the loop (e.g. before `Init`),
+  the cells do not exist yet.

binární
tvision-m2/demo


+ 14 - 0
tvision-m2/examples/Makefile

@@ -0,0 +1,14 @@
+GM2 = gm2
+FLAGS = -fiso -I ..
+TV = ../tv.o
+PROGS = empty window dialog menus
+
+all: $(PROGS)
+
+%: %.mod $(TV)
+	$(GM2) $(FLAGS) $(TV) $< -o $@
+
+clean:
+	rm -f $(PROGS)
+
+.PHONY: all clean

binární
tvision-m2/examples/dialog


+ 61 - 0
tvision-m2/examples/dialog.mod

@@ -0,0 +1,61 @@
+MODULE dialog;
+
+(* Press D for a form dialog, M for a message box, Esc to quit. *)
+
+FROM InOut IMPORT WriteString, WriteLn;
+IMPORT tv;
+
+VAR e : tv.Event; quit : BOOLEAN; st : tv.ViewPtr;
+    iName, iCity, cOn, rFast, rSafe, cmd : INTEGER;
+    s, msg : ARRAY [0..127] OF CHAR;
+
+BEGIN
+    IF NOT tv.Init() THEN WriteString("not a tty"); WriteLn; HALT END;
+    st := tv.NewView(tv.VText, 0, tv.Rows() - 1, tv.Cols(), 1);
+    tv.SetText(st, " D: dialog   M: message box   Esc: quit");
+    tv.AddView(tv.Desk(), st);
+    tv.Finish;
+
+    quit := FALSE;
+    WHILE NOT quit DO
+        IF tv.Resized() THEN tv.Finish END;
+        IF tv.ReadEvent(e) THEN
+            IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN
+                quit := TRUE
+            ELSIF (e.kind = tv.evKey) AND (e.key = tv.kChar) AND
+                  ((e.ch = VAL(CARDINAL, ORD('d'))) OR (e.ch = VAL(CARDINAL, ORD('D')))) THEN
+                tv.Dialog("Options", 44, 12);
+                iName := tv.DlgInput("Name:", "Modula-2");
+                iCity := tv.DlgInput("City:", "Paris");
+                cOn := tv.DlgCheck("Enabled", TRUE);
+                rFast := tv.DlgRadio("Fast mode", TRUE);
+                rSafe := tv.DlgRadio("Safe mode", FALSE);
+                tv.DlgButton("OK", tv.cmOK);
+                tv.DlgButton("Cancel", tv.cmCancel);
+                cmd := tv.DlgRun();
+                IF cmd = tv.cmOK THEN
+                    tv.DlgTextAt(iName, s);
+                    tv.CopyText(msg, "Name=");
+                    tv.AppendText(msg, s);
+                    tv.AppendText(msg, "  Enabled=");
+                    IF tv.DlgValue(cOn) = 1 THEN tv.AppendText(msg, "yes")
+                    ELSE tv.AppendText(msg, "no") END;
+                    tv.AppendText(msg, "  Fast=");
+                    IF tv.DlgValue(rFast) = 1 THEN tv.AppendText(msg, "yes")
+                    ELSE tv.AppendText(msg, "no") END;
+                    IF tv.MessageBox("Result", msg, tv.mbOK) = tv.cmOK THEN END
+                END
+            ELSIF (e.kind = tv.evKey) AND (e.key = tv.kChar) AND
+                  ((e.ch = VAL(CARDINAL, ORD('m'))) OR (e.ch = VAL(CARDINAL, ORD('M')))) THEN
+                IF tv.MessageBox("Question", "Proceed with the operation?",
+                                 tv.mbYesNo) = tv.cmYes THEN
+                    tv.Message("Info", "You chose Yes.")
+                ELSE
+                    tv.Message("Info", "You chose No.")
+                END
+            END;
+            IF NOT quit THEN tv.Finish END
+        END
+    END;
+    tv.Done
+END dialog.

binární
tvision-m2/examples/empty


+ 25 - 0
tvision-m2/examples/empty.mod

@@ -0,0 +1,25 @@
+MODULE empty;
+
+(* Smallest possible program: an empty desktop with a status line. *)
+
+FROM InOut IMPORT WriteString, WriteLn;
+IMPORT tv;
+
+VAR e : tv.Event; quit : BOOLEAN; st : tv.ViewPtr;
+
+BEGIN
+    IF NOT tv.Init() THEN WriteString("not a tty"); WriteLn; HALT END;
+    st := tv.NewView(tv.VText, 0, tv.Rows() - 1, tv.Cols(), 1);
+    tv.SetText(st, " Empty TVision app - press Esc to quit");
+    tv.AddView(tv.Desk(), st);
+    tv.Finish;
+    quit := FALSE;
+    WHILE NOT quit DO
+        IF tv.Resized() THEN tv.Finish END;
+        IF tv.ReadEvent(e) THEN
+            IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN quit := TRUE END;
+            IF NOT quit THEN tv.Finish END
+        END
+    END;
+    tv.Done
+END empty.

binární
tvision-m2/examples/menus


+ 50 - 0
tvision-m2/examples/menus.mod

@@ -0,0 +1,50 @@
+MODULE menus;
+
+(* Menu bar demo: File / Edit / Help with a status line. *)
+
+FROM InOut IMPORT WriteString, WriteLn;
+IMPORT tv;
+
+CONST mNew = 100; mQuit = 101; mCut = 102; mCopy = 103; mAbout = 104;
+
+VAR e : tv.Event; quit : BOOLEAN; st : tv.ViewPtr; cmd : INTEGER;
+    msg, tmp : ARRAY [0..95] OF CHAR;
+
+PROCEDURE Show(s : ARRAY OF CHAR);
+BEGIN tv.SetText(st, s); tv.Finish END Show;
+
+BEGIN
+    IF NOT tv.Init() THEN WriteString("not a tty"); WriteLn; HALT END;
+    st := tv.NewView(tv.VText, 0, tv.Rows() - 1, tv.Cols(), 1);
+    tv.AddView(tv.Desk(), st);
+
+    tv.MenuBarInit;
+    tv.MenuAdd("File"); tv.MenuItemAdd("New", mNew);
+    tv.MenuSep; tv.MenuItemAdd("Quit", mQuit);
+    tv.MenuAdd("Edit"); tv.MenuItemAdd("Cut", mCut); tv.MenuItemAdd("Copy", mCopy);
+    tv.MenuAdd("Help"); tv.MenuItemAdd("About", mAbout);
+
+    Show(" Choose a menu with the mouse or F10");
+    quit := FALSE;
+    WHILE NOT quit DO
+        IF tv.Resized() THEN tv.Finish END;
+        IF tv.ReadEvent(e) THEN
+            IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN quit := TRUE
+            ELSE
+                cmd := tv.HandleEvent(e);
+                IF cmd = mQuit THEN quit := TRUE
+                ELSIF cmd = mAbout THEN
+                    IF tv.MessageBox("About", "tvision-m2", tv.mbOK) = tv.cmOK THEN END;
+                    Show(" About closed")
+                ELSIF cmd >= 100 THEN
+                    tv.CopyText(msg, " Command #");
+                    tv.IntToText(tmp, cmd, 0);
+                    tv.AppendText(msg, tmp);
+                    Show(msg)
+                END
+            END;
+            IF NOT quit THEN tv.Finish END
+        END
+    END;
+    tv.Done
+END menus.

binární
tvision-m2/examples/window


+ 44 - 0
tvision-m2/examples/window.mod

@@ -0,0 +1,44 @@
+MODULE window;
+
+(* One window with text and a Close button. *)
+
+FROM InOut IMPORT WriteString, WriteLn;
+IMPORT tv;
+
+VAR e : tv.Event; quit : BOOLEAN; w, t, b, st : tv.ViewPtr; cmd : INTEGER;
+
+BEGIN
+    IF NOT tv.Init() THEN WriteString("not a tty"); WriteLn; HALT END;
+
+    st := tv.NewView(tv.VText, 0, tv.Rows() - 1, tv.Cols(), 1);
+    tv.SetText(st, " Drag the title bar, click Close or press Esc");
+    tv.AddView(tv.Desk(), st);
+
+    w := tv.NewView(tv.VWindow, 6, 3, 46, 10);
+    tv.SetText(w, "Hello");
+    t := tv.NewView(tv.VText, 8, 5, 42, 1);
+    tv.SetText(t, "This is a TVision-style window.");
+    tv.AddView(w, t);
+    t := tv.NewView(tv.VText, 8, 6, 42, 1);
+    tv.SetText(t, "Move it with the mouse.");
+    tv.AddView(w, t);
+    b := tv.NewView(tv.VButton, 8, 9, 10, 1);
+    tv.SetText(b, "Close"); tv.SetTag(b, tv.cmClose); tv.AddView(w, b);
+    tv.AddView(tv.Desk(), w);
+    tv.FocusView(b);
+
+    tv.Finish;
+    quit := FALSE;
+    WHILE NOT quit DO
+        IF tv.Resized() THEN tv.Finish END;
+        IF tv.ReadEvent(e) THEN
+            IF (e.kind = tv.evKey) AND (e.key = tv.kEsc) THEN quit := TRUE
+            ELSE
+                cmd := tv.HandleEvent(e);
+                IF (cmd = tv.cmClose) OR (cmd = tv.cmOK) THEN quit := TRUE END
+            END;
+            IF NOT quit THEN tv.Finish END
+        END
+    END;
+    tv.Done
+END window.

+ 24 - 1
tvision-m2/tv.def

@@ -44,6 +44,11 @@ CONST
     cmYes    = 4;
     cmNo     = 5;
 
+    (* message box kinds *)
+    mbOK       = 0;
+    mbOKCancel = 1;
+    mbYesNo    = 2;
+
     (* view flags *)
     vfDisabled = 1;
     vfSelected = 2;
@@ -154,7 +159,25 @@ PROCEDURE MenuItemAdd (text : ARRAY OF CHAR; tag : INTEGER);
 PROCEDURE MenuSep;
 PROCEDURE MenuBar (show : BOOLEAN);
 
-(* Modal helper: shows a small centered box with the text and an OK button. *)
+(* Generic modal dialog builder.  Dialog() starts a centered window; the
+   Dlg* procedures add controls top-to-bottom (inputs/checks/radios get a
+   field index); DlgRun() shows it modally and returns the command of the
+   button that was pressed (its tag), cmCancel on Esc, or cmClose. *)
+PROCEDURE Dialog (title : ARRAY OF CHAR; w, h : INTEGER);
+PROCEDURE DlgLabel (text : ARRAY OF CHAR);
+PROCEDURE DlgInput (label : ARRAY OF CHAR; initial : ARRAY OF CHAR) : INTEGER;
+PROCEDURE DlgCheck (label : ARRAY OF CHAR; checked : BOOLEAN) : INTEGER;
+PROCEDURE DlgRadio (label : ARRAY OF CHAR; selected : BOOLEAN) : INTEGER;
+PROCEDURE DlgButton (text : ARRAY OF CHAR; tag : INTEGER);
+PROCEDURE DlgRun () : INTEGER;
+PROCEDURE DlgTextAt (idx : INTEGER; VAR s : ARRAY OF CHAR);
+PROCEDURE DlgValue (idx : INTEGER) : INTEGER;   (* 1 if check/radio selected *)
+
+(* Modal message box; returns cmOK/cmCancel/cmYes/cmNo. *)
+PROCEDURE MessageBox (title : ARRAY OF CHAR; text : ARRAY OF CHAR;
+                      kind : INTEGER) : INTEGER;
+
+(* Modal helper: a message box with an OK button. *)
 PROCEDURE Message (title : ARRAY OF CHAR; text : ARRAY OF CHAR);
 
 END tv.

+ 214 - 36
tvision-m2/tv.mod

@@ -4,7 +4,7 @@ IMPLEMENTATION MODULE tv;
    See tv.def.  Pure ISO GNU Modula-2 apart from the libc binding tvtty.def. *)
 
 FROM SYSTEM IMPORT ADDRESS, ADR, ADDADR, CAST, BYTE;
-FROM tvtty IMPORT tcgetattr, tcsetattr, ioctl, read, write;
+FROM tvtty IMPORT tcgetattr, tcsetattr, ioctl, read, write, signal;
 
 CONST
     MAXROWS  = 120;
@@ -78,6 +78,15 @@ VAR
     menuSel   : INTEGER;
     menuShown : BOOLEAN;
 
+    winch    : BOOLEAN;
+    oldWinch : ADDRESS;
+
+    dlg        : ViewPtr;
+    dlgW       : INTEGER;
+    dlgWX, dlgWY, dlgX, dlgY, dlgInputX, dlgBtnX, dlgBtnY : INTEGER;
+    dlgItems   : ARRAY [0..31] OF ViewPtr;
+    dlgCount   : INTEGER;
+
 (*========================================================================*)
 (*  libc struct helpers                                                    *)
 (*========================================================================*)
@@ -200,6 +209,11 @@ END FlushOut;
 (*  Terminal setup                                                         *)
 (*========================================================================*)
 
+PROCEDURE OnWinch (sig : INTEGER);
+BEGIN
+    winch := TRUE
+END OnWinch;
+
 PROCEDURE Init() : BOOLEAN;
     VAR lf, iff : CARDINAL; i : INTEGER;
 BEGIN
@@ -222,6 +236,9 @@ BEGIN
     clipN := 0;
     modal := NIL; dragWin := NIL; lastSender := NIL;
     menuCount := 0; menuOpen := -1; menuSel := 0; menuShown := FALSE;
+    winch := FALSE;
+    oldWinch := signal(28, ADR(OnWinch));
+    dlg := NIL; dlgCount := 0;
     (* reset view pool *)
     FOR i := 0 TO MAXVIEWS - 1 DO used[i] := FALSE END;
     desk := NIL;
@@ -255,10 +272,12 @@ PROCEDURE Rows() : INTEGER;
 BEGIN RETURN rows END Rows;
 
 PROCEDURE Resized() : BOOLEAN;
-    VAR r, c : INTEGER;
+    VAR r, c : INTEGER; changed : BOOLEAN;
 BEGIN
     GetWinSize(r, c);
-    IF (r # rows) OR (c # cols) THEN
+    changed := winch OR (r # rows) OR (c # cols);
+    winch := FALSE;
+    IF changed THEN
         rows := r; cols := c;
         prevValid := FALSE;
         Clear(A(LightGray, Blue, FALSE));
@@ -1358,6 +1377,23 @@ BEGIN
     END
 END EditKey;
 
+PROCEDURE SelectRadio(v : ViewPtr);
+    VAR c : ViewPtr;
+BEGIN
+    IF (v = NIL) OR (v^.parent = NIL) THEN RETURN END;
+    c := v^.parent^.first;
+    WHILE c # NIL DO
+        IF (c # v) AND (c^.kind = VRadio) AND
+           ((c^.flags DIV vfSelected) MOD 2 = 1) THEN
+            c^.flags := c^.flags - vfSelected
+        END;
+        c := c^.next
+    END;
+    IF (v^.flags DIV vfSelected) MOD 2 = 0 THEN
+        v^.flags := v^.flags + vfSelected
+    END
+END SelectRadio;
+
 PROCEDURE WidgetKey(v : ViewPtr; VAR e : Event) : INTEGER;
     VAR cmd : INTEGER;
 BEGIN
@@ -1375,7 +1411,10 @@ BEGIN
             cmd := v^.tag
         END
     ELSIF v^.kind = VRadio THEN
-        IF (e.key = kEnter) OR (e.key = kSpace) THEN cmd := v^.tag END
+        IF (e.key = kEnter) OR (e.key = kSpace) THEN
+            SelectRadio(v);
+            cmd := v^.tag
+        END
     ELSIF v^.kind = VList THEN
         IF e.key = kUp THEN
             IF v^.value > 0 THEN v^.value := v^.value - 1 END;
@@ -1424,7 +1463,10 @@ BEGIN
             cmd := v^.tag
         END
     ELSIF v^.kind = VRadio THEN
-        IF e.mreleased THEN cmd := v^.tag END
+        IF e.mreleased THEN
+            SelectRadio(v);
+            cmd := v^.tag
+        END
     ELSIF v^.kind = VList THEN
         IF e.mwheel # 0 THEN
             IF (e.mwheel < 0) AND (v^.pos > 0) THEN v^.pos := v^.pos - 1 END;
@@ -1563,42 +1605,178 @@ BEGIN RETURN lastSender END Sender;
 (*  Modal message box                                                      *)
 (*========================================================================*)
 
-PROCEDURE Message (title : ARRAY OF CHAR; text : ARRAY OF CHAR);
-    VAR w, t, b : ViewPtr; e : Event;
-        tw, ww, wx, wy : INTEGER; cmd : INTEGER; done : BOOLEAN;
-BEGIN
-    tw := TextLen(text);
-    ww := tw + 6;
-    IF ww < 24 THEN ww := 24 END;
-    IF ww > cols - 4 THEN ww := cols - 4 END;
-    wx := (cols - ww) DIV 2; wy := (rows - 7) DIV 2;
-    w := NewView(VWindow, wx, wy, ww, 7);
-    SetText(w, title);
-    t := NewView(VText, wx + 2, wy + 2, ww - 4, 1);
-    SetText(t, text);
-    AddView(w, t);
-    b := NewView(VButton, wx + (ww - 8) DIV 2, wy + 4, 8, 1);
-    SetText(b, "OK");
-    SetTag(b, cmOK);
-    AddView(w, b);
-    AddView(desk, w);
-    BringToFront(w);
-    FocusView(b);
-    modal := w;
-    done := FALSE;
+(*========================================================================*)
+(*  Generic dialog builder                                                 *)
+(*========================================================================*)
+
+PROCEDURE NewDlgItem (k : VKind; x, y, w, h : INTEGER;
+                      text : ARRAY OF CHAR) : ViewPtr;
+    VAR v : ViewPtr;
+BEGIN
+    v := NewView(k, x, y, w, h);
+    SetText(v, text);
+    AddView(dlg, v);
+    IF dlgCount < 32 THEN
+        dlgItems[dlgCount] := v;
+        dlgCount := dlgCount + 1
+    END;
+    RETURN v
+END NewDlgItem;
+
+PROCEDURE Dialog (title : ARRAY OF CHAR; w, h : INTEGER);
+    VAR x, y : INTEGER;
+BEGIN
+    x := (cols - w) DIV 2;
+    y := (rows - h) DIV 2;
+    IF x < 0 THEN x := 0 END;
+    IF y < 0 THEN y := 0 END;
+    dlg := NewView(VWindow, x, y, w, h);
+    SetText(dlg, title);
+    dlgW := w; dlgWX := x; dlgWY := y;
+    dlgX := x + 2; dlgY := y + 2; dlgInputX := x + 18;
+    dlgBtnX := x + 2; dlgBtnY := y + h - 2;
+    dlgCount := 0
+END Dialog;
+
+PROCEDURE DlgLabel (text : ARRAY OF CHAR);
+    VAR v : ViewPtr;
+BEGIN
+    IF dlg = NIL THEN RETURN END;
+    v := NewView(VText, dlgX, dlgY, dlgW - 4, 1);
+    SetText(v, text);
+    AddView(dlg, v);
+    dlgY := dlgY + 1
+END DlgLabel;
+
+PROCEDURE DlgInput (label : ARRAY OF CHAR; initial : ARRAY OF CHAR) : INTEGER;
+    VAR lv : ViewPtr; iw : INTEGER;
+BEGIN
+    IF dlg = NIL THEN RETURN -1 END;
+    IF TextLen(label) > 0 THEN
+        lv := NewView(VText, dlgX, dlgY, dlgInputX - dlgX - 1, 1);
+        SetText(lv, label);
+        AddView(dlg, lv)
+    END;
+    iw := dlgWX + dlgW - 2 - dlgInputX;
+    IF iw < 8 THEN iw := 8 END;
+    IF NewDlgItem(VInput, dlgInputX, dlgY, iw, 1, initial) = NIL THEN END;
+    dlgY := dlgY + 2;
+    RETURN dlgCount - 1
+END DlgInput;
+
+PROCEDURE DlgCheck (label : ARRAY OF CHAR; checked : BOOLEAN) : INTEGER;
+    VAR v : ViewPtr; idx : INTEGER;
+BEGIN
+    IF dlg = NIL THEN RETURN -1 END;
+    v := NewDlgItem(VCheck, dlgX, dlgY, dlgW - 4, 1, label);
+    idx := dlgCount - 1;
+    IF (v # NIL) AND checked THEN v^.flags := v^.flags + vfSelected END;
+    dlgY := dlgY + 1;
+    RETURN idx
+END DlgCheck;
+
+PROCEDURE DlgRadio (label : ARRAY OF CHAR; selected : BOOLEAN) : INTEGER;
+    VAR v : ViewPtr; idx : INTEGER;
+BEGIN
+    IF dlg = NIL THEN RETURN -1 END;
+    v := NewDlgItem(VRadio, dlgX, dlgY, dlgW - 4, 1, label);
+    idx := dlgCount - 1;
+    IF (v # NIL) AND selected THEN v^.flags := v^.flags + vfSelected END;
+    dlgY := dlgY + 1;
+    RETURN idx
+END DlgRadio;
+
+PROCEDURE DlgButton (text : ARRAY OF CHAR; tag : INTEGER);
+    VAR v : ViewPtr; w : INTEGER;
+BEGIN
+    IF dlg = NIL THEN RETURN END;
+    w := TextLen(text) + 4;
+    v := NewView(VButton, dlgBtnX, dlgBtnY, w, 1);
+    SetText(v, text);
+    SetTag(v, tag);
+    AddView(dlg, v);
+    dlgBtnX := dlgBtnX + w + 2
+END DlgButton;
+
+PROCEDURE DlgTextAt (idx : INTEGER; VAR s : ARRAY OF CHAR);
+BEGIN
+    IF (idx >= 0) AND (idx < dlgCount) AND (dlgItems[idx]^.kind = VInput) THEN
+        CopyText(s, dlgItems[idx]^.text)
+    ELSE
+        s[0] := 0C
+    END
+END DlgTextAt;
+
+PROCEDURE DlgValue (idx : INTEGER) : INTEGER;
+BEGIN
+    IF (idx >= 0) AND (idx < dlgCount) AND
+       ((dlgItems[idx]^.flags DIV vfSelected) MOD 2 = 1) THEN
+        RETURN 1
+    END;
+    RETURN 0
+END DlgValue;
+
+PROCEDURE DlgRun () : INTEGER;
+    VAR e : Event; cmd : INTEGER; done : BOOLEAN; f : ViewPtr;
+BEGIN
+    IF dlg = NIL THEN RETURN cmCancel END;
+    AddView(desk, dlg);
+    BringToFront(dlg);
+    f := dlg^.first;
+    WHILE (f # NIL) AND (NOT IsFocusable(f)) DO f := f^.next END;
+    IF f # NIL THEN FocusView(f) END;
+    modal := dlg;
+    done := FALSE; cmd := cmNone;
     WHILE NOT done DO
         Finish;
-        cmd := cmNone;
         IF ReadEvent(e) THEN
-            IF e.kind = evKey THEN
-                IF e.key = kEsc THEN cmd := cmOK END
-            END;
-            IF cmd = cmNone THEN cmd := HandleEvent(e) END
-        END;
-        IF (cmd = cmOK) OR (cmd = cmClose) THEN done := TRUE END
+            IF (e.kind = evKey) AND (e.key = kEsc) THEN
+                cmd := cmCancel; done := TRUE
+            ELSE
+                cmd := HandleEvent(e);
+                IF cmd # cmNone THEN done := TRUE END
+            END
+        END
     END;
     modal := NIL;
-    DelView(w)
+    DelView(dlg);
+    dlg := NIL;
+    RETURN cmd
+END DlgRun;
+
+PROCEDURE MessageBox (title : ARRAY OF CHAR; text : ARRAY OF CHAR;
+                      kind : INTEGER) : INTEGER;
+    VAR w, total, left : INTEGER; b1, b2 : ViewPtr;
+BEGIN
+    w := TextLen(text) + 6;
+    IF w < 30 THEN w := 30 END;
+    IF w > cols - 4 THEN w := cols - 4 END;
+    Dialog(title, w, 7);
+    DlgLabel(text);
+    b1 := NIL; b2 := NIL;
+    IF kind = mbOKCancel THEN
+        DlgButton("OK", cmOK); b1 := dlg^.last;
+        DlgButton("Cancel", cmCancel); b2 := dlg^.last
+    ELSIF kind = mbYesNo THEN
+        DlgButton("Yes", cmYes); b1 := dlg^.last;
+        DlgButton("No", cmNo); b2 := dlg^.last
+    ELSE
+        DlgButton("OK", cmOK); b1 := dlg^.last
+    END;
+    IF b1 # NIL THEN
+        IF b2 # NIL THEN total := b1^.rect.w + b2^.rect.w + 2
+        ELSE total := b1^.rect.w END;
+        left := dlgWX + (dlgW - total) DIV 2;
+        MoveViewBy(b1, left - b1^.rect.x, 0);
+        IF b2 # NIL THEN MoveViewBy(b2, left + b1^.rect.w + 2 - b2^.rect.x, 0) END
+    END;
+    RETURN DlgRun()
+END MessageBox;
+
+PROCEDURE Message (title : ARRAY OF CHAR; text : ARRAY OF CHAR);
+    VAR discard : INTEGER;
+BEGIN
+    discard := MessageBox(title, text, mbOK)
 END Message;
 
 END tv.

binární
tvision-m2/tv.o


+ 2 - 1
tvision-m2/tvtty.def

@@ -8,12 +8,13 @@ DEFINITION MODULE FOR "C" tvtty;
 
 FROM SYSTEM IMPORT ADDRESS;
 
-EXPORT UNQUALIFIED tcgetattr, tcsetattr, ioctl, read, write;
+EXPORT UNQUALIFIED tcgetattr, tcsetattr, ioctl, read, write, signal;
 
 PROCEDURE tcgetattr(fd : INTEGER; termios : ADDRESS) : INTEGER;
 PROCEDURE tcsetattr(fd, action : INTEGER; termios : ADDRESS) : INTEGER;
 PROCEDURE ioctl(fd : INTEGER; request : CARDINAL; arg : ADDRESS) : INTEGER;
 PROCEDURE read(fd : INTEGER; buf : ADDRESS; count : CARDINAL) : INTEGER;
 PROCEDURE write(fd : INTEGER; buf : ADDRESS; count : CARDINAL) : INTEGER;
+PROCEDURE signal(sig : INTEGER; handler : ADDRESS) : ADDRESS;
 
 END tvtty.