Subversion Repositories oidplus

Rev

Rev 758 | Go to most recent revision | 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;
538
        j := Length(s)-2(*CRLF*)-j;
539
        x := initX + j;
540
        s := Copy(s, 1, Length(s)-1); (* Remove #10 now. #13 will be removed below *)
541
      end
542
      else
543
      begin
544
        Dec(x);
545
      end;
546
      GotoXY(x, y);
547
      Write(' ');
548
      GotoXY(x, y);
549
      s := Copy(s, 1, Length(s)-1);
550
      continue;
551
    end
552
    else if sc = #13(*Return*) then
553
    begin
554
      if GetShiftState and kbRightShift <> 0 then
555
      begin
556
        if y=initY+height-1 then
557
        begin
558
          Beep;
559
          continue;
560
        end;
992 daniel-mar 561
        if Length(s) >= 254 then
562
        begin
563
          Beep;
564
          continue;
565
        end;
733 daniel-mar 566
        s := s + #13 + #10;
567
        x := initX;
568
        Inc(y);
569
      end
570
      else
571
      begin
572
        QueryVal := true;
573
        exit;
574
      end;
575
    end
576
    else if sc = #27(*ESC*) then
577
    begin
578
      QueryVal := false;
579
      exit;
580
    end
581
    else
582
    begin
583
      if (x=initX+width-1) and (y=initY+height-1) then
584
      begin
585
        Beep;
586
        continue;
587
      end;
992 daniel-mar 588
      if Length(s) >= 255-Length(sc)+1 then
589
      begin
590
        Beep;
591
        continue;
592
      end;
733 daniel-mar 593
      s := s + sc;
594
      Write(sc);
595
      Inc(x);
596
      if x >= initX+width then
597
      begin
598
        Inc(y);
599
        x := initX;
600
      end;
601
    end;
602
  until false;
603
end;
604
 
605
procedure ShowMessage(msg: string; title: string; dobeep: boolean);
606
var
607
  x, y, w, h: integer;
608
begin
609
  x := SINGLE_LINE_BOX_PADDING_INNER;
610
  y := ScreenHeight div 2 - 1;
611
  w := ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2;
612
  h := 1;
613
  DrawDoubleBorder(x-1, y, w+2, h+2);
614
  ClearSection(x, y+1, w-2, h);
615
  if title <> '' then
616
  begin
617
    GoToXY(x+1, y);
618
    Write(title);
619
  end;
620
  GoToXY(x, y+1);
621
  Write(msg);
622
  if DoBeep then Beep;
623
end;
624
 
746 daniel-mar 625
procedure CursorOn; assembler;
626
asm
627
  mov ah,1     (* Set text-mode cursor shape *)
628
  mov cx,0607h (* normal underline cursor *)
629
  int 10h
630
end;
631
 
632
procedure CursorOff; assembler;
633
asm
634
  mov ah,1     (* Set text-mode cursor shape *)
635
  mov cx,2607h (* hide cursor (Start>End) *)
636
  int 10h
637
end;
638
 
747 daniel-mar 639
procedure ResetDefaultDosColors;
640
begin
641
  TextBackground(Black);
642
  TextColor(LightGray);
643
end;
644
 
748 daniel-mar 645
procedure WriteLnKeepX(s: string);
646
var
647
  i: integer;
648
  initX: integer;
649
  bytesToWrite: integer;
650
  pNewLine: integer;
651
begin
652
  initX := WhereX;
653
  while Length(s) > 0 do
654
  begin
655
    pNewLine := Pos(#13#10, s);
656
    GoToXy(initX, WhereY);
657
    bytesToWrite := ScreenWidth - initX + 1;
658
    if (pNewLine > 0) and (pNewLine < bytesToWrite) then
659
      bytesToWrite := pNewLine;
660
    Write(Copy(s, 1, bytesToWrite)); (* No WriteLn because there is automatic scrolling *)
661
    Delete(s, 1, bytesToWrite);
662
  end;
663
  WriteLn('');
664
end;
665
 
733 daniel-mar 666
end.