Subversion Repositories oidplus

Rev

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

  1. program OIDPLUS;
  2.  
  3. (************************************************)
  4. (* OIDPLUS.PAS                                  *)
  5. (* Author:   Daniel Marschall                   *)
  6. (* Revision: 2022-10-11                         *)
  7. (* License:  Apache 2.0                         *)
  8. (* This file contains:                          *)
  9. (* - "OIDplus for DOS" program                  *)
  10. (************************************************)
  11.  
  12. (* IMPORTANT:                                                  *)
  13. (* When you compile this code with Turbo Pascal 7.01,          *)
  14. (* it won't run on fast PCs (Runtime Error 200).               *)
  15. (* The built EXE file needs to be patched.                     *)
  16. (* The program "PatchCRT" by Kennedy Software                  *)
  17. (* WON'T work because it somehow breaks our "_Pause" function. *)
  18. (* Instead, use the tool "TPPATCH" by Andreas Bauer.           *)
  19.  
  20. uses
  21.   Dos, Crt, Drivers, StrList, VtsFuncs, VtsCui, OidFile, OidUtils,
  22.   Weid;
  23.  
  24. const
  25.   VERSIONINFO            = 'Revision: 2022-10-10';
  26.   TITLEBAR_LEFT_TEXT     = 'OIDplus';
  27.   DISKIO_SOUND_DEBUGGING = false;
  28.   DISKIO_SOUND_DELAY     = 500;
  29.   ASNEDIT_LINES          = 10;
  30.   DESCEDIT_LINES         = 10;
  31.   DESCEDIT_PADDING       = 3;
  32.   ACTIONMENU_SIZE        = 5;
  33.   MAINMENU_WIDTH         = 15;
  34.   MAINMENU_HEIGHT        = 3;
  35.   MAINMENU_ALLOW_ESC     = false;
  36.   TREEVIEW_INDENT        = 0;
  37.   TREEVIEW_INCLUDE_DESC  = true;
  38.   TREEVIEW_WIDTH         = 80;
  39.   OID_EXTENSION          = '.OID';
  40.   TREEVIEW_FILENAME      = 'OIDTREE.TXT';
  41.  
  42. procedure _Pause;
  43. begin
  44.   DrawStatusBar('Press any key to continue');
  45.   CursorOn;
  46.   ReadKey;
  47.   CursorOff;
  48.   DrawStatusBar('');
  49. end;
  50.  
  51. function _WriteOidFile(filename: string; oid: POid; ShowErrorMessage: boolean): boolean;
  52. var
  53.   res: boolean;
  54. begin
  55.   DrawStatusBar('Write file ' + filename + '...');
  56.   res := WriteOidFile(filename, oid);
  57.   if DISKIO_SOUND_DEBUGGING then
  58.   begin
  59.     Sound(70);
  60.     Delay(DISKIO_SOUND_DELAY - 10);
  61.     NoSound;
  62.     Delay(10);
  63.   end;
  64.   DrawStatusBar('');
  65.  
  66.   _WriteOidFile := res;
  67.  
  68.   if ShowErrorMessage and not res then
  69.   begin
  70.     ShowMessage('Cannot write to file ' + filename, 'ERROR', true);
  71.     _Pause;
  72.   end;
  73. end;
  74.  
  75. function _ReadOidFile(filename: string; oid: POid; ShowErrorMessage: boolean): boolean;
  76. var
  77.   res: boolean;
  78. begin
  79.   DrawStatusBar('Read file ' + filename + '...');
  80.   res := ReadOidFile(filename, oid);
  81.   if DISKIO_SOUND_DEBUGGING then
  82.   begin
  83.     Sound(50);
  84.     Delay(DISKIO_SOUND_DELAY - 10);
  85.     NoSound;
  86.     Delay(10);
  87.   end;
  88.   DrawStatusBar('');
  89.  
  90.   _ReadOidFile := res;
  91.  
  92.   if ShowErrorMessage and not res then
  93.   begin
  94.     ShowMessage('Cannot read file ' + filename, 'ERROR', true);
  95.     _Pause;
  96.   end;
  97. end;
  98.  
  99. function _ShowASNIds(childOID: POID): string;
  100. var
  101.   j, jmax: integer;
  102.   sTmp: string;
  103. begin
  104.   sTmp := '';
  105.   jmax := ListCount(childOID^.ASNIds)-1;
  106.   for j := 0 to jmax do
  107.   begin
  108.     if j = 0 then sTmp := sTmp + ' (';
  109.     sTmp := sTmp + ListGetElement(childOID^.ASNIds, j);
  110.     if j = jmax then
  111.       sTmp := sTmp + ')'
  112.     else
  113.       sTmp := sTmp + ', ';
  114.   end;
  115.   _ShowASNIds := sTmp;
  116. end;
  117.  
  118. function AsnAlreadyExisting(oid: POID; asnid: string): boolean;
  119. begin
  120.   AsnAlreadyExisting := ListContains(oid^.AsnIds, asnid);
  121. end;
  122.  
  123. function AsnEditor(oid: POID): boolean;
  124. var
  125.   asnList: PStringList;
  126.   i: integer;
  127.   x, y, w, h: integer;
  128.   res: integer;
  129.   sInput: string;
  130.   menuIdNew, menuIdSave, menuIdExit: integer;
  131. begin
  132.   AsnEditor := false;
  133.  
  134.   repeat
  135.     CreateList(asnList);
  136.  
  137.     for i := 0 to ListCount(oid^.ASNIds)-1 do
  138.     begin
  139.       ListAppend(asnList, ListGetElement(oid^.ASNIDs, i));
  140.     end;
  141.     menuIdNew  := ListAppend(asnList, '<NEW>');
  142.     menuIdSave := ListAppend(asnList, '<SAVE>');
  143.     menuIdExit := ListAppend(asnList, '<CANCEL>');
  144.  
  145.     DrawStatusBar('');
  146.     x := SINGLE_LINE_BOX_PADDING;
  147.     y := ScreenHeight div 2 - ASNEDIT_LINES div 2;
  148.     w := ScreenWidth - (SINGLE_LINE_BOX_PADDING-1)*2;
  149.     h := ASNEDIT_LINES;
  150.     res := DrawSelectionList(x, y, w, h,
  151.                              asnList, true,
  152.                              'EDIT ASN.1 IDENTIFIERS',
  153.                              2);
  154.     FreeList(asnList);
  155.  
  156.     (* Change double-border to thin-border *)
  157.     DrawThinBorder(x-1, y-1, w+2, h+2);
  158.     GoToXY(x+1, y-1);
  159.     Write('EDIT ASN.1 IDENTIFIERS');
  160.  
  161.     if res = -1 then
  162.     begin
  163.       exit;
  164.     end
  165.     else if res = menuIdNew then
  166.     begin
  167.       (* "NEW" item was selected *)
  168.       sInput := '';
  169.       CursorOn;
  170.       repeat
  171.         if QueryVal(sInput,
  172.                     SINGLE_LINE_BOX_PADDING_INNER,
  173.                     ScreenHeight div 2,
  174.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  175.                     1,
  176.                     'ADD SINGLE ASN.1 ID',
  177.                     2) then
  178.         begin
  179.           if sInput = '' then continue;
  180.           if not ASN1IDValid(sInput) then
  181.           begin
  182.             ShowMessage('Invalid ASN1.ID! (Require -, a..z, A..Z, 0..9, begin with a-z)', 'ERROR', true);
  183.             _Pause;
  184.           end
  185.           else if AsnAlreadyExisting(oid, sInput) then
  186.           begin
  187.             ShowMessage('ASN.1 identifier is already existing on this arc', 'ERROR', true);
  188.             _Pause;
  189.           end
  190.           else
  191.           begin
  192.             ListAppend(oid^.ASNIDs, sInput);
  193.             break;
  194.           end;
  195.         end
  196.         else break;
  197.       until false;
  198.       CursorOff;
  199.     end
  200.     else if res = menuIdSave then
  201.     begin
  202.       (* "SAVE" item was selected *)
  203.       AsnEditor := true;
  204.       Exit;
  205.     end
  206.     else if res = menuIdExit then
  207.     begin
  208.       (* "CANCEL" item was selected *)
  209.       AsnEditor := false;
  210.       Exit;
  211.     end
  212.     else
  213.     begin
  214.       DrawStatusBar('Note: Remove the text to delete the ASN.1 identifier');
  215.       sInput := ListGetElement(oid^.ASNIDs, res);
  216.           CursorOn;
  217.       repeat
  218.         if QueryVal(sInput,
  219.                     SINGLE_LINE_BOX_PADDING_INNER,
  220.                     ScreenHeight div 2,
  221.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  222.                     1,
  223.                     'EDIT SINGLE ASN.1 ID',
  224.                     2) then
  225.         begin
  226.           if sInput = '' then
  227.           begin
  228.             (* Empty input = Delete ASN.1 ID *)
  229.             ListDeleteElementByIndex(oid^.ASNIDs, res);
  230.             break;
  231.           end
  232.           else if not ASN1IDValid(sInput) then
  233.           begin
  234.             ShowMessage('Invalid ASN1.ID! (Require -, a..z, A..Z, 0..9, begin with a-z)', 'ERROR', true);
  235.             _Pause;
  236.           end
  237.           else if AsnAlreadyExisting(oid, sInput) and
  238.               not (ListGetElement(oid^.ASNIDs, res) = sInput) then
  239.           begin
  240.             ShowMessage('ASN.1 identifier is already existing on this arc', 'ERROR', true);
  241.             _Pause;
  242.           end
  243.           else
  244.           begin
  245.             ListSetElement(oid^.ASNIDs, res, sInput);
  246.             break;
  247.           end;
  248.         end
  249.         else break;
  250.       until false;
  251.       CursorOff;
  252.     end;
  253.   until false;
  254. end;
  255.  
  256. function UnicodeLabelAlreadyExisting(oid: POID; unicodeLabel: string): boolean;
  257. begin
  258.   UnicodeLabelAlreadyExisting := ListContains(oid^.UnicodeLabels, unicodeLabel);
  259. end;
  260.  
  261. function IriEditor(oid: POID): boolean;
  262. var
  263.   iriList: PStringList;
  264.   i: integer;
  265.   x, y, w, h: integer;
  266.   res: integer;
  267.   sInput: string;
  268.   menuIdNew, menuIdSave, menuIdExit: integer;
  269. begin
  270.   IriEditor := false;
  271.  
  272.   repeat
  273.     CreateList(iriList);
  274.  
  275.     for i := 0 to ListCount(oid^.UnicodeLabels)-1 do
  276.     begin
  277.       ListAppend(iriList, ListGetElement(oid^.UnicodeLabels, i));
  278.     end;
  279.     menuIdNew  := ListAppend(iriList, '<NEW>');
  280.     menuIdSave := ListAppend(iriList, '<SAVE>');
  281.     menuIdExit := ListAppend(iriList, '<CANCEL>');
  282.  
  283.     DrawStatusBar('');
  284.     x := SINGLE_LINE_BOX_PADDING;
  285.     y := ScreenHeight div 2 - ASNEDIT_LINES div 2;
  286.     w := ScreenWidth - (SINGLE_LINE_BOX_PADDING-1)*2;
  287.     h := ASNEDIT_LINES;
  288.     res := DrawSelectionList(x, y, w, h,
  289.                              iriList, true,
  290.                              'EDIT UNICODE LABELS',
  291.                              2);
  292.     FreeList(iriList);
  293.  
  294.     (* Change double-border to thin-border *)
  295.     DrawThinBorder(x-1, y-1, w+2, h+2);
  296.     GoToXY(x+1, y-1);
  297.     Write('EDIT UNICODE LABELS');
  298.  
  299.     if res = -1 then
  300.     begin
  301.       exit;
  302.     end
  303.     else if res = menuIdNew then
  304.     begin
  305.       (* "NEW" item was selected *)
  306.       sInput := '';
  307.       CursorOn;
  308.       repeat
  309.         if QueryVal(sInput,
  310.                     SINGLE_LINE_BOX_PADDING_INNER,
  311.                     ScreenHeight div 2,
  312.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  313.                     1,
  314.                     'ADD SINGLE UNICODE LABEL',
  315.                     2) then
  316.         begin
  317.           if sInput = '' then continue;
  318.           if not UnicodeLabelValid(sInput) then
  319.           begin
  320.             ShowMessage('Invalid Unicode Label!', 'ERROR', true);
  321.             _Pause;
  322.           end
  323.           else if UnicodeLabelAlreadyExisting(oid, sInput) then
  324.           begin
  325.             ShowMessage('Unicode Label is already existing on this arc', 'ERROR', true);
  326.             _Pause;
  327.           end
  328.           else
  329.           begin
  330.             ListAppend(oid^.UnicodeLabels, sInput);
  331.             break;
  332.           end;
  333.         end
  334.         else break;
  335.       until false;
  336.       CursorOff;
  337.     end
  338.     else if res = menuIdSave then
  339.     begin
  340.       (* "SAVE" item was selected *)
  341.       IriEditor := true;
  342.       Exit;
  343.     end
  344.     else if res = menuIdExit then
  345.     begin
  346.       (* "CANCEL" item was selected *)
  347.       IriEditor := false;
  348.       Exit;
  349.     end
  350.     else
  351.     begin
  352.       DrawStatusBar('Note: Remove the text to delete the Unicode Label');
  353.       sInput := ListGetElement(oid^.UnicodeLabels, res);
  354.           CursorOn;
  355.       repeat
  356.         if QueryVal(sInput,
  357.                     SINGLE_LINE_BOX_PADDING_INNER,
  358.                     ScreenHeight div 2,
  359.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  360.                     1,
  361.                     'EDIT SINGLE UNICODE LABEL',
  362.                     2) then
  363.         begin
  364.           if sInput = '' then
  365.           begin
  366.             (* Empty input = Delete Unicode label *)
  367.             ListDeleteElementByIndex(oid^.UnicodeLabels, res);
  368.             break;
  369.           end
  370.           else if not UnicodeLabelValid(sInput) then
  371.           begin
  372.             ShowMessage('Invalid Unicode Label!', 'ERROR', true);
  373.             _Pause;
  374.           end
  375.           else if UnicodeLabelAlreadyExisting(oid, sInput) and
  376.               not (ListGetElement(oid^.UnicodeLabels, res) = sInput) then
  377.           begin
  378.             ShowMessage('Unicode Label is already existing on this arc', 'ERROR', true);
  379.             _Pause;
  380.           end
  381.           else
  382.           begin
  383.             ListSetElement(oid^.UnicodeLabels, res, sInput);
  384.             break;
  385.           end;
  386.         end
  387.         else break;
  388.       until false;
  389.       CursorOff;
  390.     end;
  391.   until false;
  392. end;
  393.  
  394. function DescEditor(oid: POID): boolean;
  395. var
  396.   sInput: string;
  397. begin
  398.   DescEditor := false;
  399.  
  400.   DrawStatusBar('Note: Press Ctrl+Return for a line-break.');
  401.   sInput := oid^.description;
  402.   CursorOn;
  403.   if QueryVal(sInput,
  404.               DESCEDIT_PADDING,
  405.               ScreenHeight div 2 - DESCEDIT_LINES div 2,
  406.               ScreenWidth - (DESCEDIT_PADDING-1)*2,
  407.               DESCEDIT_LINES,
  408.               'EDIT DESCRIPTION',
  409.               2) then
  410.   begin
  411.     oid^.description := sInput;
  412.     DescEditor := true; (* request caller to save <oid> *)
  413.   end;
  414.   CursorOff;
  415. end;
  416.  
  417. function NextPossibleFileID: string;
  418. var
  419.   DirInfo: SearchRec;
  420.   iId, imax: LongInt;
  421.   sId: string;
  422. begin
  423.   FindFirst(RepeatStr('?',8)+OID_EXTENSION, Archive, DirInfo);
  424.   imax := -1;
  425.   while DosError = 0 do
  426.   begin
  427.     sId := Copy(DirInfo.Name, 1, 8);
  428.     if IsPositiveIntegerOrZero(sId) then
  429.     begin
  430.       iId := StrToInt(sId);
  431.       if iId > iMax then iMax := iId;
  432.     end;
  433.     FindNext(DirInfo);
  434.   end;
  435.   NextPossibleFileId := ZeroPad(iMax+1, 8);
  436. end;
  437.  
  438. function NumIdAlreadyExisting(parentOID: POID; arcval: string): boolean;
  439. var
  440.   searchDotNotation: string;
  441.   sTmp: string;
  442.   i: integer;
  443. begin
  444.   if parentOID^.DotNotation = '' then
  445.     searchDotNotation := arcval
  446.   else
  447.     searchDotNotation := parentOID^.DotNotation + '.' + arcval;
  448.   for i := 0 to ListCount(parentOID^.SubIds)-1 do
  449.   begin
  450.     sTmp := ListGetElement(parentOID^.SubIds, i);
  451.     if DotNotationPart(sTmp) = searchDotNotation then
  452.     begin
  453.       NumIdAlreadyExisting := true;
  454.       exit;
  455.     end;
  456.   end;
  457.   NumIdAlreadyExisting := false;
  458. end;
  459.  
  460. function NumIdEditor(oid: POID; parentOID: POID): boolean;
  461. var
  462.   sInput: string;
  463.   title: string;
  464.   base36mode: boolean;
  465.   arcval: string;
  466. begin
  467.   NumIdEditor := false;
  468.   sInput := '';
  469.  
  470.   base36mode := false;
  471.   CursorOn;
  472.   repeat
  473.     if base36mode then
  474.     begin
  475.       DrawStatusBar('Press ESC to cancel');
  476.       title := 'ENTER BASE36 ID'
  477.     end
  478.     else
  479.     begin
  480.       DrawStatusBar('Enter "WEID" to enter a Base36 instead of Base10; press ESC to cancel');
  481.       title := 'ENTER NUMERIC ID';
  482.     end;
  483.     if QueryVal(sInput,
  484.                 SINGLE_LINE_BOX_PADDING_INNER,
  485.                 ScreenHeight div 2,
  486.                 ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  487.                 1,
  488.                 title,
  489.                 2) then
  490.     begin
  491.       if sInput = '' then continue;
  492.       if not base36mode and (sInput = 'WEID') then
  493.       begin
  494.         sInput := '';
  495.         base36mode := true;
  496.       end
  497.       else if not base36mode and not IsPositiveIntegerOrZero(sInput) then
  498.       begin
  499.         ShowMessage('Invalid numeric ID (must be a positive integer)', 'ERROR', true);
  500.         _Pause;
  501.       end
  502.       else if base36mode and not IsBase36String(sInput) then
  503.       begin
  504.         ShowMessage('Invalid base36 ID (must be 0..9, A..Z)', 'ERROR', true);
  505.         _Pause;
  506.       end
  507.       else
  508.       begin
  509.         if base36mode then
  510.           arcval := base_convert_bigint(sInput, 36, 10)
  511.         else
  512.           arcval := sInput;
  513.         arcval := StripLeadingZeros(arcval);
  514.  
  515.         if (parentOID^.DotNotation='') and (arcval <> '0') and (arcval <> '1') and (arcval <> '2') then
  516.         begin
  517.           ShowMessage('Invalid numeric ID (root arc can only be 0, 1, or 2)', 'ERROR', true);
  518.           _Pause;
  519.         end
  520.         else if ((parentOID^.DotNotation='0') or (parentOID^.DotNotation='1')) and
  521.                 ((Length(arcval)>2) or (StrToInt(arcval) > 39)) then
  522.         begin
  523.           ShowMessage('Invalid numeric ID (root 0 and 1 must have sub-arc of 0..39)', 'ERROR', true);
  524.           _Pause;
  525.         end
  526.         else if NumIdAlreadyExisting(parentOID, arcval) then
  527.         begin
  528.           ShowMessage('This numeric ID is already used in this arc', 'ERROR', true);
  529.           _Pause;
  530.         end
  531.         else
  532.         begin
  533.           if parentOID^.DotNotation = '' then
  534.             oid^.DotNotation := arcval
  535.           else
  536.             oid^.DotNotation := parentOID^.DotNotation + '.' + arcval;
  537.           NumIdEditor := true; (* request caller to save <oid> *)
  538.           Break;
  539.         end;
  540.       end;
  541.     end
  542.     else
  543.     begin
  544.       (* User pressed ESC *)
  545.       Break;
  546.     end;
  547.   until false;
  548.   CursorOff;
  549. end;
  550.  
  551. function NewOidEditor(oid: POID): boolean;
  552. var
  553.   newfilename: string;
  554.   newOID: POID;
  555. begin
  556.   NewOidEditor := false;
  557.  
  558.   CreateOidDef(newOID);
  559.   newOID^.FileId := NextPossibleFileID;
  560.   newOID^.ParentFileId := oid^.FileId;
  561.   newOID^.ParentDotNotation := oid^.DotNotation;
  562.   if NumIdEditor(newOID, oid) and
  563.      AsnEditor(newOID) and
  564.      IriEditor(newOID) and
  565.      DescEditor(newOID) then
  566.   begin
  567.     newfilename := newOID^.FileId + OID_EXTENSION;
  568.     if _WriteOidFile(newfilename, newOID, true) then
  569.     begin
  570.       (* Add link to original file and enable the saving of it *)
  571.       ListAppend(oid^.SubIds, newOID^.FileId + newOID^.DotNotation);
  572.       NewOidEditor := true; (* request caller to save <oid> *)
  573.     end;
  574.   end;
  575.   FreeOidDef(newOID);
  576. end;
  577.  
  578. procedure DeleteChildrenRecursive(oid: POID);
  579. var
  580.   i: integer;
  581.   childOID: POID;
  582.   filenameChild: string;
  583. begin
  584.   for i := 0 to ListCount(oid^.SubIds)-1 do
  585.   begin
  586.     filenameChild := FileIdPart(ListGetElement(oid^.SubIds, i)) + OID_EXTENSION;
  587.     if FileExists(filenameChild) then
  588.     begin
  589.       CreateOidDef(childOID);
  590.       if _ReadOidFile(filenameChild, childOID, false) and
  591.          (childOID^.ParentFileId = oid^.FileId) and
  592.          (childOID^.ParentDotNotation = oid^.DotNotation) then
  593.       begin
  594.         DeleteChildrenRecursive(childOID);
  595.       end;
  596.       FreeOidDef(childOID);
  597.       DeleteFile(filenameChild);
  598.     end;
  599.   end;
  600.   ListClear(oid^.SubIds);
  601. end;
  602.  
  603. procedure DeleteOidRecursive(selfOID: POID);
  604. var
  605.   i: integer;
  606.   parentOID: POID;
  607.   filenameSelf, filenameParent: string;
  608. begin
  609.   (* Remove all children and their files recursively *)
  610.   DeleteChildrenRecursive(selfOID);
  611.  
  612.   (* Remove forward reference in parent OID *)
  613.   (* (this is the most important part)      *)
  614.   filenameParent := selfOID^.ParentFileId + OID_EXTENSION;
  615.   if FileExists(filenameParent) then
  616.   begin
  617.     CreateOidDef(parentOID);
  618.     if _ReadOidFile(filenameParent, parentOID, true) then
  619.     begin
  620.       if ListDeleteElementByValue(parentOID^.SubIds, selfOID^.FileId + selfOID^.DotNotation) then
  621.       begin
  622.         _WriteOidFile(filenameParent, parentOID, true);
  623.       end;
  624.     end;
  625.     FreeOidDef(parentOID);
  626.   end;
  627.  
  628.   (* Delete own file *)
  629.   filenameSelf := selfOID^.FileId + OID_EXTENSION;
  630.   if FileExists(filenameSelf) then
  631.   begin
  632.     DeleteFile(filenameSelf);
  633.   end;
  634. end;
  635.  
  636. function _DeleteConfirmation: boolean;
  637. var
  638.   sc: Char;
  639. begin
  640.   repeat
  641.     ShowMessage('Are you sure you want to delete this OID? (Y/N)', 'DELETE OID', true);
  642.     DrawStatusBar('Y=Yes, N=No');
  643.  
  644.     CursorOn;
  645.     sc := ReadKey;
  646.     CursorOff;
  647.     if sc = #0 then
  648.     begin
  649.       (* Extended key. Nothing we care about. *)
  650.       ReadKey;
  651.       continue;
  652.     end;
  653.  
  654.     if UpCase(sc) = 'Y' then
  655.     begin
  656.       _DeleteConfirmation := true;
  657.       break;
  658.     end
  659.     else if UpCase(sc) = 'N' then
  660.     begin
  661.       _DeleteConfirmation := false;
  662.       break;
  663.     end;
  664.   until false;
  665. end;
  666.  
  667. procedure _DrawOidTitleBar(filename: string; oid: POID);
  668. begin
  669.   if oid^.DotNotation = '' then
  670.     DrawTitleBar('OID ROOT', TITLEBAR_LEFT_TEXT, filename)
  671.   else
  672.     DrawTitleBar('OID ' + oid^.DotNotation, TITLEBAR_LEFT_TEXT, filename);
  673. end;
  674.  
  675. function DotNotation(oid: POid): string;
  676. var
  677.   res: string;
  678. begin
  679.   res := oid^.DotNotation;
  680.   if res = '' then res := '.'; (* root *)
  681.   DotNotation := res;
  682. end;
  683.  
  684. function OidLastArc(oid: POid): string;
  685. var
  686.   s: string;
  687.   p: integer;
  688. begin
  689.   s := oid^.DotNotation;
  690.  
  691.   while true do
  692.   begin
  693.     p := Pos('.', s);
  694.     if p = 0 then break;
  695.     Delete(s, 1, p);
  696.   end;
  697.  
  698.   OidLastArc := s;
  699. end;
  700.  
  701. function AsnNotation(oid: POid): string;
  702. var
  703.   prevOid, curOid: POid;
  704.   res: string;
  705. begin
  706.   CreateOidDef(curOid);
  707.   prevOid := oid;
  708.   res := '';
  709.  
  710.   while true do
  711.   begin
  712.     (* Note: BackRef is not checked yet! Infinite loop is possible! (TODO) *)
  713.     ReadOidFile(prevOid^.ParentFileId + '.OID', curOid);
  714.     if curOid^.ParentFileId = '' then break;
  715.     if curOid^.ParentFileId = curOid^.FileId then break;
  716.     if ListCount(curOid^.AsnIds) > 0 then
  717.       res := ListGetElement(curOid^.AsnIds, 0) + '('+OidLastArc(curOid)+') ' + res
  718.     else
  719.       res := OidLastArc(curOid) + ' ' + res;
  720.     prevOid := curOid;
  721.   end;
  722.   FreeOidDef(curOid);
  723.   if ListCount(oid^.AsnIds) > 0 then
  724.     res := res + ListGetElement(oid^.AsnIds, 0) + '('+OidLastArc(oid)+')'
  725.   else
  726.     res := res + OidLastArc(oid);
  727.   if res = '' then
  728.     AsnNotation := ''
  729.   else
  730.     AsnNotation := '{ ' + res + ' }';
  731. end;
  732.  
  733. function IriNotation(oid: POid): string;
  734. var
  735.   prevOid, curOid: POid;
  736.   res: string;
  737. begin
  738.   CreateOidDef(curOid);
  739.   prevOid := oid;
  740.   res := '';
  741.  
  742.   while true do
  743.   begin
  744.     (* Note: BackRef is not checked yet! Infinite loop is possible! (TODO) *)
  745.     ReadOidFile(prevOid^.ParentFileId + '.OID', curOid);
  746.     if curOid^.ParentFileId = '' then break;
  747.     if curOid^.ParentFileId = curOid^.FileId then break;
  748.     if ListCount(curOid^.UnicodeLabels) > 0 then
  749.       res := ListGetElement(curOid^.UnicodeLabels, 0) + '/' + res
  750.     else
  751.       res := OidLastArc(curOid) + '/' + res;
  752.     prevOid := curOid;
  753.   end;
  754.   FreeOidDef(curOid);
  755.   if ListCount(oid^.UnicodeLabels) > 0 then
  756.     res := res + ListGetElement(oid^.UnicodeLabels, 0)
  757.   else
  758.     res := res + OidLastArc(oid);
  759.   IriNotation := '/' + res;
  760. end;
  761.  
  762. function WeidNotation(oid: POid): string;
  763. begin
  764.   WeidNotation := OidToWeid(oid^.DotNotation);
  765. end;
  766.  
  767. procedure DisplayOIDFile(filename, from: string);
  768. var
  769.   isRoot: boolean;
  770.   oid, tmpOID: POID;
  771.   i: integer;
  772.   sTmp, subfile: string;
  773.   subsel, subfiles: PStringList;
  774.   subselres: integer;
  775.   exitRequest: boolean;
  776.   menuIdExit, menuIdAsnEdit, menuIdIriEdit, menuIdDescEdit, menuIdAdd, menuIdDelete: integer;
  777.   menuX, menuY: integer;
  778. begin
  779.   exitRequest := false;
  780.   repeat
  781.     CreateOidDef(oid);
  782.  
  783.     if not _ReadOidFile(filename, oid, true) then
  784.     begin
  785.       FreeOidDef(oid);
  786.       exit;
  787.     end;
  788.  
  789.     (* Print OID information *)
  790.  
  791.     ClrScr;
  792.     _DrawOidTitleBar(filename, oid);
  793.     DrawStatusBar('');
  794.     GotoXY(1,2);
  795.  
  796.     Write('Dot notation:   ');
  797.     WriteLnKeepX(DotNotation(oid));
  798.     Write('ASN.1 notation: ');
  799.     WriteLnKeepX(AsnNotation(oid));
  800.     Write('OID-IRI:        ');
  801.     WriteLnKeepX(IriNotation(oid));
  802.     Write('WEID notation:  ');
  803.     WriteLnKeepX(WeidNotation(oid));
  804.     WriteLn('');
  805.  
  806.     if Trim(oid^.Description) <> '' then
  807.     begin
  808.       (* WriteLn('Description:'); *)
  809.       (* TODO: We should avoid that this is more than 10 lines *)
  810.       WriteLn(oid^.Description);
  811.       WriteLn('');
  812.     end
  813.     else
  814.     begin
  815.       WriteLn('(No description has been added to this OID.)');
  816.       WriteLn('');
  817.     end;
  818.  
  819.     (* Now prepare the menu entries *)
  820.  
  821.     CreateList(subsel);   (* Contains the human-readable OID name *)
  822.     CreateList(subfiles); (* Contains the file name               *)
  823.  
  824.     if oid^.ParentFileId = '' then
  825.     begin
  826.       isRoot := true;
  827.     end
  828.     else
  829.     begin
  830.       isRoot := oid^.ParentDotNotation = oid^.DotNotation;
  831.     end;
  832.  
  833.     if (oid^.ParentFileId <> '') and not isRoot then
  834.     begin
  835.       subfile := oid^.ParentFileId + OID_EXTENSION;
  836.       if FileExists(subfile) then
  837.       begin
  838.         CreateOidDef(tmpOID);
  839.         if not _ReadOidFile(subfile, tmpOID, true) then
  840.         begin
  841.           ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + ' (READ ERROR)');
  842.           ListAppend(subfiles, 'ERROR: '+subfile+' Read error or file invalid');
  843.         end
  844.         else
  845.         begin
  846.           ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + _ShowASNIds(tmpOID));
  847.           ListAppend(subfiles, subfile);
  848.         end;
  849.         FreeOidDef(tmpOID);
  850.       end
  851.       else
  852.       begin
  853.         ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + ' (FILE NOT FOUND)');
  854.         ListAppend(subfiles, 'ERROR: File '+subfile+' was not found');
  855.       end;
  856.     end;
  857.  
  858.     if isRoot then
  859.     begin
  860.       menuIdExit := ListAppend(subsel, 'Back to '+from);
  861.       ListAppend(subfiles, '');
  862.     end
  863.     else menuIdExit := -99;
  864.  
  865.     for i := 0 to ListCount(oid^.SubIds)-1 do
  866.     begin
  867.       sTmp := ListGetElement(oid^.SubIds, i);
  868.       subfile := FileIdPart(sTmp) + OID_EXTENSION;
  869.       if FileExists(subfile) then
  870.       begin
  871.         CreateOidDef(tmpOID);
  872.         if not _ReadOidFile(subfile, tmpOID, true) then
  873.         begin
  874.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (READ ERROR)');
  875.           ListAppend(subfiles, 'ERROR: Read error at file '+subfile+', or file is invalid.');
  876.         end
  877.         else if (tmpOID^.ParentFileId <> oid^.FileId) or
  878.                 (tmpOID^.ParentDotNotation <> oid^.DotNotation) then
  879.         begin
  880.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (BAD BACKREF)');
  881.           ListAppend(subfiles, 'ERROR: File '+subfile+' has a wrong back-reference.');
  882.         end
  883.         else
  884.         begin
  885.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + _ShowASNIds(tmpOID));
  886.           ListAppend(subfiles, subfile);
  887.         end;
  888.         FreeOidDef(tmpOID);
  889.       end
  890.       else
  891.       begin
  892.         ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (FILE NOT FOUND)');
  893.         ListAppend(subfiles, 'ERROR: File '+subfile+' was not found');
  894.       end;
  895.     end;
  896.  
  897.     if oid^.DotNotation <> '' then
  898.     begin
  899.       menuIdAsnEdit := ListAppend(subsel, 'View/Edit ASN.1 identifiers');
  900.       ListAppend(subfiles, '');
  901.     end
  902.     else menuIdAsnEdit := -99;
  903.  
  904.     if oid^.DotNotation <> '' then
  905.     begin
  906.       menuIdIriEdit := ListAppend(subsel, 'View/Edit Unicode Labels (OID-IRI)');
  907.       ListAppend(subfiles, '');
  908.     end
  909.     else menuIdIriEdit := -99;
  910.  
  911.     menuIdDescEdit := ListAppend(subsel, 'Edit description');
  912.     ListAppend(subfiles, '');
  913.  
  914.     menuIdAdd := ListAppend(subsel, 'Add child');
  915.     ListAppend(subfiles, '');
  916.  
  917.     if not isRoot then
  918.     begin
  919.       menuIdDelete := ListAppend(subsel, 'Delete OID');
  920.       ListAppend(subfiles, '');
  921.     end
  922.     else menuIdDelete := -99;
  923.  
  924.     (* Show menu *)
  925.  
  926.     menuX := WhereX + 1;
  927.     menuY := ScreenHeight - ACTIONMENU_SIZE - 1;
  928.     subselres := DrawSelectionList(menuX, menuY,
  929.                                    ScreenWidth-2,
  930.                                    ACTIONMENU_SIZE,
  931.                                    subsel,
  932.                                    true,
  933.                                    'SELECT ACTION',
  934.                                    1);
  935.  
  936.     (* Process user selection *)
  937.  
  938.     if subselres = -1 then
  939.     begin
  940.       exitRequest := true;
  941.     end
  942.     else if subselres = menuIdAsnEdit then
  943.     begin
  944.       if AsnEditor(oid) then
  945.         _WriteOidFile(filename, oid, true);
  946.     end
  947.     else if subselres = menuIdIriEdit then
  948.     begin
  949.       if IriEditor(oid) then
  950.         _WriteOidFile(filename, oid, true);
  951.     end
  952.     else if subselres = menuIdDescEdit then
  953.     begin
  954.       if DescEditor(oid) then
  955.         _WriteOidFile(filename, oid, true);
  956.     end
  957.     else if subselres = menuIdAdd then
  958.     begin
  959.       if NewOidEditor(oid) then
  960.         _WriteOidFile(filename, oid, true);
  961.     end
  962.     else if subselres = menuIdDelete then
  963.     begin
  964.       if _DeleteConfirmation then
  965.       begin
  966.         sTmp := oid^.ParentFileId + OID_EXTENSION;
  967.         DeleteOidRecursive(oid);
  968.         if FileExists(sTmp) then
  969.         begin
  970.           filename := sTmp;
  971.         end
  972.         else
  973.         begin
  974.           ShowMessage('Parent file ' + sTmp + ' was not found', 'ERROR', true);
  975.           _Pause;
  976.           exitRequest := true;
  977.         end;
  978.       end;
  979.     end
  980.     else if subselres = menuIdExit then
  981.     begin
  982.       exitRequest := true;
  983.     end
  984.     else
  985.     begin
  986.       (* Normal OID *)
  987.       (* Above we already checked if the files are valild and existing *)
  988.       sTmp := ListGetElement(subfiles, subselres);
  989.       if Copy(sTmp, 1, Length('ERROR: ')) = 'ERROR: ' then
  990.       begin
  991.         Delete(sTmp, 1, Length('ERROR: '));
  992.         ShowMessage(sTmp, 'ERROR', true);
  993.         _Pause;
  994.       end
  995.       else
  996.       begin
  997.         filename := sTmp;
  998.       end;
  999.     end;
  1000.     FreeList(subsel);
  1001.     FreeList(subfiles);
  1002.  
  1003.     FreeOidDef(oid);
  1004.   until exitRequest;
  1005. end;
  1006.  
  1007. function CreateRootOIDFile(filename: string; ShowErrorMessage: boolean): boolean;
  1008. var
  1009.   oid: POID;
  1010. begin
  1011.   CreateOidDef(oid);
  1012.   oid^.Description  := 'This is the root of the OID tree.' +#13#10 +
  1013.                        #13#10 +
  1014.                        'Valid subsequent arcs are per definition:' + #13#10 +
  1015.                        '- 0 (itu-t)' + #13#10 +
  1016.                        '- 1 (iso)' + #13#10 +
  1017.                        '- 2 (joint-iso-itu-t)';
  1018.   oid^.FileId       := ZeroPad(0, 8);
  1019.   oid^.DotNotation  := '';
  1020.   oid^.ParentFileId := ZeroPad(0, 8);
  1021.   oid^.ParentDotNotation := '';
  1022.   CreateRootOIDFile := _WriteOidFile(filename, oid, ShowErrorMessage);
  1023.   FreeOidDef(oid);
  1024. end;
  1025.  
  1026. function _GetRootFile(ShowErrorMessage: boolean): string;
  1027. var
  1028.   rootFile: string;
  1029. begin
  1030.   rootFile := ZeroPad(0, 8) + OID_EXTENSION;
  1031.   _GetRootFile := rootFile;
  1032.   if not FileExists(rootFile) then
  1033.   begin
  1034.     if not CreateRootOIDFile(rootFile, ShowErrorMessage) then
  1035.     begin
  1036.       _GetRootFile := '';
  1037.     end;
  1038.   end;
  1039. end;
  1040.  
  1041. procedure OP_ManageOIDs;
  1042. var
  1043.   rootfile: string;
  1044. begin
  1045.   ClrScr;
  1046.   DrawTitleBar('Manage Object Identifiers', TITLEBAR_LEFT_TEXT, '');
  1047.   DrawStatusBar('Loading data... please wait...');
  1048.  
  1049.   (* This will try creating a new root file if it does not exist *)
  1050.   rootfile := _GetRootFile(true);
  1051.   if rootfile = '' then Exit;
  1052.  
  1053.   DisplayOIDFile(rootfile, 'main menu');
  1054. end;
  1055.  
  1056. procedure OP_ReturnToMSDOS;
  1057. begin
  1058.   (* Note: These two lines don't seem to be necessary if you use DoneVideo *)
  1059.   ResetDefaultDosColors;
  1060.   ClrScr; (*Important, so that the DOS command prompt is also LightGray *)
  1061.  
  1062.   WriteLn('Thank you for using OIDplus for DOS.');
  1063.   WriteLn('');
  1064. end;
  1065.  
  1066. function _GetTreeViewLine(oid: POID; indent: integer): string;
  1067. var
  1068.   i: integer;
  1069.   sTmp, sTmp2: string;
  1070. begin
  1071.   (* Build line *)
  1072.   sTmp := RepeatStr(' ', indent*TREEVIEW_INDENT);
  1073.   if oid^.DotNotation = '' then
  1074.     sTmp := sTmp + 'Object Identifiers'
  1075.   else
  1076.     sTmp := sTmp + oid^.DotNotation;
  1077.   sTmp := sTmp + _ShowAsnIds(oid);
  1078.   if TREEVIEW_INCLUDE_DESC then
  1079.   begin
  1080.     if Trim(oid^.Description) <> '' then
  1081.     begin
  1082.       sTmp := sTmp + ': ' + oid^.Description;
  1083.     end;
  1084.   end;
  1085.  
  1086.   sTmp := StringReplace(sTmp, #13#10, ' ');
  1087.   repeat
  1088.     sTmp2 := sTmp;
  1089.     sTmp := StringReplace(sTmp, '  ', ' ');
  1090.   until sTmp = sTmp2;
  1091.  
  1092.   sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1093.   _GetTreeViewLine := sTmp;
  1094. end;
  1095.  
  1096. procedure _RecTreeExport(oid: POID; visList, targetList: PStringList; indent: integer);
  1097. var
  1098.   i: integer;
  1099.   sTmp: string;
  1100.   suboid: POID;
  1101.   childFilename: string;
  1102. begin
  1103.   sTmp := _GetTreeViewLine(oid, indent);
  1104.   sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1105.   ListAppend(visList, sTmp);
  1106.   ListAppend(targetList, oid^.FileID);
  1107.  
  1108.   (* Recursively call children *)
  1109.   for i := 0 to ListCount(oid^.SubIds)-1 do
  1110.   begin
  1111.     sTmp := ListGetElement(oid^.SubIds, i);
  1112.     CreateOidDef(suboid);
  1113.     childFilename := FileIdPart(sTmp) + OID_EXTENSION;
  1114.     if not FileExists(childFilename) then
  1115.     begin
  1116.       sTmp := 'ERROR: MISSING ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1117.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1118.       ListAppend(visList, sTmp);
  1119.       ListAppend(targetList, 'ERROR');
  1120.     end
  1121.     else if not _ReadOidFile(childFilename, suboid, false) then
  1122.     begin
  1123.       sTmp := 'ERROR: READ ERROR AT ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1124.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1125.       ListAppend(visList, sTmp);
  1126.       ListAppend(targetList, 'ERROR');
  1127.     end
  1128.     else if (suboid^.ParentFileId <> oid^.FileId) or
  1129.             (suboid^.ParentDotNotation <> oid^.DotNotation) then
  1130.     begin
  1131.       (* This can happen if a file is missing, and then another OID gets this filename since the number seems to be free *)
  1132.       sTmp := 'ERROR: BAD BACKREF AT ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1133.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1134.       ListAppend(visList, sTmp);
  1135.       ListAppend(targetList, 'ERROR');
  1136.     end
  1137.     else
  1138.     begin
  1139.       _RecTreeExport(suboid, visList, targetList, indent+1);
  1140.       FreeOidDef(suboid);
  1141.     end
  1142.   end;
  1143. end;
  1144.  
  1145. procedure TreeViewPreview(visList, targetList: PStringList);
  1146. var
  1147.   res: integer;
  1148.   sTmp: string;
  1149. begin
  1150.   ClrScr;
  1151.   DrawTitleBar('TreeView Export', TITLEBAR_LEFT_TEXT, TREEVIEW_FILENAME);
  1152.   DrawStatusBar('Press ESC to return to the main menu. Enter to jump to OID.');
  1153.  
  1154.   while true do
  1155.   begin
  1156.     res := DrawSelectionList(2, 3, ScreenWidth-2, ScreenHeight-4,
  1157.                              visList, true, 'PREVIEW OF '+TREEVIEW_FILENAME, 2);
  1158.     if res > -1 then
  1159.     begin
  1160.       (* Jump to selected OID or show error *)
  1161.       sTmp := ListGetElement(targetList, res);
  1162.       if sTmp = 'ERROR' then
  1163.       begin
  1164.         ShowMessage(ListGetElement(visList, res), 'ERROR', true);
  1165.         _Pause;
  1166.       end
  1167.       else
  1168.       begin
  1169.         DisplayOidFile(sTmp + '.OID', 'TreeView Export');
  1170.       end;
  1171.     end
  1172.     else
  1173.     begin
  1174.       break;
  1175.     end;
  1176.   end;
  1177.  
  1178.   DrawStatusBar('');
  1179. end;
  1180.  
  1181. procedure OP_TreeView;
  1182. var
  1183.   F: Text;
  1184.   rootoid: POID;
  1185.   rootfile: string;
  1186.   res: boolean;
  1187.   visList, targetList: PStringList;
  1188. begin
  1189.   ClrScr;
  1190.   DrawTitleBar('TreeView Export', TITLEBAR_LEFT_TEXT, '');
  1191.   DrawStatusBar('Exporting data... please wait...');
  1192.  
  1193.   (* This will try creating a new root file if it does not exist *)
  1194.   rootfile := _GetRootFile(true);
  1195.   if rootfile = '' then
  1196.   begin
  1197.     DrawStatusBar('');
  1198.     Exit;
  1199.   end;
  1200.  
  1201.   CreateList(visList);
  1202.   CreateList(targetList);
  1203.  
  1204.   (* First check if the disk is read-only *)
  1205.   Assign(F, TREEVIEW_FILENAME);
  1206.   {$I-}
  1207.   Rewrite(F);
  1208.   {$I+}
  1209.   if IoResult <> 0 then
  1210.   begin
  1211.     (* Can happen if disk is read-only (Runtime Error 150) *)
  1212.     ShowMessage('Cannot open '+TREEVIEW_FILENAME+' for writing.', 'ERROR', true);
  1213.     _Pause;
  1214.     DrawStatusBar('');
  1215.     Exit;
  1216.   end;
  1217.   Close(F);
  1218.  
  1219.   (* Now do the export *)
  1220.   res := false;
  1221.   CreateOidDef(rootoid);
  1222.   if _ReadOidFile(rootfile, rootoid, true) then
  1223.   begin
  1224.     _RecTreeExport(rootoid, visList, targetList, 0);
  1225.     res := true;
  1226.   end;
  1227.   FreeOidDef(rootoid);
  1228.  
  1229.   (* Save the list (visual part only) *)
  1230.   ListSaveToFile(visList, TREEVIEW_FILENAME);
  1231.  
  1232.   DrawStatusBar('');
  1233.   if res then
  1234.   begin
  1235.     ShowMessage('TreeView successfully exported as '+TREEVIEW_FILENAME, 'TREEVIEW EXPORT', true);
  1236.     _Pause;
  1237.   end;
  1238.  
  1239.   TreeViewPreview(visList, targetList);
  1240.  
  1241.   FreeList(visList);
  1242.   FreeList(targetList);
  1243. end;
  1244.  
  1245. procedure OP_MainMenu;
  1246. var
  1247.   menu: PStringList;
  1248.   menuRes, menuLeft, menuTop: integer;
  1249.   menuIdOID, menuIdTree, menuIdExit: integer;
  1250. begin
  1251.   repeat
  1252.     ClrScr;
  1253.  
  1254.     DrawTitleBar('Welcome to OIDplus for DOS', '', '');
  1255.     DrawStatusBar('(C)2020-2022 ViaThinkSoft. Licensed under the terms of the Apache 2.0 license.');
  1256.     GoToXY(ScreenWidth-Length(VERSIONINFO), ScreenHeight-1);
  1257.     Write(VERSIONINFO);
  1258.  
  1259.     CreateList(menu);
  1260.  
  1261.     menuIdOID  := ListAppend(menu, 'Manage OIDs');
  1262.     menuIdTree := ListAppend(menu, 'Export TreeView');
  1263.     menuIdExit := ListAppend(menu, 'Return to DOS');
  1264.  
  1265.     menuLeft := round(ScreenWidth/2 -MAINMENU_WIDTH/2);
  1266.     menuTop  := round(ScreenHeight/2-MAINMENU_HEIGHT/2);
  1267.     menuRes  := DrawSelectionList(menuLeft, menuTop,
  1268.                                   MAINMENU_WIDTH, MAINMENU_HEIGHT,
  1269.                                   menu, true, 'MAIN MENU', 2);
  1270.     FreeList(menu);
  1271.  
  1272.     if menuRes = menuIdOID then
  1273.     begin
  1274.       OP_ManageOIDs;
  1275.     end
  1276.     else if menuRes = menuIdTree then
  1277.     begin
  1278.       OP_Treeview;
  1279.     end;
  1280.   until (menuRes = menuIdExit) or (MAINMENU_ALLOW_ESC and (menuRes = -1));
  1281.  
  1282.   OP_ReturnToMSDOS;
  1283. end;
  1284.  
  1285. begin
  1286.   InitVideo; (* sets ScreenWidth and ScreenHeight *)
  1287.   CursorOff;
  1288.   OP_MainMenu;
  1289.   CursorOn;
  1290.   DoneVideo;
  1291. end.
  1292.