home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 15 / CD_ASCQ_15_070894.iso / vrac / 4utils83.zip / 4DESC.PAS < prev    next >
Pascal/Delphi Source File  |  1994-04-28  |  37KB  |  1,081 lines

  1. PROGRAM FileDescEditor;
  2. {$A+,B-,D-,E-,F-,G+,L+,N-,O-,R+,S+,V-,X-}
  3. {$M 8192,0,655360}
  4.  
  5. (* ----------------------------------------------------------------------
  6.    A Simple 4DOS File Description Editor
  7.  
  8.    (c) 1992, 1993 Copyright by
  9.  
  10.        David Frey,         & Tom Bowden
  11.        Urdorferstrasse 30    1575 Canberra Drive
  12.        8952 Schlieren ZH     Stone Mountain, GA 30088-3629
  13.        Switzerland           USA
  14.  
  15.        Code created using Turbo Pascal 7.0, (c) Borland International 1992
  16.  
  17.    DISCLAIMER: This program is freeware: you are allowed to use, copy
  18.                and change it free of charge, but you may not sell or hire
  19.                4DESC. The copyright remains in our hands.
  20.  
  21.                If you make any (considerable) changes to the source code,
  22.                please let us know. (send a copy or a listing).
  23.                We would like to see what you have done.
  24.  
  25.                We, David Frey and Tom Bowden, the authors, provide absolutely
  26.                no warranty of any kind. The user of this software takes the
  27.                entire risk of damages, failures, data losses or other
  28.                incidents.
  29.  
  30.    ----------------------------------------------------------------------- *)
  31.  
  32. USES {$IFOPT G+} Test286, {$ENDIF}
  33.      Fix, Crt, Dos, Memory, Drivers,
  34.      StringDateHandling, DisplayKeyboardAndCursor, HandleINIFile,
  35.      DescriptionHandling, dmouse;
  36.  
  37. CONST DelimiterTable : STRING = ',.();:-!?/[]{}+*=''`"@%&$_£';
  38.  
  39. VAR  EdStart     : BYTE;      (* column where the description starts     *)
  40.  
  41.      ActDir      : DirStr;    (* current directory                       *)
  42.      StartDir    : DirStr;    (* directory where we started from         *)
  43.      ResetDir    : BOOLEAN;   (* TRUE = return to StartDir on exit       *)
  44.  
  45.      StartIndex  : INTEGER;   (* index of entry at the top of the screen *)
  46.      Index       : INTEGER;   (* index of entry we are editing           *)
  47.  
  48.      CutPasteDesc: DescStr;   (* cut, resp. pasted description           *)
  49.      Changed     : BOOLEAN;   (* TRUE=the descriptions have been edited  *)
  50.      IORes       : INTEGER;
  51.  
  52.      NewDir      : DirStr;    (* temporary storage for a directory path, *)
  53.      NewName     : NameExtStr;(* used by view and others                 *)
  54.  
  55.      FirstParam  : STRING[8];
  56.      i           : BYTE;      (* variable for counting (index etc)       *)
  57.      ShowHelp    : BOOLEAN;   (* TRUE = start in help mode [/h]          *)
  58.      Querier     : BOOLEAN;   (* TRUE = ask user if he wants to save
  59.                                         the descriptions   [/dontask]    *)
  60.      PasteMovesToNextIndex: BOOLEAN; (* TRUE = Paste advances to next index *)
  61.      Overwrite   : BOOLEAN;   (* overwrite / Insert mode                 *)
  62.  
  63.      s           : STRING;    (* temporary string variable               *)
  64.  
  65. (*-------------------------------------------------------- Display-Routines *)
  66. PROCEDURE DisplayFileEntry(Index: INTEGER; ox, x: BYTE;
  67.                            Selection, Hilighted: BOOLEAN);
  68. (* Displays the Index'th file entry. If the description is longer than
  69.    DispLen characters, DispLen characters - starting at character x of the
  70.    description - will be shown. (this feature is needed for scrolling).
  71.    Hilighted = TRUE will hilight the description.
  72.  
  73.    When Selection is TRUE, we want to display the text we just put into
  74.    the buffer, ox (old x) gives us the start of the selection.
  75.  
  76.    P.S. Scrolling implies hilighting, but this fact has not been exploited. *)
  77.  
  78.  VAR FileEntry : PFileData;
  79.      xs,oxs,t  : BYTE;
  80.      y,l       : BYTE;
  81.      s         : STRING;
  82.  
  83.  BEGIN
  84.   y := 3+Index-StartIndex;
  85.   IF (Index >= 0) AND (Index < FileList^.Count) THEN
  86.    BEGIN
  87.     FileEntry := NILCheck(FileList^.At(Index));
  88.  
  89. (*    IF x <= DispLen THEN xs := 1
  90.                     ELSE xs := x-DispLen+1; *)
  91.  
  92.     IF x <=   DispLen THEN xs := 1
  93.     ELSE
  94.     IF x <= 2*DispLen THEN xs := DispLen+1
  95.     ELSE
  96.     IF x <= 3*DispLen THEN xs := 2*DispLen+1
  97.     ELSE
  98.     IF x <= 4*DispLen THEN xs := 3*DispLen+1
  99.                       ELSE xs := 4*DispLen+1;
  100.     (* I haven't found a simple formula yet, so I'm doing the
  101.        job with a table. That's the lazy's man solution .... *)
  102.  
  103.     IF ox <=   DispLen THEN oxs := 1
  104.     ELSE
  105.     IF ox <= 2*DispLen THEN oxs := DispLen+1
  106.     ELSE
  107.     IF ox <= 3*DispLen THEN oxs := 2*DispLen+1
  108.     ELSE
  109.     IF ox <= 4*DispLen THEN oxs := 3*DispLen+1
  110.                        ELSE oxs := 4*DispLen+1;
  111.  
  112.     IF Hilighted THEN
  113.      BEGIN TextColor(SelectFg); TextBackGround(SelectBg); END
  114.     ELSE
  115.      BEGIN
  116.       TextBackGround(NormBg);
  117.  
  118.       IF FileEntry^.IsADir THEN TextColor(DirFg)
  119.                            ELSE TextColor(NormFg)
  120.      END;
  121.  
  122.     GotoXY(1,y);
  123.  
  124.     s := FileEntry^.FormatScrollableDescription(xs,DispLen);
  125.     IF Selection THEN
  126.      BEGIN
  127.        IF ox > x  THEN BEGIN t := x; x := ox; ox := t; END
  128.                   ELSE t := x;
  129.        IF ox < xs THEN ox := xs;
  130.  
  131.        Write(Copy(s,1,EdStart+ox-xs-1));
  132.        TextBackGround(NormFg);  TextColor(NormBg);   Write(Copy(s,EdStart+ox-xs,x-ox));
  133.        TextBackGround(SelectBg);TextColor(SelectFg); Write(Copy(s,EdStart+x-xs,255));
  134.  
  135.        x := t;
  136.      END
  137.     ELSE Write(s);
  138.  
  139.     l := Length(FileEntry^.GetDesc);
  140.     IF l-xs < DispLen THEN
  141.      ClrEol
  142.     ELSE
  143.      BEGIN
  144.       TextColor(WarnFg); Write(Chr(16)); TextColor(NormFg);
  145.      END;
  146.  
  147. (*    IF x <= DispLen THEN GotoXY(EdStart+x-1,y)
  148.                     ELSE GotoXY(EdStart+DispLen-1,y) *)
  149.     GotoXY(EdStart+x-xs,y);
  150.    END
  151.   ELSE BEGIN GotoXY(1,y); ClrEol; END;
  152.  END;  (* DisplayFileEntry *)
  153.  
  154. PROCEDURE DrawDirLine(UpdateDir: BOOLEAN);
  155. (* Draw the line, which tells us where in the directory tree we are. *)
  156.  
  157. BEGIN
  158.  IF UpdateDir THEN
  159.   BEGIN
  160.    GetDir(0,ActDir);
  161.    IF ActDir[Length(ActDir)] <> '\' THEN ActDir := ActDir + '\';
  162.    UpString(ActDir);
  163.   END;
  164.  TextColor(DirFg); TextBackGround(NormBg);
  165.  GotoXY(1,2); Write(' ',ActDir); ClrEol;
  166. END; (* DrawDirLine *)
  167.  
  168. PROCEDURE ReDrawScreen;
  169. (* Redraws the full screen, needed after shelling out or after printing
  170.    the help screen.                                                     *)
  171.  
  172. VAR Index: INTEGER;
  173.  
  174. BEGIN
  175. (* GetDir(0,ActDir); *)
  176.  FOR Index := StartIndex TO StartIndex+MaxLines-4 DO
  177.   DisplayFileEntry(Index,0,1,FALSE,FALSE);
  178. END; (* ReDrawScreen *)
  179.  
  180.  
  181. (*-------------------------------------------------------- Read-Directory *)
  182. PROCEDURE ReadFiles;
  183. (* Scan the current directory and read in the DESCRIPT.ION file. Build a
  184.    file list database and associate the right description.
  185.  
  186.    Warn the user if there are too long descriptions or if there are too
  187.    much descriptions.                                                     *)
  188.  
  189. VAR i   : BYTE;
  190.     ch  : WORD;
  191.     Dir : PathStr;
  192.  
  193. BEGIN
  194.  Changed    := FALSE;
  195.  DescLong   := FALSE;
  196.  Index      := 0;
  197.  StartIndex := 0;
  198.  Dir := FExpand('.');
  199.  
  200.  IF FileList <> NIL THEN
  201.   BEGIN
  202.    Dispose(FileList,Done); FileList := NIL;
  203.   END;
  204.  
  205.  TextColor(StatusFg); TextBackGround(StatusBg);
  206.  GotoXY(1,MaxLines);
  207.  Write(Chars(' ',((ScreenWidth-40-Length(Dir)) DIV 2)),
  208.        'Scanning directory ',Dir,' .....  please wait.');
  209.  ClrEol;
  210.  
  211.  FileList := NIL; FileList := New(PFileList,Init(Dir,'*.*',0));
  212.  IF FileList = NIL THEN Abort('Unable to allocate FileList');
  213.  
  214.  IF (FileList^.Status = ListTooManyFiles) OR
  215.     (FileList^.Status = ListOutofMem) THEN
  216.   BEGIN
  217.    TextColor(NormFg); TextBackGround(NormBg);
  218.    FOR i := 3 TO MaxLines-1 DO
  219.     BEGIN
  220.      GotoXY(1,i); ClrEol;
  221.     END;
  222.    IF FileList^.Status = ListTooManyFiles THEN
  223.     ReportError('Warning! Too many files in directory, description file will be truncated! (Key)',(CutPasteDesc <> ''),Changed)
  224.    ELSE
  225.     ReportError('Warning! Out of memory, description file will be truncated! (Key)',(CutPasteDesc <> ''),Changed);
  226.   END;
  227.  
  228.  IF FileList^.Count > 0 THEN
  229.   BEGIN
  230.    DrawMainScreen(Index,FileList^.Count,1,0); DrawDirLine(TRUE);
  231.   END;
  232.  
  233.  IF DescLong THEN
  234.   BEGIN
  235.    TextColor(NormFg); TextBackGround(NormBg);
  236.    FOR i := 3 TO MaxLines-1 DO
  237.     BEGIN
  238.      GotoXY(1,i); ClrEol;
  239.     END;
  240.    ReportError('Warning! Some descriptions are too long; they will be truncated. Press any key.',(CutPasteDesc <> ''),Changed);
  241.   END;
  242. END;  (* ReadFiles *)
  243.  
  244. (*-------------------------------------------------------- Save Descriptions *)
  245. PROCEDURE SaveDescriptions;
  246. (* Save the modified descriptions currently held in memory onto disk.
  247.    Rename the old description file into DESCRIPT.OLD and write the
  248.    new one out. Any problems occuring at this point (disk full etc),
  249.    raise a warning message and cause a deletion of the (half-written)
  250.    description file DESCRIPT.ION. In this case the user "only" looses his
  251.    new, edited descriptions, but the old ones are stored in the DESCRIPT.OLD
  252.    file and can be restored by typing
  253.  
  254.    REN DESCRIPT.OLD DESCRIPT.ION
  255.    ATTRIB +H DESCRIPT.ION
  256.  
  257.    If all went fine, the old description file gets deleted. This procedure
  258.    minimizes data loss.                                                    *)
  259.  
  260. VAR DescFile  : TEXT;
  261.     DescSaved : BOOLEAN;
  262.     Time      : DateTime;
  263.     ch        : WORD;
  264.     FileEntry : PFileData;
  265.  
  266.  
  267.  PROCEDURE SaveEntry(FileEntry: PFileData); FAR;
  268.  (* Save a single description, writes a single line of the description
  269.     file. This procedures is called for each entry in the FileEntry list *)
  270.  
  271.  VAR Desc     : DescStr;
  272.      ProgInfo : STRING;
  273.      Dir      : DirStr;
  274.      BaseName : NameStr;
  275.      Ext      : ExtStr;
  276.  
  277.  BEGIN
  278.   Desc := FileEntry^.GetDesc;
  279.   StripLeadingSpaces(Desc); StripTrailingSpaces(Desc);
  280.   IF Desc <> '' THEN
  281.    BEGIN
  282.     StripTrailingSpaces(FileEntry^.Name);
  283.     Write(DescFile,FileEntry^.Name);
  284.  
  285.     StripLeadingSpaces(FileEntry^.Ext);
  286.     StripTrailingSpaces(FileEntry^.Ext);
  287.     IF FileEntry^.Ext <> '' THEN Write(DescFile,FileEntry^.Ext);
  288.  
  289.     Write(DescFile,' ',Desc);
  290.     IF DescSaved = FALSE THEN DescSaved := TRUE;
  291.  
  292.     ProgInfo :=  FileEntry^.GetProgInfo;
  293.     IF ProgInfo <> '' THEN Write(DescFile,ProgInfo);
  294.     WriteLn(DescFile);
  295.    END;
  296.  END; (* SaveEntry *)
  297.  
  298. BEGIN
  299.  DescSaved := FALSE;
  300.  IF DiskFree(0) < FileList^.Count*SizeOf(TFileData) THEN
  301.    ReportError('Probably out of disk space. Nevertheless trying to save DESCRIPT.ION...',(CutPasteDesc <> ''),Changed);
  302.  TextColor(StatusFg); TextBackGround(StatusBg);
  303.  GotoXY(1,MaxLines);
  304.  Write(Chars(' ',((ScreenWidth-41) div 2)),
  305.        'Saving descriptions........  please wait.');
  306.  ClrEol;
  307.  
  308.  {$I-}
  309.  Assign(DescFile,'DESCRIPT.ION'); Rename(DescFile,'DESCRIPT.OLD'); IORes := IOResult;
  310.  Assign(DescFile,'DESCRIPT.ION'); SetFAttr(DescFile,Archive); IORes := IOResult;
  311.  Rewrite(DescFile);
  312.  {$I+}
  313.  IF IOResult > 0 THEN
  314.   BEGIN
  315.    ReportError('Unable to write DESCRIPT.ION !',(CutPasteDesc <> ''),Changed);
  316.    {$I-}
  317.    Assign(DescFile,'DESCRIPT.OLD'); Rename(DescFile,'DESCRIPT.ION'); IORes := IOResult;
  318.    {$I+}
  319.   END
  320.  ELSE
  321.   BEGIN
  322.    FileList^.ForEach(@SaveEntry);
  323.    {$I-}
  324.    Close(DescFile);
  325.    {$I+}
  326.  
  327.    IF IOResult > 0 THEN
  328.     BEGIN
  329.      ReportError('Unable to write DESCRIPT.ION !',(CutPasteDesc <> ''),Changed);
  330.      {$I-}
  331.      Assign(DescFile,'DESCRIPT.OLD'); Rename(DescFile,'DESCRIPT.ION'); IORes := IOResult;
  332.      {$I+}
  333.     END
  334.    ELSE
  335.     BEGIN
  336.      IF DescSaved THEN SetFAttr(DescFile, Archive + Hidden)
  337.                   ELSE Erase(DescFile);  (* Don't keep zero-byte file. *)
  338.      Changed := FALSE; DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed,FALSE);
  339.      {$I-}
  340.      Assign(DescFile,'DESCRIPT.OLD'); Erase(DescFile); IORes := IOResult;
  341.      {$I+}
  342.     END;
  343.   END;
  344. END;  (* SaveDescriptions *)
  345.  
  346. (*-------------------------------------------------------- Edit Descriptions *)
  347. PROCEDURE EditDescriptions;
  348. (* This is the heart of 4DESC: the editing of the descriptions. *)
  349.  
  350. VAR Key          : WORD;
  351.     Drv          : STRING[3];
  352.     LastDrv      : CHAR;
  353.     x,y,l        : BYTE;        (* current cursor position *)
  354.     ox           : BYTE;        (* old cursor position *)
  355.     EditStr      : DescStr;
  356.     InShiftState : BOOLEAN;
  357.     Cmd          : BYTE;
  358.  
  359.     Cursor       : WORD;
  360.     OldDir       : DirStr;
  361.     ActFileData  : PFileData;
  362.     n            : NameExtStr;
  363.  
  364.     ReverseFlag  : BOOLEAN;   (* for sorting *)
  365.  
  366.  PROCEDURE UpdateLineNum(Index: INTEGER);
  367.  (* Update the line number indicator in the right corner and redraw
  368.     the associated description line                                 *)
  369.  
  370.  BEGIN
  371.   TextColor(StatusFg); TextBackGround(StatusBg);
  372.   GotoXY(46,1); Write(Index+1:5);
  373.  
  374.   IF Changed THEN DrawStatusLine(FALSE,(CutPasteDesc <> ''),Changed,ReverseFlag);
  375.  
  376.   IF Index < FileList^.Count THEN
  377.    BEGIN
  378.     EditStr := PFileData(FileList^.At(Index))^.GetDesc;
  379.     DisplayFileEntry(Index,0,1,FALSE,TRUE);
  380.    END;
  381.  
  382.   ActFileData := NILCheck(FileList^.At(Index));
  383.  END;
  384.  
  385.  PROCEDURE UpdateColNum(Col, CurDescLen: BYTE);
  386.  (* Update the column number indicator in the right corner *)
  387.  
  388.  VAR x,y: BYTE;
  389.  
  390.  BEGIN
  391.   x := WhereX; y := WhereY;
  392.   TextColor(StatusFg); TextBackGround(StatusBg);
  393.   GotoXY(66,1); Write(Col:3); GotoXY(77,1); Write(CurDescLen:3);
  394.  
  395. (*  TextBackGround(NormBg);
  396.     GotoXY(EdStart+40-xs,MaxLines); Write('^'); *)
  397.  
  398.   GotoXY(x,y);
  399.  END;
  400.  
  401.  PROCEDURE PrevIndex(VAR Index: INTEGER);
  402.  (* Go up one description line (if possible) *)
  403.  
  404.  BEGIN
  405.   Index := Max(Index-1,0);
  406.   IF Index <= StartIndex THEN
  407.    BEGIN
  408.     StartIndex := Max(Index-ScreenSize,0);
  409.     RedrawScreen;
  410.    END;
  411.   UpdateLineNum(Index);
  412.  END; (* PrevIndex *)
  413.  
  414.  PROCEDURE NextIndex(VAR Index: INTEGER);
  415.  (* Go down one description line (if possible) *)
  416.  
  417.  BEGIN
  418.   Index := Min(Index+1,FileList^.Count-1);
  419.   IF Index > StartIndex+ScreenSize THEN
  420.    BEGIN
  421.     StartIndex := Index-ScreenSize;
  422.     RedrawScreen;
  423.    END;
  424.   UpdateLineNum(Index);
  425.  END; (* NextIndex *)
  426.  
  427.  PROCEDURE QuerySaveDescriptions;
  428.  (* Ask the user if he really wants to save the descriptions. *)
  429.  
  430.  VAR ch: CHAR;
  431.  
  432.  BEGIN
  433.   IF Querier THEN
  434.    BEGIN
  435.     TextColor(StatusFg); TextBackGround(StatusBg);
  436.     IF Changed THEN
  437.      BEGIN
  438.       GotoXY(1,MaxLines);
  439.       Write(Chars(' ',(ScreenWidth-58) div 2),
  440.            'Descriptions have been edited. Shall they be saved (Y/N) ?');
  441.       ClrEol;
  442.       ch := ' ';
  443.       REPEAT
  444.         If KeyPressed Then ch := UpCase(ReadKey)
  445.         Else
  446.           If MouseLoaded Then
  447.             Begin
  448.               ButtonReleased(Left);
  449.               If ReleaseCount > 0 Then ch := 'Y';
  450.               ButtonReleased(Right);
  451.               If ReleaseCount > 0 Then ch := 'N';
  452.             End;
  453.       UNTIL (ch = 'Y') OR (ch = 'N');
  454.       Write(' ',ch);
  455.       IF ch = 'Y' THEN SaveDescriptions;
  456.      END;
  457.    END
  458.   ELSE SaveDescriptions; (* always save, when not in query mode *)
  459.  END; (* QuerySaveDescriptions *)
  460.  
  461.  PROCEDURE DirUp;
  462.  (* Go up one directory in the directory tree (if possible) *)
  463.  
  464.  BEGIN
  465.   IF Changed THEN QuerySaveDescriptions;
  466.   {$I-}
  467.   ChDir('..');
  468.   {$I+}
  469.   IF IOResult = 0 THEN
  470.    BEGIN
  471.     ReadFiles;
  472.     RedrawScreen;
  473.     DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed,ReverseFlag);
  474.     Index := 0; UpdateLineNum(Index);
  475.    END;
  476.  END;  (* DirUp *)
  477.  
  478.  PROCEDURE DirDown;
  479.  (* Go down one directory in the directory tree (if possible) *)
  480.  
  481.  BEGIN
  482.   IF (Index < FileList^.Count) THEN
  483.    BEGIN
  484.     n  := ActFileData^.Name+ActFileData^.Ext;
  485.     IF (ActFileData^.IsADir) AND (n[1] <> '.') THEN
  486.      BEGIN
  487.       IF Changed THEN QuerySaveDescriptions;
  488.       {$I-}
  489.       ChDir(n);
  490.       {$I+}
  491.       IF IOResult = 0 THEN
  492.        BEGIN
  493.         ReadFiles;
  494.         RedrawScreen;
  495.        END;
  496.       DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  497.       Index := 0; UpdateLineNum(Index);
  498.     END;  (* IF Description[Index].Size = DirSize *)
  499.    END;
  500.  END;  (* DirDown *)
  501.  
  502.  PROCEDURE ReSortDirectory;
  503.  
  504.  BEGIN
  505.   ReSortFileList; ReverseFlag := FALSE;
  506.   DrawStatusLine(FALSE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  507.  
  508.   StartIndex := 0; Index := 0;
  509.   RedrawScreen; UpdateLineNum(Index);
  510.  END; (* ReSortDirectory *)
  511.  
  512.  FUNCTION IsADelimiter(c: CHAR): BOOLEAN;
  513.  (* used by Ctrl-Left resp Ctrl-Right to recognize the end of a word *)
  514.  
  515.  BEGIN
  516.   IsADelimiter := (Pos(c,DelimiterTable) > 0);
  517.  END;
  518.  
  519. BEGIN  (* EditDescriptions *)
  520.  Index := 0; UpdateLineNum(Index);
  521.  
  522.  ResetCursor(Overwrite);
  523.  EditStr := ActFileData^.GetDesc;
  524.  ReverseFlag := FALSE; InShiftState := FALSE; x := 1;
  525.  REPEAT
  526.   REPEAT
  527.     Key := $0000;
  528.     IF KeyPressed THEN Key := GetKey
  529.     ELSE
  530.       BEGIN
  531.         IF MouseLoaded THEN
  532.           BEGIN
  533.             MouseMotion;
  534.             IF VMickey > VMickeysPerKeyPress THEN Key := kbDown
  535.             ELSE
  536.               IF VMickey < -VMickeysPerKeyPress THEN Key := kbUp
  537.               ELSE
  538.               IF HMickey >  HMickeysPerKeyPress THEN Key := kbRight
  539.               ELSE
  540.                 IF HMickey < -HMickeysPerKeyPress THEN Key := kbLeft
  541.                 ELSE
  542.                   BEGIN
  543.                     ButtonReleased(Left);
  544.                     IF ReleaseCount > 0 THEN Key := kbEnter;
  545.                     ButtonReleased(Right);
  546.                     IF ReleaseCount > 0 THEN Key := kbEsc;
  547.                   END;
  548.  
  549.           END;  (* if mouseloaded *)
  550.       END;
  551.   UNTIL Key <> $0000;
  552.  
  553.   IF NOT InShiftState THEN ox := x;
  554.   (* save the old cursor position for cutting *)
  555.   CASE Key OF
  556.    kbUp       : BEGIN
  557.                  ActFileData^.AssignDesc(EditStr);
  558.                  x := 1;
  559.                  DisplayFileEntry(Index,0,x,FALSE,FALSE); PrevIndex(Index);
  560.                  InShiftState := FALSE;
  561.                 END; (* Up *)
  562.  
  563.    kbDown     : BEGIN
  564.                  ActFileData^.AssignDesc(EditStr);
  565.                  x := 1;
  566.                  DisplayFileEntry(Index,0,x,FALSE,FALSE); NextIndex(Index);
  567.                  InShiftState := FALSE;
  568.                 END; (* Down *)
  569.  
  570.    kbLeft     : BEGIN
  571.                  x := Max(1,x-1);
  572.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  573.                 END; (* Left *)
  574.  
  575.    kbRight    : BEGIN
  576.                  x := Max(1,Min(1+x,Length(EditStr)+1));
  577.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  578.                 END; (* Right *)
  579.  
  580.    kbCtrlLeft : BEGIN
  581.                  DEC(x);
  582.                  WHILE (x > 0) AND IsADelimiter(EditStr[x]) DO DEC(x);
  583.                  WHILE (x > 0) AND NOT IsADelimiter(EditStr[x]) DO DEC(x);
  584.                  INC(x);
  585.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  586.                 END; (* ^Left *)
  587.  
  588.    kbCtrlRight: BEGIN
  589.                  l := Length(EditStr);
  590.                  WHILE (x < l) AND NOT IsADelimiter(EditStr[x]) DO INC(x);
  591.                  WHILE (x < l) AND IsADelimiter(EditStr[x]) DO INC(x);
  592.                  IF x = l THEN INC(x);
  593.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  594.                 END; (*  ^Right *)
  595.  
  596.    kbHome     : BEGIN
  597.                  x := 1;
  598.                  InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  599.                 END; (* Home *)
  600.  
  601.    kbEnd      : BEGIN
  602.                   x := Min(Length(EditStr)+1,MaxDescLen);
  603.                   InShiftState := (GetShiftState AND (kbRightShift+kbLeftShift) <> 0);
  604.                 END; (* End *)
  605.  
  606.    kbCtrlHome : BEGIN
  607.                  Delete(EditStr,1,x);
  608.                  ActFileData^.AssignDesc(EditStr);
  609.                  x := 1;
  610.                  Changed := TRUE; InShiftState := FALSE;
  611.                 END;  (* ^Home *)
  612.  
  613.    kbCtrlEnd  : BEGIN
  614.                  Delete(EditStr,x,MaxDescLen);
  615.                  ActFileData^.AssignDesc(EditStr);
  616.                  Changed := TRUE; InShiftState := FALSE;
  617.                 END;  (* ^End *)
  618.  
  619.    kbIns      : BEGIN
  620.                  IF GetShiftState AND kbCtrlShift = kbCtrlShift THEN (* ^Ins: Copy *)
  621.                   BEGIN
  622.                    CutPasteDesc := Copy(EditStr,ox,x-ox);
  623.                    Changed := TRUE;
  624.                   END
  625.                  ELSE IF GetShiftState AND (kbRightShift+kbLeftShift) <> 0 THEN (* Shift-Ins: Paste *)
  626.                   BEGIN
  627.                    IF CutPasteDesc > '' THEN
  628.                     BEGIN
  629.                      EditStr := Copy(EditStr,1,x-1)+CutPasteDesc+Copy(EditStr,x,255);
  630.                      ActFileData^.AssignDesc(EditStr);
  631.                      Changed := TRUE;
  632.                      IF PasteMovesToNextIndex THEN
  633.                       BEGIN
  634.                        DisplayFileEntry(Index,0,x,FALSE,FALSE);
  635.                        NextIndex(Index);
  636.                       END;
  637.                     END
  638.                   END
  639.                  ELSE
  640.                   BEGIN
  641.                    Overwrite := NOT Overwrite; ResetCursor(Overwrite);
  642.                   END;
  643.                 END; (* Ins *)
  644.  
  645.    kbDel      : BEGIN
  646.                  IF GetShiftState AND kbCtrlShift = kbCtrlShift THEN (* ^Del: Clear *)
  647.                   BEGIN
  648.                    System.Delete(EditStr,ox,x-ox); x := ox;
  649.                    ActFileData^.AssignDesc(EditStr);
  650.                    Changed := TRUE; InShiftState := FALSE;
  651.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  652.                   END
  653.                  ELSE IF GetShiftState AND (kbRightShift+kbLeftShift) <> 0 THEN (* Shift-Del: Cut *)
  654.                   BEGIN
  655.                    CutPasteDesc := Copy(EditStr,ox,x-ox);
  656.                    Delete(EditStr,ox,x-ox); x := ox;
  657.                    ActFileData^.AssignDesc(EditStr);
  658.                    Changed := TRUE; InShiftState := FALSE;
  659.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  660.                   END
  661.                  ELSE
  662.                   BEGIN
  663.                    IF x <= Length(EditStr) THEN Delete(EditStr,x,1);
  664.                    ActFileData^.AssignDesc(EditStr);
  665.                    Changed := TRUE;
  666.                   END;
  667.                 END; (* Del *)
  668.  
  669.    kbBack     : BEGIN
  670.                  Delete(EditStr,x-1,1);
  671.                  ActFileData^.AssignDesc(EditStr);
  672.                  IF x > 1 THEN
  673.                   BEGIN
  674.                    DEC(x);
  675.                    IF x > Length(EditStr) THEN x := Length(EditStr)+1;
  676.                   END;
  677.                  Changed := TRUE; InShiftState := FALSE;
  678.                 END; (* Backspace *)
  679.  
  680.    kbPgUp     : BEGIN
  681.                  ActFileData^.AssignDesc(EditStr);
  682.                  x := 1;
  683.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  684.                  Index := Max(Index-ScreenSize,0);
  685.                  StartIndex := Index;
  686.                  RedrawScreen;
  687.                  UpdateLineNum(Index);
  688.                  InShiftState := FALSE;
  689.                 END; (* PgUp *)
  690.  
  691.    kbPgDn     : BEGIN
  692.                  ActFileData^.AssignDesc(EditStr);
  693.                  Index := Min(Index+ScreenSize,FileList^.Count-1);
  694.                  StartIndex := Max(Index-ScreenSize,0);
  695.                  x := 1;
  696.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  697.                  RedrawScreen;
  698.                  UpdateLineNum(Index);
  699.                  InShiftState := FALSE;
  700.                 END; (* PgDn *)
  701.  
  702.    kbCtrlPgUp : BEGIN
  703.                  ActFileData^.AssignDesc(EditStr);
  704.                  x := 1;
  705.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  706.                  StartIndex := 0; Index := 0;
  707.                  RedrawScreen;
  708.                  UpdateLineNum(Index);
  709.  
  710.                  ActFileData^.AssignDesc(EditStr);
  711.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  712.                  IF Length(ActDir) > 3 THEN NextIndex(Index);
  713.                  InShiftState := FALSE;
  714.                 END; (* ^PgUp *)
  715.  
  716.    kbCtrlPgDn : BEGIN
  717.                  ActFileData^.AssignDesc(EditStr);
  718.                  x := 1;
  719.                  DisplayFileEntry(Index,0,x,FALSE,FALSE);
  720.                  StartIndex := Max(FileList^.Count-ScreenSize-1,0);
  721.                  Index := FileList^.Count-1;
  722.                  RedrawScreen;
  723.                  UpdateLineNum(Index);
  724.                  InShiftState := FALSE;
  725.                 END; (* ^PgDn *)
  726.  
  727.    kbAltD     : BEGIN
  728.                  EditStr := ''; ActFileData^.AssignDesc('');
  729.                  Changed := TRUE; InShiftState := FALSE;
  730.                  x := 1;
  731.                  IF PasteMovesToNextIndex THEN
  732.                   BEGIN
  733.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  734.                    NextIndex(Index);
  735.                   END;
  736.                 END; (* Alt-D *)
  737.  
  738.    kbAltM,
  739.    kbAltT     : BEGIN
  740.                  CutPasteDesc := ActFileData^.GetDesc;
  741.                  ActFileData^.AssignDesc(''); EditStr := '';
  742.                  Changed := TRUE; InShiftState := FALSE;
  743.                  x := 1;
  744.                  IF PasteMovesToNextIndex THEN
  745.                   BEGIN
  746.                    DisplayFileEntry(Index,0,x,FALSE,FALSE);
  747.                    NextIndex(Index);
  748.                   END;
  749.                 END; (* Alt-M / Alt-T *)
  750.  
  751.    kbAltC     : BEGIN
  752.                  CutPasteDesc := ActFileData^.GetDesc;
  753.                  x := 1;
  754.                  InShiftState := FALSE;
  755.                  DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  756.                 END; (* Alt-C *)
  757.  
  758.    kbAltP     : BEGIN
  759.                  IF CutPasteDesc > '' THEN
  760.                   BEGIN
  761.                    EditStr := CutPasteDesc; ActFileData^.AssignDesc(EditStr);
  762.                    Changed := TRUE; InShiftState := FALSE;
  763.                    IF PasteMovesToNextIndex THEN
  764.                     BEGIN
  765.                      DisplayFileEntry(Index,0,x,FALSE,FALSE);
  766.                      NextIndex(Index);
  767.                     END;
  768.                   END
  769.                 END;
  770.  
  771.    kbEnter    : BEGIN
  772.                   ActFileData^.AssignDesc(EditStr);
  773.                   x := 1;
  774.                   IF (Index < FileList^.Count) THEN
  775.                     BEGIN
  776.                       n  := ActFileData^.Name+ActFileData^.Ext;
  777.                       IF ActFileData^.IsADir THEN
  778.                         IF (n[1] = '.') AND (n[2] = '.') THEN DirUp
  779.                           ELSE
  780.                         IF n[1] <> '.' THEN DirDown;
  781.                     END;
  782.                 END; (* Enter = go into directory where the cursor is at *)
  783.  
  784.    kbF1       : BEGIN                                   (* F1: Help *)
  785.                  ShowHelpPage;
  786.                  ResetCursor(Overwrite);
  787.                  DrawMainScreen(Index,FileList^.Count,x,Length(EditStr));
  788.                  DrawDirLine(FALSE);
  789.                  RedrawScreen;
  790.                  UpdateLineNum(Index);
  791.                 END;  (* F1 *)
  792.  
  793.    kbF4       : DirDown; (* F4 *)
  794.    kbF5       : DirUp;   (* F5 *)
  795.  
  796.    kbAltL,
  797.    kbF6       : BEGIN                                   (* F6: Change Drive *)
  798.                  IF Changed THEN QuerySaveDescriptions;
  799.  
  800.                  ASM
  801.                   mov ah,0eh       (* Select Disk *)
  802.                   mov dl,3
  803.                   int 21h
  804.                   add al,'@'
  805.                   mov LastDrv,al
  806.                  END;
  807.  
  808.                  IF LastDrv > 'Z' THEN LastDrv := 'Z';
  809.  
  810.                  TextColor(StatusFg); TextBackGround(StatusBg); Drv := ' :';
  811.                  GotoXY(1,MaxLines);
  812.                  Write(Chars(' ',((ScreenWidth-24) div 2)),
  813.                       'New drive letter (A..',LastDrv,'): ');
  814.                  ClrEol;
  815.                  REPEAT
  816.                   Drv[1] := UpCase(ReadKey);
  817.                  UNTIL (Drv[1] >= 'A') AND (Drv[1] <= LastDrv);
  818.                  IF Drv[1] <= 'B' THEN Drv := Drv + '\';
  819.                  OldDir := ActDir;
  820.                  {$I-}
  821.                  ChDir(Drv);
  822.                  {$I+}
  823.                  IF IOResult = 0 THEN
  824.                   BEGIN
  825.                    GetDir(0,ActDir); IORes := IOResult;
  826.                    ReadFiles;
  827.                    IF FileList^.Count = 0 THEN
  828.                     BEGIN
  829.                      IF (Length(OldDir) > 3) AND (OldDir[Length(OldDir)] = '\') THEN
  830.                         Delete(OldDir,Length(OldDir),1);
  831.                      {$I-}
  832.                      ChDir(OldDir); IORes := IOResult;
  833.                      {$I+}
  834.                      ReportError('There are no files on drive '+Drv+'. Press any key.',(CutPasteDesc <> ''),Changed);
  835.                      ReadFiles;
  836.                     END;
  837.                    RedrawScreen;
  838.                    Index := 0;
  839.                    UpdateLineNum(Index);
  840.                   END
  841.                  ELSE
  842.                   ReportError('Drive '+Drv+' not ready! Drive remains unchanged, press a key.',(CutPasteDesc <> ''),Changed);
  843.                 END;  (* Alt-L or F6 *)
  844.  
  845.    kbF2,
  846.    kbF10     : BEGIN                                    (* F2, F10: Save *)
  847.                 SaveDescriptions;
  848.                 UpdateLineNum(Index);
  849.                END; (* F10 or F2 *)
  850.    kbAltS,
  851.    kbShiftF10: BEGIN                                    (* Shell to [4]DOS *)
  852.                 IF Changed THEN QuerySaveDescriptions;
  853.  
  854.                 DoneMemory;
  855.                 SetMemTop(HeapPtr);
  856.  
  857.                 NormVideo; ClrScr;
  858.                 WriteLn('Type `Exit'' to return to 4DESC.');
  859.                 SwapVectors;
  860.                 Exec(GetEnv('COMSPEC'),'');
  861.                 SwapVectors;
  862.  
  863.                 SetMemTop(HeapEnd);
  864.                 InitMemory;
  865.  
  866.                 IF MouseLoaded THEN MouseReset;
  867.  
  868.                 ClrScr;
  869.                 DrawMainScreen(Index,FileList^.Count,x,Length(EditStr));
  870.                 DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  871.                 DrawDirLine(TRUE);
  872.                 IF DosError > 0 THEN
  873.                   ReportError('Can''t load command interpreter / program execution failed.',
  874.                              (CutPasteDesc <> ''),Changed);;
  875.                 ReadFiles;
  876.                 RedrawScreen;
  877.                 UpdateLineNum(Index);
  878.                 ResetCursor(Overwrite);
  879.                END; (* Alt-S or F10 *)
  880.  
  881.    kbF3,                                                (* F3, Alt-V: View File *)
  882.    kbAltV,                                              (* Alt-E: Edit File *)
  883.    kbAltE      : IF (Index < FileList^.Count) THEN
  884.                 BEGIN
  885.                  IF NOT ActFileData^.IsADir THEN
  886.                   BEGIN
  887.                    NewName := ActFileData^.Name;
  888.                    StripTrailingSpaces(NewName);
  889.                    NewName := NewName+ActFileData^.Ext;
  890.                    NewDir := ActDir; (* I do not want to loose actdir, newdir
  891.                                         is only a "dummy" variable. *)
  892.                    IF NewDir[Length(NewDir)] = '\' THEN Delete(NewDir,Length(NewDir),1);
  893.  
  894.                    DoneMemory;
  895.                    SetMemTop(HeapPtr);
  896.                    SwapVectors;
  897.  
  898.                    NormVideo; ClrScr;
  899.  
  900.                    IF Key = kbAltE THEN
  901.                     Exec(GetEnv('COMSPEC'),'/c '+EditCmd+' '+NewDir+'\'+NewName)
  902.                    ELSE
  903.                     Exec(GetEnv('COMSPEC'),'/c '+ListCmd+' '+NewDir+'\'+NewName);
  904.  
  905.                    SwapVectors;
  906.                    SetMemTop(HeapEnd);
  907.                    InitMemory;
  908.  
  909.                    IF MouseLoaded THEN MouseReset;
  910.  
  911.                    ClrScr;
  912.                    DrawMainScreen(Index,FileList^.Count,x,Length(EditStr));
  913.                    DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  914.                    DrawDirLine(FALSE);
  915.                    IF DosError > 0 THEN ReportError('Can''t load command interpreter/program execution failed.',
  916.                                                    (CutPasteDesc <> ''),Changed);
  917.                    RedrawScreen;
  918.                    UpdateLineNum(Index);
  919.                    ResetCursor(Overwrite);
  920.                  END;
  921.                 END; (* F3, Alt-V, or Alt-E *)
  922.    (* Sorting Options *)
  923.    Ord('R')-64  : BEGIN
  924.                    ReverseFlag := NOT ReverseFlag;
  925.                    DrawStatusLine(FALSE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  926.                   END;
  927.    Ord('N')-64  : BEGIN
  928.                    IF NOT ReverseFlag THEN SortKey := SortByName
  929.                                       ELSE SortKey := SortByNameRev;
  930.                    ReSortDirectory;
  931.                   END;
  932.    Ord('E')-64  : BEGIN
  933.                    IF NOT ReverseFlag THEN SortKey := SortByExt
  934.                                       ELSE SortKey := SortByExtRev;
  935.                    ReSortDirectory;
  936.                   END;
  937.    Ord('S')-64  : BEGIN
  938.                    IF NOT ReverseFlag THEN SortKey := SortBySize
  939.                                       ELSE SortKey := SortBySizeRev;
  940.                    ReSortDirectory;
  941.                   END;
  942.    Ord('D')-64  : BEGIN
  943.                    IF NOT ReverseFlag THEN SortKey := SortByDate
  944.                                       ELSE SortKey := SortByDateRev;
  945.                    ReSortDirectory;
  946.                   END;
  947.   ELSE
  948.    IF (Ord(Key) > 31) AND (Ord(Key) < 256) THEN
  949.     BEGIN
  950.      IF NOT Changed THEN Changed := TRUE;
  951.      ReverseFlag := FALSE; InShiftState := FALSE;
  952.  
  953.      IF x <= MaxDescLen THEN
  954.       BEGIN
  955.        IF Overwrite AND (x <= Length(EditStr)) THEN
  956.          EditStr[x] := Chr(Key)
  957.        ELSE
  958.          EditStr := Copy(EditStr,1,x-1)+Chr(Key)+Copy(EditStr,x,255);
  959.  
  960.        ActFileData^.AssignDesc(EditStr);
  961.        INC(x); UpdateColNum(x,Length(EditStr));
  962.       END;
  963.     END; (* all others *)
  964.   END;   (* case *)
  965.  
  966.   (* Select with the Shift Keys *)
  967.   IF InShiftState THEN CutPasteDesc := Copy(EditStr,ox,x-ox);
  968.  
  969.   IF Changed THEN
  970.    DrawStatusLine(TRUE,(CutPasteDesc <> ''),Changed, ReverseFlag);
  971.   DisplayFileEntry(Index,ox,x,InShiftState,TRUE);
  972.   UpdateColNum(x,Length(EditStr));
  973.  UNTIL (Key = kbEsc)  OR  (* Esc   = exit to original directory and save *)
  974.        (Key = kbAltX) OR  (* Alt-X = exit to current  directory and save *)
  975.        (Key = kbAltQ);    (* Alt-Q = exit to original directory, don't save *)
  976.  
  977.  IF (Key = kbEsc) OR (Key = kbAltQ) THEN ResetDir := TRUE
  978.                                     ELSE ResetDir := FALSE;
  979.  
  980.  IF Changed AND (Key <> kbAltQ) THEN QuerySaveDescriptions;
  981. END; (* EditDescriptions *)
  982.  
  983. (*-------------------------------------------------------- Main *)
  984. BEGIN
  985.  {$I-}
  986.  GetDir(0,StartDir); IORes  := IOResult;
  987.  {$I+}
  988.  ShowHelp := FALSE; Querier := TRUE;
  989.  IF ParamCount > 0 THEN
  990.   BEGIN
  991.    FOR i := 1 TO Min(2,ParamCount) DO
  992.     BEGIN
  993.      FirstParam := ParamStr(i);
  994.      IF (FirstParam[1] = '/') OR (FirstParam[1] = '-') THEN
  995.       BEGIN
  996.        IF NOT Monochrome THEN Monochrome := (UpCase(FirstParam[2]) = 'M');
  997.        IF     Querier    THEN Querier    := NOT (UpStr(Copy(FirstParam,2,Length(FirstParam)-1)) = 'DONTASK');
  998.        IF NOT ShowHelp   THEN ShowHelp   := (UpCase(FirstParam[2]) = 'H') OR
  999.                                             (FirstParam[2] = '?');
  1000.       END;
  1001.     END;  (* for ... do begin *)
  1002.    NewDir := UpStr(ParamStr(ParamCount));
  1003.    IF (NewDir[1] <> '/') AND (NewDir[1] <> '-') THEN
  1004.     BEGIN
  1005.     {$I-}
  1006.     ChDir(NewDir); IORes := IOResult;
  1007.     {$I+}
  1008.     END;
  1009.   END;  (* if paramcount > 0 *)
  1010.  
  1011.  (* Read the .INI files *)
  1012.  InitMemory;
  1013.  
  1014.  INIStrings := New(PINIStrings,Init); (* Read in the .INI file(s) *)
  1015.  
  1016.  IF INIFileExists THEN StringDateHandling.EvaluateINIFileSettings;
  1017.  (* The Date & Time Formats are country-specific and are pre-initialized
  1018.     in the StringDateHandling initialize-section. Re-Initializing it
  1019.     with "our" defaults is not what the users wants.                     *)
  1020.  
  1021.  DescriptionHandling.EvaluateINIFileSettings;
  1022.  DisplayKeyboardAndCursor.EvaluateINIFileSettings;
  1023.  ChooseColors(Monochrome);
  1024.  
  1025.  dmouse.EvaluateINIFileSettings;
  1026.  IF UseMouse THEN MouseReset;
  1027.  
  1028.  DelimiterTable := ReadSettingsString('misc','delimiters',DelimiterTable);
  1029.  DelimiterTable := ' '+DelimiterTable;
  1030.  
  1031.  PasteMovesToNextIndex :=  (ReadSettingsChar('misc','pastemovestonextindex','y') = 'y');
  1032.  
  1033.  overwrite := (ReadSettingsString('','editmode','overstrike') = 'overstrike');
  1034.  
  1035.  Dispose(INIStrings,Done); INIStrings := NIL;
  1036.  
  1037.  EdStart := 25+Length(DateFormat)+Length(TimeFormat);
  1038.  DispLen := ScreenWidth-EdStart;
  1039.  
  1040.  Str(DispLen,s); Template:= '%-12s %s %s %s %-'+s+'s';
  1041.  Changed := FALSE; CutPasteDesc := '';
  1042.  
  1043.  DrawMainScreen(0,0,0,0);
  1044.  IF ShowHelp THEN ShowHelpPage;
  1045.  IF IORes > 0 THEN
  1046.   ReportError(NewDir+' not found. Directory remains unchanged.',FALSE,FALSE);
  1047.  
  1048.  ReadFiles;
  1049.  IF DosError = 0 THEN
  1050.   BEGIN
  1051.    RedrawScreen;
  1052.    EditDescriptions;
  1053.   END
  1054.  ELSE
  1055.   BEGIN
  1056.    ReportError('Drive '+NewDir+' not ready, exiting (key).',FALSE,FALSE);
  1057.    ResetDir := TRUE;
  1058.   END;
  1059.  
  1060.  Dispose(FileList,Done); FileList := NIL;
  1061.  DoneMemory;
  1062.  
  1063.  IF ResetDir THEN
  1064.    BEGIN
  1065.      {$I-}
  1066.      ChDir(StartDir);
  1067.      IORes := IOResult;
  1068.      {$I+}
  1069.    END;
  1070.  
  1071.  IF MouseLoaded THEN MouseReset;
  1072.  SetCursorShape(OrigCursor);
  1073.  NormVideo;
  1074.  ClrScr;
  1075.  WriteLn(Header1);
  1076.  WriteLn(Header2);
  1077.  WriteLn;
  1078.  WriteLn('This program is freeware: you are allowed to use, copy it free');
  1079.  WriteLn('of charge, but you may not sell or hire 4DESC.');
  1080. END.
  1081.