ncurses 6.2 - patch 20210904
[ncurses.git] / Ada95 / samples / ncurses2-demo_forms.adb
1 ------------------------------------------------------------------------------
2 --                                                                          --
3 --                       GNAT ncurses Binding Samples                       --
4 --                                                                          --
5 --                                 ncurses                                  --
6 --                                                                          --
7 --                                 B O D Y                                  --
8 --                                                                          --
9 ------------------------------------------------------------------------------
10 -- Copyright 2020,2021 Thomas E. Dickey                                     --
11 -- Copyright 2000-2011,2014 Free Software Foundation, Inc.                  --
12 --                                                                          --
13 -- Permission is hereby granted, free of charge, to any person obtaining a  --
14 -- copy of this software and associated documentation files (the            --
15 -- "Software"), to deal in the Software without restriction, including      --
16 -- without limitation the rights to use, copy, modify, merge, publish,      --
17 -- distribute, distribute with modifications, sublicense, and/or sell       --
18 -- copies of the Software, and to permit persons to whom the Software is    --
19 -- furnished to do so, subject to the following conditions:                 --
20 --                                                                          --
21 -- The above copyright notice and this permission notice shall be included  --
22 -- in all copies or substantial portions of the Software.                   --
23 --                                                                          --
24 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  --
25 -- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               --
26 -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   --
27 -- IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   --
28 -- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    --
29 -- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    --
30 -- THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               --
31 --                                                                          --
32 -- Except as contained in this notice, the name(s) of the above copyright   --
33 -- holders shall not be used in advertising or otherwise to promote the     --
34 -- sale, use or other dealings in this Software without prior written       --
35 -- authorization.                                                           --
36 ------------------------------------------------------------------------------
37 --  Author: Eugene V. Melaragno <aldomel@ix.netcom.com> 2000
38 --  Version Control
39 --  $Revision: 1.9 $
40 --  $Date: 2021/09/04 10:52:55 $
41 --  Binding Version 01.00
42 ------------------------------------------------------------------------------
43 with ncurses2.util; use ncurses2.util;
44 with Terminal_Interface.Curses; use Terminal_Interface.Curses;
45 with Terminal_Interface.Curses.Forms; use Terminal_Interface.Curses.Forms;
46 with Terminal_Interface.Curses.Forms.Field_User_Data;
47 with Ada.Characters.Handling;
48 with Ada.Strings;
49 with Ada.Strings.Bounded;
50
51 procedure ncurses2.demo_forms is
52    package BS is new Ada.Strings.Bounded.Generic_Bounded_Length (80);
53
54    type myptr is access Integer;
55
56    --  The C version stores a pointer in the userptr and
57    --  converts it into a long integer.
58    --  The correct, but inconvenient  way to do it is to use a
59    --  pointer to long and keep the pointer constant.
60    --  It just adds one memory piece to allocate and deallocate (not done here)
61
62    package StringData is new
63      Terminal_Interface.Curses.Forms.Field_User_Data (Integer, myptr);
64
65    function edit_secure (me : Field; c_in : Key_Code) return Key_Code;
66    function form_virtualize (f : Form; w : Window) return Key_Code;
67    function my_form_driver (f : Form; c : Key_Code) return Boolean;
68    function make_label (frow  : Line_Position;
69                         fcol  : Column_Position;
70                         label : String) return Field;
71    function make_field (frow   : Line_Position;
72                         fcol   : Column_Position;
73                         rows   : Line_Count;
74                         cols   : Column_Count;
75                         secure : Boolean) return Field;
76    procedure display_form (f : Form);
77    procedure erase_form (f : Form);
78
79    --  prints '*' instead of characters.
80    --  Not that this keeps a bug from the C version:
81    --  type in the psasword field then move off and back.
82    --  the cursor is at position one, but
83    --  this assumes it as at the end so text gets appended instead
84    --  of overwtitting.
85    function edit_secure (me : Field; c_in : Key_Code) return Key_Code is
86       rows, frow : Line_Position;
87       nrow : Natural;
88       cols, fcol : Column_Position;
89       nbuf : Buffer_Number;
90       c : Key_Code := c_in;
91       c2 :  Character;
92
93       use StringData;
94    begin
95       Info (me, rows, cols, frow, fcol, nrow, nbuf);
96       --  TODO         if result = Form_Ok and nbuf > 0 then
97       --  C version checked the return value
98       --  of Info, the Ada binding throws an exception I think.
99       if nbuf > 0 then
100          declare
101             temp : BS.Bounded_String;
102             temps : String (1 .. 10);
103             --  TODO Get_Buffer povides no information on the field length?
104             len : myptr;
105          begin
106             Get_Buffer (me, 1, Str => temps);
107             --  strcpy(temp, field_buffer(me, 1));
108             Get_User_Data (me, len);
109             temp := BS.To_Bounded_String (temps (1 .. len.all));
110             if c <= Key_Max then
111                c2 := Code_To_Char (c);
112                if Ada.Characters.Handling.Is_Graphic (c2) then
113                   BS.Append (temp, c2);
114                   len.all := len.all + 1;
115                   Set_Buffer (me, 1, BS.To_String (temp));
116                   c := Character'Pos ('*');
117                else
118                   c := 0;
119                end if;
120             else
121                case c is
122                   when  REQ_BEG_FIELD |
123                     REQ_CLR_EOF |
124                     REQ_CLR_EOL |
125                     REQ_DEL_LINE |
126                     REQ_DEL_WORD |
127                     REQ_DOWN_CHAR |
128                     REQ_END_FIELD |
129                     REQ_INS_CHAR |
130                     REQ_INS_LINE |
131                     REQ_LEFT_CHAR |
132                     REQ_NEW_LINE |
133                     REQ_NEXT_WORD |
134                     REQ_PREV_WORD |
135                     REQ_RIGHT_CHAR |
136                     REQ_UP_CHAR =>
137                      c := 0;         -- we don't want to do inline editing
138                   when REQ_CLR_FIELD =>
139                      if len.all /= 0 then
140                         temp := BS.To_Bounded_String ("");
141                         Set_Buffer (me, 1, BS.To_String (temp));
142                         len.all := 0;
143                      end if;
144
145                   when REQ_DEL_CHAR |
146                     REQ_DEL_PREV =>
147                      if len.all /= 0 then
148                         BS.Delete (temp, BS.Length (temp), BS.Length (temp));
149                         Set_Buffer (me, 1, BS.To_String (temp));
150                         len.all := len.all - 1;
151                      end if;
152                   when others => null;
153                end case;
154             end if;
155          end;
156       end if;
157       return c;
158    end edit_secure;
159
160    mode : Key_Code := REQ_INS_MODE;
161
162    function form_virtualize (f : Form; w : Window) return Key_Code is
163       type lookup_t is record
164          code : Key_Code;
165          result : Key_Code;
166          --  should be Form_Request_Code, but we need MAX_COMMAND + 1
167       end record;
168
169       lookup : constant array (Positive range <>) of lookup_t :=
170         (
171          (
172           Character'Pos ('A') mod 16#20#, REQ_NEXT_CHOICE
173           ),
174          (
175           Character'Pos ('B') mod 16#20#, REQ_PREV_WORD
176           ),
177          (
178           Character'Pos ('C') mod 16#20#, REQ_CLR_EOL
179           ),
180          (
181           Character'Pos ('D') mod 16#20#, REQ_DOWN_FIELD
182           ),
183          (
184           Character'Pos ('E') mod 16#20#, REQ_END_FIELD
185           ),
186          (
187           Character'Pos ('F') mod 16#20#, REQ_NEXT_PAGE
188           ),
189          (
190           Character'Pos ('G') mod 16#20#, REQ_DEL_WORD
191           ),
192          (
193           Character'Pos ('H') mod 16#20#, REQ_DEL_PREV
194           ),
195          (
196           Character'Pos ('I') mod 16#20#, REQ_INS_CHAR
197           ),
198          (
199           Character'Pos ('K') mod 16#20#, REQ_CLR_EOF
200           ),
201          (
202           Character'Pos ('L') mod 16#20#, REQ_LEFT_FIELD
203           ),
204          (
205           Character'Pos ('M') mod 16#20#, REQ_NEW_LINE
206           ),
207          (
208           Character'Pos ('N') mod 16#20#, REQ_NEXT_FIELD
209           ),
210          (
211           Character'Pos ('O') mod 16#20#, REQ_INS_LINE
212           ),
213          (
214           Character'Pos ('P') mod 16#20#, REQ_PREV_FIELD
215           ),
216          (
217           Character'Pos ('R') mod 16#20#, REQ_RIGHT_FIELD
218           ),
219          (
220           Character'Pos ('S') mod 16#20#, REQ_BEG_FIELD
221           ),
222          (
223           Character'Pos ('U') mod 16#20#, REQ_UP_FIELD
224           ),
225          (
226           Character'Pos ('V') mod 16#20#, REQ_DEL_CHAR
227           ),
228          (
229           Character'Pos ('W') mod 16#20#, REQ_NEXT_WORD
230           ),
231          (
232           Character'Pos ('X') mod 16#20#, REQ_CLR_FIELD
233           ),
234          (
235           Character'Pos ('Y') mod 16#20#, REQ_DEL_LINE
236           ),
237          (
238           Character'Pos ('Z') mod 16#20#, REQ_PREV_CHOICE
239           ),
240          (
241           Character'Pos ('[') mod 16#20#, --  ESCAPE
242           Form_Request_Code'Last + 1
243           ),
244          (
245           Key_Backspace, REQ_DEL_PREV
246           ),
247          (
248           KEY_DOWN, REQ_DOWN_CHAR
249           ),
250          (
251           Key_End, REQ_LAST_FIELD
252           ),
253          (
254           Key_Home, REQ_FIRST_FIELD
255           ),
256          (
257           KEY_LEFT, REQ_LEFT_CHAR
258           ),
259          (
260           KEY_LL, REQ_LAST_FIELD
261           ),
262          (
263           Key_Next, REQ_NEXT_FIELD
264           ),
265          (
266           KEY_NPAGE, REQ_NEXT_PAGE
267           ),
268          (
269           KEY_PPAGE, REQ_PREV_PAGE
270           ),
271          (
272           Key_Previous, REQ_PREV_FIELD
273           ),
274          (
275           KEY_RIGHT, REQ_RIGHT_CHAR
276           ),
277          (
278           KEY_UP, REQ_UP_CHAR
279           ),
280          (
281           Character'Pos ('Q') mod 16#20#, --  QUIT
282           Form_Request_Code'Last + 1      --  TODO MAX_FORM_COMMAND + 1
283           )
284          );
285
286       c : Key_Code := Getchar (w);
287       me : constant Field := Current (f);
288
289    begin
290       if c = Character'Pos (']') mod 16#20# then
291          if mode = REQ_INS_MODE then
292             mode := REQ_OVL_MODE;
293          else
294             mode := REQ_INS_MODE;
295          end if;
296          c := mode;
297       else
298          for n in lookup'Range loop
299             if lookup (n).code = c then
300                c := lookup (n).result;
301                exit;
302             end if;
303          end loop;
304       end if;
305
306       --  Force the field that the user is typing into to be in reverse video,
307       --  while the other fields are shown underlined.
308       if c <= Key_Max then
309          c := edit_secure (me, c);
310          Set_Background (me, (Reverse_Video => True, others => False));
311       elsif c <= Form_Request_Code'Last then
312          c := edit_secure (me, c);
313          Set_Background (me, (Under_Line => True, others => False));
314       end if;
315       return c;
316    end form_virtualize;
317
318    function my_form_driver (f : Form; c : Key_Code) return Boolean is
319       flag : constant Driver_Result := Driver (f, F_Validate_Field);
320    begin
321       if c = Form_Request_Code'Last + 1 and
322          flag = Form_Ok
323       then
324          return True;
325       else
326          Beep;
327          return False;
328       end if;
329    end my_form_driver;
330
331    function make_label (frow  : Line_Position;
332                         fcol  : Column_Position;
333                         label : String) return Field is
334       f : constant Field := Create (1, label'Length, frow, fcol, 0, 0);
335       o : Field_Option_Set := Get_Options (f);
336    begin
337       if f /= Null_Field then
338          Set_Buffer (f, 0, label);
339          o.Active := False;
340          Set_Options (f, o);
341       end if;
342       return f;
343    end make_label;
344
345    function make_field (frow   : Line_Position;
346                         fcol   : Column_Position;
347                         rows   : Line_Count;
348                         cols   : Column_Count;
349                         secure : Boolean) return Field is
350       f : Field;
351       use StringData;
352       len : myptr;
353    begin
354       if secure then
355          f := Create (rows, cols, frow, fcol, 0, 1);
356       else
357          f := Create (rows, cols, frow, fcol, 0, 0);
358       end if;
359
360       if f /= Null_Field then
361          Set_Background (f, (Under_Line => True, others => False));
362          len := new Integer;
363          len.all := 0;
364          Set_User_Data (f, len);
365       end if;
366       return f;
367    end make_field;
368
369    procedure display_form (f : Form) is
370       w : Window;
371       rows : Line_Count;
372       cols : Column_Count;
373    begin
374       Scale (f, rows, cols);
375
376       w := New_Window (rows + 2, cols + 4, 0, 0);
377       if w /= Null_Window then
378          Set_Window (f, w);
379          Set_Sub_Window (f, Derived_Window (w, rows, cols, 1, 2));
380          Box (w); -- 0,0
381          Set_KeyPad_Mode (w, True);
382       end if;
383
384       --  TODO if Post(f) /= Form_Ok then it is a procedure
385       declare
386       begin
387          Post (f);
388       exception
389          when
390            Eti_System_Error    |
391            Eti_Bad_Argument    |
392            Eti_Posted          |
393            Eti_Connected       |
394            Eti_Bad_State       |
395            Eti_No_Room         |
396            Eti_Not_Posted      |
397            Eti_Unknown_Command |
398            Eti_No_Match        |
399            Eti_Not_Selectable  |
400            Eti_Not_Connected   |
401            Eti_Request_Denied  |
402            Eti_Invalid_Field   |
403            Eti_Current         =>
404             Refresh (w);
405       end;
406       --  end if;
407    end display_form;
408
409    procedure erase_form (f : Form) is
410       w : Window := Get_Window (f);
411       s : Window := Get_Sub_Window (f);
412    begin
413       Post (f, False);
414       Erase (w);
415       Refresh (w);
416       Delete (s);
417       Delete (w);
418    end erase_form;
419
420    finished : Boolean := False;
421    f : constant Field_Array_Access := new Field_Array (1 .. 12);
422    secure : Field;
423    myform : Form;
424    w : Window;
425    c : Key_Code;
426    result : Driver_Result;
427 begin
428    Move_Cursor (Line => 18, Column => 0);
429    Add (Str => "Defined form-traversal keys:   ^Q/ESC- exit form");
430    Add (Ch => newl);
431    Add (Str => "^N   -- go to next field       ^P  -- go to previous field");
432    Add (Ch => newl);
433    Add (Str => "Home -- go to first field      End -- go to last field");
434    Add (Ch => newl);
435    Add (Str => "^L   -- go to field to left    ^R  -- go to field to right");
436    Add (Ch => newl);
437    Add (Str => "^U   -- move upward to field   ^D  -- move downward to field");
438    Add (Ch => newl);
439    Add (Str => "^W   -- go to next word        ^B  -- go to previous word");
440    Add (Ch => newl);
441    Add (Str => "^S   -- go to start of field   ^E  -- go to end of field");
442    Add (Ch => newl);
443    Add (Str => "^H   -- delete previous char   ^Y  -- delete line");
444    Add (Ch => newl);
445    Add (Str => "^G   -- delete current word    ^C  -- clear to end of line");
446    Add (Ch => newl);
447    Add (Str => "^K   -- clear to end of field  ^X  -- clear field");
448    Add (Ch => newl);
449    Add (Str => "Arrow keys move within a field as you would expect.");
450
451    Add (Line => 4, Column => 57, Str => "Forms Entry Test");
452
453    Refresh;
454
455    --  describe the form
456    f.all (1) := make_label (0, 15, "Sample Form");
457    f.all (2) := make_label (2, 0, "Last Name");
458    f.all (3) := make_field (3, 0, 1, 18, False);
459    f.all (4) := make_label (2, 20, "First Name");
460    f.all (5) := make_field (3, 20, 1, 12, False);
461    f.all (6) := make_label (2, 34, "Middle Name");
462    f.all (7) := make_field (3, 34, 1, 12, False);
463    f.all (8) := make_label (5, 0, "Comments");
464    f.all (9) := make_field (6, 0, 4, 46, False);
465    f.all (10) := make_label (5, 20, "Password:");
466    f.all (11) := make_field (5, 30, 1, 9, True);
467    secure := f.all (11);
468    f.all (12) := Null_Field;
469
470    myform := New_Form (f);
471
472    display_form (myform);
473
474    w := Get_Window (myform);
475    Set_Raw_Mode (SwitchOn => True);
476    Set_NL_Mode (SwitchOn => True);     --  lets us read ^M's
477    while not finished loop
478       c := form_virtualize (myform, w);
479       result := Driver (myform, c);
480       case result is
481          when Form_Ok =>
482             Add (Line => 5, Column => 57, Str => Get_Buffer (secure, 1));
483             Clear_To_End_Of_Line;
484             Refresh;
485          when Unknown_Request =>
486             finished := my_form_driver (myform, c);
487          when others =>
488             Beep;
489       end case;
490    end loop;
491
492    erase_form (myform);
493
494    --  TODO Free_Form(myform);
495    --     for (c = 0; f[c] != 0; c++) free_field(f[c]);
496    Set_Raw_Mode (SwitchOn => False);
497    Set_NL_Mode (SwitchOn => True);
498
499 end ncurses2.demo_forms;