Subversion Repositories oidplus

Rev

Rev 992 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
733 daniel-mar 1
unit VTSCUI;
2
 
3
(************************************************)
4
(* VTSCUI.PAS                                   *)
5
(* Author:   Daniel Marschall                   *)
992 daniel-mar 6
(* Revision: 2022-10-10                         *)
733 daniel-mar 7
(* License:  Apache 2.0                         *)
8
(* This file contains:                          *)
9
(* - ViaThinkSoft CUI (Console User Interface)  *)
10
(************************************************)
11
 
12
interface
13
 
14
uses
15
  StrList;
16
 
17
const
746 daniel-mar 18
  (* These are available in DRIVERS.TPU, but require a call of InitVideo
733 daniel-mar 19
  ScreenWidth = 80;
20
  ScreenHeight = 25;
746 daniel-mar 21
  *)
733 daniel-mar 22
  SINGLE_LINE_BOX_PADDING = 3;
23
  SINGLE_LINE_BOX_PADDING_INNER = 10;
24
 
25
procedure DrawThinBorder(x, y, width, height: integer);
26
procedure DrawDoubleBorder(x, y, width, height: integer);
27
procedure DrawTextBar(str: string; line: integer);
738 daniel-mar 28
procedure DrawTitleBar(center, left, right: string);
733 daniel-mar 29
procedure DrawStatusBar(str: string);
30
function DrawSelectionList(X, Y, ListWidth, ListHeight: integer;
31
                           items: PStringList; allowESC: boolean;
32
                           Title: string; borderStrength: integer): integer;
33
procedure ClearSection(x, y, width, height: integer);
34
function QueryVal(var s: string; initX, initY, width, height: integer;
35
                  Title: string; borderStrength: integer): boolean;
36
procedure ShowMessage(msg: string; title: string; dobeep: boolean);
746 daniel-mar 37
procedure CursorOn;
38
procedure CursorOff;
747 daniel-mar 39
procedure ResetDefaultDosColors;
748 daniel-mar 40
procedure WriteLnKeepX(s: string);
733 daniel-mar 41
 
42
implementation
43
 
44
uses
45
  Crt, Drivers, VtsFuncs;
46
 
47
type
48
  TCharDefs = array[0..7] of char;
49
 
50
const
51
  ThinLineChars: TCharDefs =
52
    ( #$DA, #$C4, #$BF,
53
      #$B3,       #$B3,
54
      #$C0, #$C4, #$D9
55
    );
56
  DoubleLineChars: TCharDefs =
57
    ( #$C9, #$CD, #$BB,
58
      #$BA,       #$BA,
59
      #$C8, #$CD, #$BC
60
    );
61
 
62
function FillRight(str: string; len: integer; c: char): string;
63
var
64
  s: string;
65
  i: integer;
66
begin
67
  s := str;
68
  for i := Length(str) to len-1 do
69
  begin
992 daniel-mar 70
    if Length(s) >= 255 then break;
733 daniel-mar 71
    s := s + c;
72
  end;
73
  FillRight := s;
74
end;
75
 
76
procedure DrawBorder(x, y, width, height, thickness: integer);
77
var
78
  ix,iy: integer;
79
  chars: TCharDefs;
80
begin
81
  if thickness = 1 then
82
    chars := ThinLineChars;
83
  if thickness = 2 then
84
    chars := DoubleLineChars;
85
 
86
  (* Top line *)
87
  if y >= 1 then
88
  begin
89
    (* Top left corner *)
90
    if x >= 1 then
91
    begin
92
      GotoXY(x,y);
93
      Write(chars[0]);
94
    end
95
    else
96
    begin
97
      GotoXY(1,y);
98
    end;
99
 
100
    (* Top edge *)
101
    for ix := 1 to width-2 do
102
      Write(chars[1]);
103
 
104
    (* Top right corner *)
105
    if x+width-1 <= ScreenWidth then
106
      Write(chars[2]);
107
  end;
108
 
109
  (* Left edge *)
110
  for iy := 1 to height-2 do
111
  begin
112
    if (x    >= 1) and (x    <= ScreenWidth) and
113
       (y+iy >= 1) and (y+iy <= ScreenHeight) then
114
    begin
115
      GotoXY(x,y+iy);
116
      Write(chars[3]);
117
    end;
118
  end;
119
 
120
  (* Right edge *)
121
  for iy := 1 to height-2 do
122
  begin
123
    if (x+width-1 >= 1) and (x+width-1 <= ScreenWidth) and
124
       (y+iy      >= 1) and (y+iy      <= ScreenHeight) then
125
    begin
126
      GotoXY(x+width-1,y+iy);
127
      Write(chars[4]);
128
    end;
129
  end;
130
 
131
  (* Bottom line *)
132
  if y+height-1 <= ScreenHeight then
133
  begin
134
    (* Bottom left corner *)
135
    if x >= 1 then
136
    begin
137
      GotoXY(x,y+height-1);
138
      Write(chars[5]);
139
    end
140
    else
141
    begin
142
      GotoXY(1,y+height-1);
143
    end;
144
 
145
    (* Bottom edge *)
146
    for ix := 1 to width-2 do
147
      Write(chars[6]);
148
 
149
    (* Bottom right corner *)
150
    if x+width-1 <= ScreenWidth then
151
      Write(chars[7]);
152
  end;
153
end;
154
 
155
procedure DrawThinBorder(x, y, width, height: integer);
156
begin
157
  DrawBorder(x, y, width, height, 1);
158
end;
159
 
160
procedure DrawDoubleBorder(x, y, width, height: integer);
161
begin
162
  DrawBorder(x, y, width, height, 2);
163
end;
164
 
165
procedure DrawTextBar(str: string; line: integer);
166
var
167
  i, left, right: integer;
168
  len: integer;
169
begin
170
  GotoXY(1,line);
171
 
172
  TextBackground(White);
173
  TextColor(Black);
174
  len := Length(str);
175
 
176
  left := round((ScreenWidth-len)/2);
177
  right := ScreenWidth - left - len;
178
 
179
  for i := 1 to left do
180
  begin
181
    Write(' ');
182
  end;
183
 
184
  Write(str);
185
 
186
  (* TODO: If we do "for i := 1 to right", then the console will scroll *)
187
  (*       since the char in the right bottom corner is written!        *)
188
  for i := 1 to right-1 do
189
  begin
190
    Write(' ');
191
  end;
746 daniel-mar 192
  GoToXY(1,1);
733 daniel-mar 193
 
194
  TextBackground(Black);
195
  TextColor(White);
196
end;
197
 
738 daniel-mar 198
procedure DrawTitleBar(center, left, right: string);
746 daniel-mar 199
var
200
  bakx, baky: integer;
733 daniel-mar 201
begin
746 daniel-mar 202
  bakx := WhereX;
203
  baky := WhereY;
204
 
738 daniel-mar 205
  DrawTextBar(center, 1);
206
 
207
  (* Put left text into the title bar *)
208
  GoToXY(1,1);
209
  TextBackground(White);
210
  TextColor(Black);
211
  WriteLn(left);
212
  TextBackground(Black);
213
  TextColor(White);
214
 
215
  (* Put right text into the title bar *)
216
  GotoXY(ScreenWidth-Length(right)+1,1);
217
  TextBackground(White);
218
  TextColor(Black);
219
  WriteLn(right);
220
  TextBackground(Black);
221
  TextColor(White);
746 daniel-mar 222
 
223
  GoToXY(bakx, baky);
733 daniel-mar 224
end;
225
 
226
procedure DrawStatusBar(str: string);
746 daniel-mar 227
var
228
  bakx, baky: integer;
733 daniel-mar 229
begin
746 daniel-mar 230
  bakx := WhereX;
231
  baky := WhereY;
733 daniel-mar 232
  DrawTextBar(str, ScreenHeight);
746 daniel-mar 233
  GoToXY(bakx, baky);
733 daniel-mar 234
end;
235
 
236
function DrawSelectionList(X, Y, ListWidth, ListHeight: integer;
237
                           items: PStringList; allowESC: boolean;
238
                           Title: string; borderStrength: integer): integer;
239
var
240
  i: integer;
241
  itemIndex: integer;
242
  sc: char;
243
  iStartScope, iEndScope: integer;
735 daniel-mar 244
  sTmp: string;
733 daniel-mar 245
label
246
  doAgain;
247
begin
248
  if borderStrength = 1 then
249
  begin
250
    DrawThinBorder(X-1, Y-1, ListWidth+2, ListHeight+2);
251
  end;
252
 
253
  if borderStrength = 2 then
254
  begin
255
    DrawDoubleBorder(X-1, Y-1, ListWidth+2, ListHeight+2);
256
  end;
257
 
258
  if Title <> '' then
259
  begin
260
    if borderStrength > 0 then
261
      GoToXY(X+1, Y-1)
262
    else
263
      GoToXY(X, Y-1);
264
    Write(Title);
265
  end;
266
 
267
  (*CursorOff;*)
268
 
269
  itemIndex := 0;
270
  iStartScope := itemIndex;
271
  iEndScope := itemIndex + ListHeight;
272
 
273
doAgain:
274
 
275
  if itemIndex < 0 then
276
    itemIndex := 0;
277
  if itemIndex > ListCount(items)-1 then
278
    itemIndex := ListCount(items)-1;
279
 
280
  if itemIndex < iStartScope then
281
  begin
282
    Dec(iEndScope);
283
    Dec(iStartScope);
284
  end;
285
 
286
  if itemIndex > iEndScope-1 then
287
  begin
288
    Inc(iEndScope);
289
    Inc(iStartScope);
290
  end;
291
 
292
  if borderStrength > 0 then
293
  begin
294
    (* Show scroll arrows *)
295
    GotoXY(X+ListWidth, Y);
296
    if iStartScope > 0 then
297
    begin
298
      TextBackground(White);
299
      TextColor(Black);
300
      WriteLn(#$18(*ArrowUp*));
301
      TextBackground(Black);
302
      TextColor(White);
303
    end
304
    else if borderStrength = 1 then
305
      WriteLn(ThinLineChars[4])
306
    else if borderStrength = 2 then
307
      WriteLn(DoubleLineChars[4]);
308
 
309
    GotoXY(X+ListWidth, Y+ListHeight-1);
310
    if ListCount(items) > iEndScope then
311
    begin
312
      TextBackground(White);
313
      TextColor(Black);
314
      WriteLn(#$19(*ArrowDown*));
315
      TextBackground(Black);
316
      TextColor(White);
317
    end
318
    else if borderStrength = 1 then
319
      WriteLn(ThinLineChars[4])
320
    else if borderStrength = 2 then
321
      WriteLn(DoubleLineChars[4]);
322
  end;
323
 
324
  for i := iStartScope to iEndScope-1 do
325
  begin
326
    if itemIndex = i then
327
    begin
328
      TextColor(Black);
329
      TextBackground(White);
330
    end
331
    else
332
    begin
333
      TextColor(White);
334
      TextBackground(Black);
335
    end;
336
    GotoXY(x,y+i-iStartScope);
337
    if i > ListCount(items)-1 then
338
      Write(FillRight('', ListWidth, ' '))
339
    else
735 daniel-mar 340
    begin
341
      sTmp := ListGetElement(items, i);
740 daniel-mar 342
      sTmp := TrimLineToWidth(sTmp, ListWidth);
735 daniel-mar 343
      Write(FillRight(sTmp, ListWidth, ' '));
344
    end;
733 daniel-mar 345
    TextColor(White);
346
    TextBackground(Black);
347
  end;
348
 
349
  repeat
350
    GotoXY(ScreenWidth, ScreenHeight);
351
 
352
    sc := ReadKey;
353
    if sc = #$00(*ExtendedKeyCode*) then
354
    begin
355
      sc := ReadKey;
356
      if sc = #$48(*UpKey*) then
357
      begin
358
        dec(itemIndex);
359
        goto doAgain;
360
      end
361
      else if sc = #$50(*DownKey*) then
362
      begin
363
        inc(itemIndex);
364
        goto doAgain;
365
      end
366
      else if sc = #$47(*POS1*) then
367
      begin
368
        itemIndex := 0;
758 daniel-mar 369
        iStartScope := itemIndex;
740 daniel-mar 370
        iEndScope := iStartScope + ListHeight;
733 daniel-mar 371
        goto doAgain;
372
      end
373
      else if sc = #$4F(*END*) then
374
      begin
740 daniel-mar 375
        itemIndex := ListCount(items)-1;
376
        iStartScope := itemIndex - Min(ListHeight,ListCount(items));
377
        iEndScope := itemIndex;
733 daniel-mar 378
        goto doAgain;
758 daniel-mar 379
      end
380
      else if sc = #$49(*PgUp*) then
381
      begin
382
        Dec(itemIndex, ListHeight);
383
        if itemIndex < 0 then
384
          itemIndex := 0;
385
        iStartScope := itemIndex;
386
        iEndScope := itemIndex + ListHeight;
387
        goto doAgain;
388
      end
389
      else if sc = #$51(*PgDown*) then
390
      begin
391
        Inc(itemIndex, ListHeight);
392
        if itemIndex > ListCount(items)-1 then
393
          itemIndex := ListCount(items)-1;
394
        iStartScope := itemIndex - Min(ListHeight,ListCount(items));
395
        iEndScope := itemIndex;
396
        goto doAgain;
733 daniel-mar 397
      end;
398
    end;
399
 
400
    if sc = #13(*Return*) then
401
    begin
402
      DrawSelectionList := itemIndex;
403
      break;
404
    end;
405
 
406
    if allowESC and (sc = #27(*ESC*)) then
407
    begin
408
      DrawSelectionList := -1;
409
      break;
410
    end;
411
  until false;
412
 
413
  (*CursorOn;*)
414
end;
415
 
416
procedure ClearSection(x, y, width, height: integer);
417
var
418
  ix, iy: integer;
419
begin
420
  for iy := y to y+height-1 do
421
  begin
422
    for ix := x to x+width-1 do
423
    begin
424
      GoToXY(ix,iy);
425
      Write(' ');
426
    end;
427
  end;
428
end;
429
 
430
function QueryVal(var s: string; initX, initY, width, height: integer;
431
                  Title: string; borderStrength: integer): boolean;
432
var
433
  x, y: integer;
434
  i, j: integer;
435
  sc: char;
436
  stmp: string;
437
begin
438
  if borderStrength = 1 then
439
    DrawThinBorder(initX-1,initY-1,width+2,height+2);
440
  if borderStrength = 2 then
441
    DrawDoubleBorder(initX-1,initY-1,width+2,height+2);
442
  if title <> '' then
443
  begin
444
    if borderStrength > 0 then
445
      GoToXY(initX+1, initY-1)
446
    else
447
      GoToXY(initX, initY-1);
448
    Write(title);
449
  end;
450
  ClearSection(initX,initY,width,height);
451
 
452
  x := initX;
453
  y := initY;
454
 
455
  (* Write existing string value and set cursor *)
456
  stmp := s;
457
  s := '';
458
  for i := 1 to Length(stmp) do
459
  begin
460
    if stmp[i] = #10 then
461
    begin
992 daniel-mar 462
      if Length(s) >= 255 then
463
      begin
464
        Beep;
465
        continue;
466
      end;
733 daniel-mar 467
      s := s + stmp[i];
468
      continue;
469
    end;
470
 
471
    GoToXY(x,y);
992 daniel-mar 472
    if Length(s) >= 255 then
473
    begin
474
      Beep;
475
      continue;
476
    end;
733 daniel-mar 477
    s := s + stmp[i];
478
    Write(stmp[i]);
479
    Inc(x);
480
 
481
    if (x=initX+width-1) and (y=initY+height-1) then
482
    begin
483
      (* Attention: Data following after this will be lost! *)
484
      break;
485
    end;
486
 
487
    if stmp[i] = #13 then
488
    begin
489
      if y=initY+height-1 then
490
      begin
491
        (* Attention: Data following after this will be lost! *)
492
        s := Copy(s, 1, Length(s)-1);
493
        Dec(x);
494
        break;
495
      end;
496
      x := initX;
497
      Inc(y);
498
      continue;
499
    end;
500
 
501
    if x=initX+width then
502
    begin
503
      Inc(y);
504
      x := initX;
505
    end;
506
  end;
507
 
508
  repeat
509
    GotoXY(x, y);
510
    sc := ReadKey;
511
 
512
    if sc = #0 then
513
    begin
514
      (* Extended key code *)
515
      sc := ReadKey;
516
      (* TODO: Implement keys like DEL, END, POS1, and ArrowKeys *)
517
      Beep;
518
      continue;
519
    end
520
    else if sc = #8(*Backspace*) then
521
    begin
522
      if x <= initX then
523
      begin
524
        if y = initY then
525
        begin
526
          Beep;
527
          continue;
528
        end;
529
        Dec(y);
530
 
531
        (* Find out length of previous line *)
532
        j := Length(s)-2(*CRLF*);
533
        while (j >= 0) do
534
        begin
535
          if (s[j]=#13) or (s[j]=#10) then break;
536
          Dec(j);
537
        end;
993 daniel-mar 538
        if j < 0 then j := 0;
733 daniel-mar 539
        j := Length(s)-2(*CRLF*)-j;
993 daniel-mar 540
        if Copy(s,Length(s),1) <> #10 then j := j + 1;
541
        j := j mod width;
542
 
733 daniel-mar 543
        x := initX + j;
993 daniel-mar 544
        if Copy(s,Length(s),1) = #10 then
545
          s := Copy(s, 1, Length(s)-1); (* Remove #10 now. #13 will be removed below *)
733 daniel-mar 546
      end
547
      else
548
      begin
549
        Dec(x);
550
      end;
551
      GotoXY(x, y);
552
      Write(' ');
553
      GotoXY(x, y);
554
      s := Copy(s, 1, Length(s)-1);
555
      continue;
556
    end
557
    else if sc = #13(*Return*) then
558
    begin
559
      if GetShiftState and kbRightShift <> 0 then
560
      begin
561
        if y=initY+height-1 then
562
        begin
563
          Beep;
564
          continue;
565
        end;
992 daniel-mar 566
        if Length(s) >= 254 then
567
        begin
568
          Beep;
569
          continue;
570
        end;
733 daniel-mar 571
        s := s + #13 + #10;
572
        x := initX;
573
        Inc(y);
574
      end
575
      else
576
      begin
577
        QueryVal := true;
578
        exit;
579
      end;
580
    end
581
    else if sc = #27(*ESC*) then
582
    begin
583
      QueryVal := false;
584
      exit;
585
    end
586
    else
587
    begin
588
      if (x=initX+width-1) and (y=initY+height-1) then
589
      begin
590
        Beep;
591
        continue;
592
      end;
992 daniel-mar 593
      if Length(s) >= 255-Length(sc)+1 then
594
      begin
595
        Beep;
596
        continue;
597
      end;
733 daniel-mar 598
      s := s + sc;
599
      Write(sc);
600
      Inc(x);
601
      if x >= initX+width then
602
      begin
603
        Inc(y);
604
        x := initX;
605
      end;
606
    end;
607
  until false;
608
end;
609
 
610
procedure ShowMessage(msg: string; title: string; dobeep: boolean);
611
var
612
  x, y, w, h: integer;
613
begin
614
  x := SINGLE_LINE_BOX_PADDING_INNER;
615
  y := ScreenHeight div 2 - 1;
616
  w := ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2;
617
  h := 1;
618
  DrawDoubleBorder(x-1, y, w+2, h+2);
619
  ClearSection(x, y+1, w-2, h);
620
  if title <> '' then
621
  begin
622
    GoToXY(x+1, y);
623
    Write(title);
624
  end;
625
  GoToXY(x, y+1);
626
  Write(msg);
627
  if DoBeep then Beep;
628
end;
629
 
746 daniel-mar 630
procedure CursorOn; assembler;
631
asm
632
  mov ah,1     (* Set text-mode cursor shape *)
633
  mov cx,0607h (* normal underline cursor *)
634
  int 10h
635
end;
636
 
637
procedure CursorOff; assembler;
638
asm
639
  mov ah,1     (* Set text-mode cursor shape *)
640
  mov cx,2607h (* hide cursor (Start>End) *)
641
  int 10h
642
end;
643
 
747 daniel-mar 644
procedure ResetDefaultDosColors;
645
begin
646
  TextBackground(Black);
647
  TextColor(LightGray);
648
end;
649
 
748 daniel-mar 650
procedure WriteLnKeepX(s: string);
651
var
652
  i: integer;
653
  initX: integer;
654
  bytesToWrite: integer;
655
  pNewLine: integer;
656
begin
657
  initX := WhereX;
658
  while Length(s) > 0 do
659
  begin
660
    pNewLine := Pos(#13#10, s);
661
    GoToXy(initX, WhereY);
662
    bytesToWrite := ScreenWidth - initX + 1;
663
    if (pNewLine > 0) and (pNewLine < bytesToWrite) then
664
      bytesToWrite := pNewLine;
665
    Write(Copy(s, 1, bytesToWrite)); (* No WriteLn because there is automatic scrolling *)
666
    Delete(s, 1, bytesToWrite);
667
  end;
668
  WriteLn('');
669
end;
670
 
733 daniel-mar 671
end.