Subversion Repositories oidplus

Rev

Rev 965 | Go to most recent revision | 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-10                         *)
  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.   list: PStringList;
  421.   iId: LongInt;
  422.   sId: string;
  423. begin
  424.   (* Put all found files into a list *)
  425.   CreateList(list);
  426.   FindFirst(RepeatStr('?',8)+OID_EXTENSION, Archive, DirInfo);
  427.   while DosError = 0 do
  428.   begin
  429.     sId := Copy(DirInfo.Name, 1, 8);
  430.     ListAppend(list, sId);
  431.     FindNext(DirInfo);
  432.   end;
  433.  
  434.   (* Search for the first non existing item in the list *)
  435.   sId := '';
  436.   for iId := 0 to 99999999 do
  437.   begin
  438.     sId := ZeroPad(iId, 8);
  439.     if not ListContains(list, sId) then break;
  440.   end;
  441.   NextPossibleFileId := sId;
  442.   FreeList(list);
  443. end;
  444.  
  445. function NumIdAlreadyExisting(parentOID: POID; arcval: string): boolean;
  446. var
  447.   searchDotNotation: string;
  448.   sTmp: string;
  449.   i: integer;
  450. begin
  451.   if parentOID^.DotNotation = '' then
  452.     searchDotNotation := arcval
  453.   else
  454.     searchDotNotation := parentOID^.DotNotation + '.' + arcval;
  455.   for i := 0 to ListCount(parentOID^.SubIds)-1 do
  456.   begin
  457.     sTmp := ListGetElement(parentOID^.SubIds, i);
  458.     if DotNotationPart(sTmp) = searchDotNotation then
  459.     begin
  460.       NumIdAlreadyExisting := true;
  461.       exit;
  462.     end;
  463.   end;
  464.   NumIdAlreadyExisting := false;
  465. end;
  466.  
  467. function NumIdEditor(oid: POID; parentOID: POID): boolean;
  468. var
  469.   sInput: string;
  470.   title: string;
  471.   base36mode: boolean;
  472.   arcval: string;
  473. begin
  474.   NumIdEditor := false;
  475.   sInput := '';
  476.  
  477.   base36mode := false;
  478.   CursorOn;
  479.   repeat
  480.     if base36mode then
  481.     begin
  482.       DrawStatusBar('Press ESC to cancel');
  483.       title := 'ENTER BASE36 ID'
  484.     end
  485.     else
  486.     begin
  487.       DrawStatusBar('Enter "WEID" to enter a Base36 instead of Base10; press ESC to cancel');
  488.       title := 'ENTER NUMERIC ID';
  489.     end;
  490.     if QueryVal(sInput,
  491.                 SINGLE_LINE_BOX_PADDING_INNER,
  492.                 ScreenHeight div 2,
  493.                 ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  494.                 1,
  495.                 title,
  496.                 2) then
  497.     begin
  498.       if sInput = '' then continue;
  499.       if not base36mode and (sInput = 'WEID') then
  500.       begin
  501.         sInput := '';
  502.         base36mode := true;
  503.       end
  504.       else if not base36mode and not IsPositiveIntegerOrZero(sInput) then
  505.       begin
  506.         ShowMessage('Invalid numeric ID (must be a positive integer)', 'ERROR', true);
  507.         _Pause;
  508.       end
  509.       else if base36mode and not IsBase36String(sInput) then
  510.       begin
  511.         ShowMessage('Invalid base36 ID (must be 0..9, A..Z)', 'ERROR', true);
  512.         _Pause;
  513.       end
  514.       else
  515.       begin
  516.         if base36mode then
  517.           arcval := base_convert_bigint(sInput, 36, 10)
  518.         else
  519.           arcval := sInput;
  520.         arcval := StripLeadingZeros(arcval);
  521.  
  522.         if (parentOID^.DotNotation='') and (arcval <> '0') and (arcval <> '1') and (arcval <> '2') then
  523.         begin
  524.           ShowMessage('Invalid numeric ID (root arc can only be 0, 1, or 2)', 'ERROR', true);
  525.           _Pause;
  526.         end
  527.         else if ((parentOID^.DotNotation='0') or (parentOID^.DotNotation='1')) and
  528.                 ((Length(arcval)>2) or (StrToInt(arcval) > 39)) then
  529.         begin
  530.           ShowMessage('Invalid numeric ID (root 0 and 1 must have sub-arc of 0..39)', 'ERROR', true);
  531.           _Pause;
  532.         end
  533.         else if NumIdAlreadyExisting(parentOID, arcval) then
  534.         begin
  535.           ShowMessage('This numeric ID is already used in this arc', 'ERROR', true);
  536.           _Pause;
  537.         end
  538.         else
  539.         begin
  540.           if parentOID^.DotNotation = '' then
  541.             oid^.DotNotation := arcval
  542.           else
  543.             oid^.DotNotation := parentOID^.DotNotation + '.' + arcval;
  544.           NumIdEditor := true; (* request caller to save <oid> *)
  545.           Break;
  546.         end;
  547.       end;
  548.     end
  549.     else
  550.     begin
  551.       (* User pressed ESC *)
  552.       Break;
  553.     end;
  554.   until false;
  555.   CursorOff;
  556. end;
  557.  
  558. function NewOidEditor(oid: POID): boolean;
  559. var
  560.   newfilename: string;
  561.   newOID: POID;
  562. begin
  563.   NewOidEditor := false;
  564.  
  565.   CreateOidDef(newOID);
  566.   newOID^.FileId := NextPossibleFileID;
  567.   newOID^.ParentFileId := oid^.FileId;
  568.   newOID^.ParentDotNotation := oid^.DotNotation;
  569.   if NumIdEditor(newOID, oid) and
  570.      AsnEditor(newOID) and
  571.      IriEditor(newOID) and
  572.      DescEditor(newOID) then
  573.   begin
  574.     newfilename := newOID^.FileId + OID_EXTENSION;
  575.     if _WriteOidFile(newfilename, newOID, true) then
  576.     begin
  577.       (* Add link to original file and enable the saving of it *)
  578.       ListAppend(oid^.SubIds, newOID^.FileId + newOID^.DotNotation);
  579.       NewOidEditor := true; (* request caller to save <oid> *)
  580.     end;
  581.   end;
  582.   FreeOidDef(newOID);
  583. end;
  584.  
  585. procedure DeleteChildrenRecursive(oid: POID);
  586. var
  587.   i: integer;
  588.   childOID: POID;
  589.   filenameChild: string;
  590. begin
  591.   for i := 0 to ListCount(oid^.SubIds)-1 do
  592.   begin
  593.     filenameChild := FileIdPart(ListGetElement(oid^.SubIds, i)) + OID_EXTENSION;
  594.     if FileExists(filenameChild) then
  595.     begin
  596.       CreateOidDef(childOID);
  597.       if _ReadOidFile(filenameChild, childOID, false) and
  598.          (childOID^.ParentFileId = oid^.FileId) and
  599.          (childOID^.ParentDotNotation = oid^.DotNotation) then
  600.       begin
  601.         DeleteChildrenRecursive(childOID);
  602.       end;
  603.       FreeOidDef(childOID);
  604.       DeleteFile(filenameChild);
  605.     end;
  606.   end;
  607.   ListClear(oid^.SubIds);
  608. end;
  609.  
  610. procedure DeleteOidRecursive(selfOID: POID);
  611. var
  612.   i: integer;
  613.   parentOID: POID;
  614.   filenameSelf, filenameParent: string;
  615. begin
  616.   (* Remove all children and their files recursively *)
  617.   DeleteChildrenRecursive(selfOID);
  618.  
  619.   (* Remove forward reference in parent OID *)
  620.   (* (this is the most important part)      *)
  621.   filenameParent := selfOID^.ParentFileId + OID_EXTENSION;
  622.   if FileExists(filenameParent) then
  623.   begin
  624.     CreateOidDef(parentOID);
  625.     if _ReadOidFile(filenameParent, parentOID, true) then
  626.     begin
  627.       if ListDeleteElementByValue(parentOID^.SubIds, selfOID^.FileId + selfOID^.DotNotation) then
  628.       begin
  629.         _WriteOidFile(filenameParent, parentOID, true);
  630.       end;
  631.     end;
  632.     FreeOidDef(parentOID);
  633.   end;
  634.  
  635.   (* Delete own file *)
  636.   filenameSelf := selfOID^.FileId + OID_EXTENSION;
  637.   if FileExists(filenameSelf) then
  638.   begin
  639.     DeleteFile(filenameSelf);
  640.   end;
  641. end;
  642.  
  643. function _DeleteConfirmation: boolean;
  644. var
  645.   sc: Char;
  646. begin
  647.   repeat
  648.     ShowMessage('Are you sure you want to delete this OID? (Y/N)', 'DELETE OID', true);
  649.     DrawStatusBar('Y=Yes, N=No');
  650.  
  651.     CursorOn;
  652.     sc := ReadKey;
  653.     CursorOff;
  654.     if sc = #0 then
  655.     begin
  656.       (* Extended key. Nothing we care about. *)
  657.       ReadKey;
  658.       continue;
  659.     end;
  660.  
  661.     if UpCase(sc) = 'Y' then
  662.     begin
  663.       _DeleteConfirmation := true;
  664.       break;
  665.     end
  666.     else if UpCase(sc) = 'N' then
  667.     begin
  668.       _DeleteConfirmation := false;
  669.       break;
  670.     end;
  671.   until false;
  672. end;
  673.  
  674. procedure _DrawOidTitleBar(filename: string; oid: POID);
  675. begin
  676.   if oid^.DotNotation = '' then
  677.     DrawTitleBar('OID ROOT', TITLEBAR_LEFT_TEXT, filename)
  678.   else
  679.     DrawTitleBar('OID ' + oid^.DotNotation, TITLEBAR_LEFT_TEXT, filename);
  680. end;
  681.  
  682. function DotNotation(oid: POid): string;
  683. var
  684.   res: string;
  685. begin
  686.   res := oid^.DotNotation;
  687.   if res = '' then res := '.'; (* root *)
  688.   DotNotation := res;
  689. end;
  690.  
  691. function OidLastArc(oid: POid): string;
  692. var
  693.   s: string;
  694.   p: integer;
  695. begin
  696.   s := oid^.DotNotation;
  697.  
  698.   while true do
  699.   begin
  700.     p := Pos('.', s);
  701.     if p = 0 then break;
  702.     Delete(s, 1, p);
  703.   end;
  704.  
  705.   OidLastArc := s;
  706. end;
  707.  
  708. function AsnNotation(oid: POid): string;
  709. var
  710.   prevOid, curOid: POid;
  711.   res: string;
  712. begin
  713.   CreateOidDef(curOid);
  714.   prevOid := oid;
  715.   res := '';
  716.  
  717.   while true do
  718.   begin
  719.     (* Note: BackRef is not checked yet! Infinite loop is possible! (TODO) *)
  720.     ReadOidFile(prevOid^.ParentFileId + '.OID', curOid);
  721.     if curOid^.ParentFileId = '' then break;
  722.     if curOid^.ParentFileId = curOid^.FileId then break;
  723.     if ListCount(curOid^.AsnIds) > 0 then
  724.       res := ListGetElement(curOid^.AsnIds, 0) + '('+OidLastArc(curOid)+') ' + res
  725.     else
  726.       res := OidLastArc(curOid) + ' ' + res;
  727.     prevOid := curOid;
  728.   end;
  729.   FreeOidDef(curOid);
  730.   if ListCount(oid^.AsnIds) > 0 then
  731.     res := res + ListGetElement(oid^.AsnIds, 0) + '('+OidLastArc(oid)+')'
  732.   else
  733.     res := res + OidLastArc(oid);
  734.   if res = '' then
  735.     AsnNotation := ''
  736.   else
  737.     AsnNotation := '{ ' + res + ' }';
  738. end;
  739.  
  740. function IriNotation(oid: POid): string;
  741. var
  742.   prevOid, curOid: POid;
  743.   res: string;
  744. begin
  745.   CreateOidDef(curOid);
  746.   prevOid := oid;
  747.   res := '';
  748.  
  749.   while true do
  750.   begin
  751.     (* Note: BackRef is not checked yet! Infinite loop is possible! (TODO) *)
  752.     ReadOidFile(prevOid^.ParentFileId + '.OID', curOid);
  753.     if curOid^.ParentFileId = '' then break;
  754.     if curOid^.ParentFileId = curOid^.FileId then break;
  755.     if ListCount(curOid^.UnicodeLabels) > 0 then
  756.       res := ListGetElement(curOid^.UnicodeLabels, 0) + '/' + res
  757.     else
  758.       res := OidLastArc(curOid) + '/' + res;
  759.     prevOid := curOid;
  760.   end;
  761.   FreeOidDef(curOid);
  762.   if ListCount(oid^.UnicodeLabels) > 0 then
  763.     res := res + ListGetElement(oid^.UnicodeLabels, 0)
  764.   else
  765.     res := res + OidLastArc(oid);
  766.   IriNotation := '/' + res;
  767. end;
  768.  
  769. function WeidNotation(oid: POid): string;
  770. begin
  771.   WeidNotation := OidToWeid(oid^.DotNotation);
  772. end;
  773.  
  774. procedure DisplayOIDFile(filename, from: string);
  775. var
  776.   isRoot: boolean;
  777.   oid, tmpOID: POID;
  778.   i: integer;
  779.   sTmp, subfile: string;
  780.   subsel, subfiles: PStringList;
  781.   subselres: integer;
  782.   exitRequest: boolean;
  783.   menuIdExit, menuIdAsnEdit, menuIdIriEdit, menuIdDescEdit, menuIdAdd, menuIdDelete: integer;
  784.   menuX, menuY: integer;
  785. begin
  786.   exitRequest := false;
  787.   repeat
  788.     CreateOidDef(oid);
  789.  
  790.     if not _ReadOidFile(filename, oid, true) then
  791.     begin
  792.       FreeOidDef(oid);
  793.       exit;
  794.     end;
  795.  
  796.     (* Print OID information *)
  797.  
  798.     ClrScr;
  799.     _DrawOidTitleBar(filename, oid);
  800.     DrawStatusBar('');
  801.     GotoXY(1,2);
  802.  
  803.     Write('Dot notation:   ');
  804.     WriteLnKeepX(DotNotation(oid));
  805.     Write('ASN.1 notation: ');
  806.     WriteLnKeepX(AsnNotation(oid));
  807.     Write('OID-IRI:        ');
  808.     WriteLnKeepX(IriNotation(oid));
  809.     Write('WEID notation:  ');
  810.     WriteLnKeepX(WeidNotation(oid));
  811.     WriteLn('');
  812.  
  813.     if Trim(oid^.Description) <> '' then
  814.     begin
  815.       (* WriteLn('Description:'); *)
  816.       (* TODO: We should avoid that this is more than 10 lines *)
  817.       WriteLn(oid^.Description);
  818.       WriteLn('');
  819.     end
  820.     else
  821.     begin
  822.       WriteLn('(No description has been added to this OID.)');
  823.       WriteLn('');
  824.     end;
  825.  
  826.     (* Now prepare the menu entries *)
  827.  
  828.     CreateList(subsel);   (* Contains the human-readable OID name *)
  829.     CreateList(subfiles); (* Contains the file name               *)
  830.  
  831.     if oid^.ParentFileId = '' then
  832.     begin
  833.       isRoot := true;
  834.     end
  835.     else
  836.     begin
  837.       isRoot := oid^.ParentDotNotation = oid^.DotNotation;
  838.     end;
  839.  
  840.     if (oid^.ParentFileId <> '') and not isRoot then
  841.     begin
  842.       subfile := oid^.ParentFileId + OID_EXTENSION;
  843.       if FileExists(subfile) then
  844.       begin
  845.         CreateOidDef(tmpOID);
  846.         if not _ReadOidFile(subfile, tmpOID, true) then
  847.         begin
  848.           ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + ' (READ ERROR)');
  849.           ListAppend(subfiles, 'ERROR: '+subfile+' Read error or file invalid');
  850.         end
  851.         else
  852.         begin
  853.           ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + _ShowASNIds(tmpOID));
  854.           ListAppend(subfiles, subfile);
  855.         end;
  856.         FreeOidDef(tmpOID);
  857.       end
  858.       else
  859.       begin
  860.         ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + ' (FILE NOT FOUND)');
  861.         ListAppend(subfiles, 'ERROR: File '+subfile+' was not found');
  862.       end;
  863.     end;
  864.  
  865.     if isRoot then
  866.     begin
  867.       menuIdExit := ListAppend(subsel, 'Back to '+from);
  868.       ListAppend(subfiles, '');
  869.     end
  870.     else menuIdExit := -99;
  871.  
  872.     for i := 0 to ListCount(oid^.SubIds)-1 do
  873.     begin
  874.       sTmp := ListGetElement(oid^.SubIds, i);
  875.       subfile := FileIdPart(sTmp) + OID_EXTENSION;
  876.       if FileExists(subfile) then
  877.       begin
  878.         CreateOidDef(tmpOID);
  879.         if not _ReadOidFile(subfile, tmpOID, true) then
  880.         begin
  881.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (READ ERROR)');
  882.           ListAppend(subfiles, 'ERROR: Read error at file '+subfile+', or file is invalid.');
  883.         end
  884.         else if (tmpOID^.ParentFileId <> oid^.FileId) or
  885.                 (tmpOID^.ParentDotNotation <> oid^.DotNotation) then
  886.         begin
  887.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (BAD BACKREF)');
  888.           ListAppend(subfiles, 'ERROR: File '+subfile+' has a wrong back-reference.');
  889.         end
  890.         else
  891.         begin
  892.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + _ShowASNIds(tmpOID));
  893.           ListAppend(subfiles, subfile);
  894.         end;
  895.         FreeOidDef(tmpOID);
  896.       end
  897.       else
  898.       begin
  899.         ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (FILE NOT FOUND)');
  900.         ListAppend(subfiles, 'ERROR: File '+subfile+' was not found');
  901.       end;
  902.     end;
  903.  
  904.     if oid^.DotNotation <> '' then
  905.     begin
  906.       menuIdAsnEdit := ListAppend(subsel, 'View/Edit ASN.1 identifiers');
  907.       ListAppend(subfiles, '');
  908.     end
  909.     else menuIdAsnEdit := -99;
  910.  
  911.     if oid^.DotNotation <> '' then
  912.     begin
  913.       menuIdIriEdit := ListAppend(subsel, 'View/Edit Unicode Labels (OID-IRI)');
  914.       ListAppend(subfiles, '');
  915.     end
  916.     else menuIdIriEdit := -99;
  917.  
  918.     menuIdDescEdit := ListAppend(subsel, 'Edit description');
  919.     ListAppend(subfiles, '');
  920.  
  921.     menuIdAdd := ListAppend(subsel, 'Add child');
  922.     ListAppend(subfiles, '');
  923.  
  924.     if not isRoot then
  925.     begin
  926.       menuIdDelete := ListAppend(subsel, 'Delete OID');
  927.       ListAppend(subfiles, '');
  928.     end
  929.     else menuIdDelete := -99;
  930.  
  931.     (* Show menu *)
  932.  
  933.     menuX := WhereX + 1;
  934.     menuY := ScreenHeight - ACTIONMENU_SIZE - 1;
  935.     subselres := DrawSelectionList(menuX, menuY,
  936.                                    ScreenWidth-2,
  937.                                    ACTIONMENU_SIZE,
  938.                                    subsel,
  939.                                    true,
  940.                                    'SELECT ACTION',
  941.                                    1);
  942.  
  943.     (* Process user selection *)
  944.  
  945.     if subselres = -1 then
  946.     begin
  947.       exitRequest := true;
  948.     end
  949.     else if subselres = menuIdAsnEdit then
  950.     begin
  951.       if AsnEditor(oid) then
  952.         _WriteOidFile(filename, oid, true);
  953.     end
  954.     else if subselres = menuIdIriEdit then
  955.     begin
  956.       if IriEditor(oid) then
  957.         _WriteOidFile(filename, oid, true);
  958.     end
  959.     else if subselres = menuIdDescEdit then
  960.     begin
  961.       if DescEditor(oid) then
  962.         _WriteOidFile(filename, oid, true);
  963.     end
  964.     else if subselres = menuIdAdd then
  965.     begin
  966.       if NewOidEditor(oid) then
  967.         _WriteOidFile(filename, oid, true);
  968.     end
  969.     else if subselres = menuIdDelete then
  970.     begin
  971.       if _DeleteConfirmation then
  972.       begin
  973.         sTmp := oid^.ParentFileId + OID_EXTENSION;
  974.         DeleteOidRecursive(oid);
  975.         if FileExists(sTmp) then
  976.         begin
  977.           filename := sTmp;
  978.         end
  979.         else
  980.         begin
  981.           ShowMessage('Parent file ' + sTmp + ' was not found', 'ERROR', true);
  982.           _Pause;
  983.           exitRequest := true;
  984.         end;
  985.       end;
  986.     end
  987.     else if subselres = menuIdExit then
  988.     begin
  989.       exitRequest := true;
  990.     end
  991.     else
  992.     begin
  993.       (* Normal OID *)
  994.       (* Above we already checked if the files are valild and existing *)
  995.       sTmp := ListGetElement(subfiles, subselres);
  996.       if Copy(sTmp, 1, Length('ERROR: ')) = 'ERROR: ' then
  997.       begin
  998.         Delete(sTmp, 1, Length('ERROR: '));
  999.         ShowMessage(sTmp, 'ERROR', true);
  1000.         _Pause;
  1001.       end
  1002.       else
  1003.       begin
  1004.         filename := sTmp;
  1005.       end;
  1006.     end;
  1007.     FreeList(subsel);
  1008.     FreeList(subfiles);
  1009.  
  1010.     FreeOidDef(oid);
  1011.   until exitRequest;
  1012. end;
  1013.  
  1014. function CreateRootOIDFile(filename: string; ShowErrorMessage: boolean): boolean;
  1015. var
  1016.   oid: POID;
  1017. begin
  1018.   CreateOidDef(oid);
  1019.   oid^.Description  := 'This is the root of the OID tree.' +#13#10 +
  1020.                        #13#10 +
  1021.                        'Valid subsequent arcs are per definition:' + #13#10 +
  1022.                        '- 0 (itu-t)' + #13#10 +
  1023.                        '- 1 (iso)' + #13#10 +
  1024.                        '- 2 (joint-iso-itu-t)';
  1025.   oid^.FileId       := ZeroPad(0, 8);
  1026.   oid^.DotNotation  := '';
  1027.   oid^.ParentFileId := ZeroPad(0, 8);
  1028.   oid^.ParentDotNotation := '';
  1029.   CreateRootOIDFile := _WriteOidFile(filename, oid, ShowErrorMessage);
  1030.   FreeOidDef(oid);
  1031. end;
  1032.  
  1033. function _GetRootFile(ShowErrorMessage: boolean): string;
  1034. var
  1035.   rootFile: string;
  1036. begin
  1037.   rootFile := ZeroPad(0, 8) + OID_EXTENSION;
  1038.   _GetRootFile := rootFile;
  1039.   if not FileExists(rootFile) then
  1040.   begin
  1041.     if not CreateRootOIDFile(rootFile, ShowErrorMessage) then
  1042.     begin
  1043.       _GetRootFile := '';
  1044.     end;
  1045.   end;
  1046. end;
  1047.  
  1048. procedure OP_ManageOIDs;
  1049. var
  1050.   rootfile: string;
  1051. begin
  1052.   ClrScr;
  1053.   DrawTitleBar('Manage Object Identifiers', TITLEBAR_LEFT_TEXT, '');
  1054.   DrawStatusBar('Loading data... please wait...');
  1055.  
  1056.   (* This will try creating a new root file if it does not exist *)
  1057.   rootfile := _GetRootFile(true);
  1058.   if rootfile = '' then Exit;
  1059.  
  1060.   DisplayOIDFile(rootfile, 'main menu');
  1061. end;
  1062.  
  1063. procedure OP_ReturnToMSDOS;
  1064. begin
  1065.   (* Note: These two lines don't seem to be necessary if you use DoneVideo *)
  1066.   ResetDefaultDosColors;
  1067.   ClrScr; (*Important, so that the DOS command prompt is also LightGray *)
  1068.  
  1069.   WriteLn('Thank you for using OIDplus for DOS.');
  1070.   WriteLn('');
  1071. end;
  1072.  
  1073. function _GetTreeViewLine(oid: POID; indent: integer): string;
  1074. var
  1075.   i: integer;
  1076.   sTmp, sTmp2: string;
  1077. begin
  1078.   (* Build line *)
  1079.   sTmp := RepeatStr(' ', indent*TREEVIEW_INDENT);
  1080.   if oid^.DotNotation = '' then
  1081.     sTmp := sTmp + 'Object Identifiers'
  1082.   else
  1083.     sTmp := sTmp + oid^.DotNotation;
  1084.   sTmp := sTmp + _ShowAsnIds(oid);
  1085.   if TREEVIEW_INCLUDE_DESC then
  1086.   begin
  1087.     if Trim(oid^.Description) <> '' then
  1088.     begin
  1089.       sTmp := sTmp + ': ' + oid^.Description;
  1090.     end;
  1091.   end;
  1092.  
  1093.   sTmp := StringReplace(sTmp, #13#10, ' ');
  1094.   repeat
  1095.     sTmp2 := sTmp;
  1096.     sTmp := StringReplace(sTmp, '  ', ' ');
  1097.   until sTmp = sTmp2;
  1098.  
  1099.   sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1100.   _GetTreeViewLine := sTmp;
  1101. end;
  1102.  
  1103. procedure _RecTreeExport(oid: POID; visList, targetList: PStringList; indent: integer);
  1104. var
  1105.   i: integer;
  1106.   sTmp: string;
  1107.   suboid: POID;
  1108.   childFilename: string;
  1109. begin
  1110.   sTmp := _GetTreeViewLine(oid, indent);
  1111.   sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1112.   ListAppend(visList, sTmp);
  1113.   ListAppend(targetList, oid^.FileID);
  1114.  
  1115.   (* Recursively call children *)
  1116.   for i := 0 to ListCount(oid^.SubIds)-1 do
  1117.   begin
  1118.     sTmp := ListGetElement(oid^.SubIds, i);
  1119.     CreateOidDef(suboid);
  1120.     childFilename := FileIdPart(sTmp) + OID_EXTENSION;
  1121.     if not FileExists(childFilename) then
  1122.     begin
  1123.       sTmp := 'ERROR: MISSING ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1124.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1125.       ListAppend(visList, sTmp);
  1126.       ListAppend(targetList, 'ERROR');
  1127.     end
  1128.     else if not _ReadOidFile(childFilename, suboid, false) then
  1129.     begin
  1130.       sTmp := 'ERROR: READ ERROR AT ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1131.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1132.       ListAppend(visList, sTmp);
  1133.       ListAppend(targetList, 'ERROR');
  1134.     end
  1135.     else if (suboid^.ParentFileId <> oid^.FileId) or
  1136.             (suboid^.ParentDotNotation <> oid^.DotNotation) then
  1137.     begin
  1138.       (* This can happen if a file is missing, and then another OID gets this filename since the number seems to be free *)
  1139.       sTmp := 'ERROR: BAD BACKREF AT ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1140.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1141.       ListAppend(visList, sTmp);
  1142.       ListAppend(targetList, 'ERROR');
  1143.     end
  1144.     else
  1145.     begin
  1146.       _RecTreeExport(suboid, visList, targetList, indent+1);
  1147.       FreeOidDef(suboid);
  1148.     end
  1149.   end;
  1150. end;
  1151.  
  1152. procedure TreeViewPreview(visList, targetList: PStringList);
  1153. var
  1154.   res: integer;
  1155.   sTmp: string;
  1156. begin
  1157.   ClrScr;
  1158.   DrawTitleBar('TreeView Export', TITLEBAR_LEFT_TEXT, TREEVIEW_FILENAME);
  1159.   DrawStatusBar('Press ESC to return to the main menu. Enter to jump to OID.');
  1160.  
  1161.   while true do
  1162.   begin
  1163.     res := DrawSelectionList(2, 3, ScreenWidth-2, ScreenHeight-4,
  1164.                              visList, true, 'PREVIEW OF '+TREEVIEW_FILENAME, 2);
  1165.     if res > -1 then
  1166.     begin
  1167.       (* Jump to selected OID or show error *)
  1168.       sTmp := ListGetElement(targetList, res);
  1169.       if sTmp = 'ERROR' then
  1170.       begin
  1171.         ShowMessage(ListGetElement(visList, res), 'ERROR', true);
  1172.         _Pause;
  1173.       end
  1174.       else
  1175.       begin
  1176.         DisplayOidFile(sTmp + '.OID', 'TreeView Export');
  1177.       end;
  1178.     end
  1179.     else
  1180.     begin
  1181.       break;
  1182.     end;
  1183.   end;
  1184.  
  1185.   DrawStatusBar('');
  1186. end;
  1187.  
  1188. procedure OP_TreeView;
  1189. var
  1190.   F: Text;
  1191.   rootoid: POID;
  1192.   rootfile: string;
  1193.   res: boolean;
  1194.   visList, targetList: PStringList;
  1195. begin
  1196.   ClrScr;
  1197.   DrawTitleBar('TreeView Export', TITLEBAR_LEFT_TEXT, '');
  1198.   DrawStatusBar('Exporting data... please wait...');
  1199.  
  1200.   (* This will try creating a new root file if it does not exist *)
  1201.   rootfile := _GetRootFile(true);
  1202.   if rootfile = '' then
  1203.   begin
  1204.     DrawStatusBar('');
  1205.     Exit;
  1206.   end;
  1207.  
  1208.   CreateList(visList);
  1209.   CreateList(targetList);
  1210.  
  1211.   (* First check if the disk is read-only *)
  1212.   Assign(F, TREEVIEW_FILENAME);
  1213.   {$I-}
  1214.   Rewrite(F);
  1215.   {$I+}
  1216.   if IoResult <> 0 then
  1217.   begin
  1218.     (* Can happen if disk is read-only (Runtime Error 150) *)
  1219.     ShowMessage('Cannot open '+TREEVIEW_FILENAME+' for writing.', 'ERROR', true);
  1220.     _Pause;
  1221.     DrawStatusBar('');
  1222.     Exit;
  1223.   end;
  1224.   Close(F);
  1225.  
  1226.   (* Now do the export *)
  1227.   res := false;
  1228.   CreateOidDef(rootoid);
  1229.   if _ReadOidFile(rootfile, rootoid, true) then
  1230.   begin
  1231.     _RecTreeExport(rootoid, visList, targetList, 0);
  1232.     res := true;
  1233.   end;
  1234.   FreeOidDef(rootoid);
  1235.  
  1236.   (* Save the list (visual part only) *)
  1237.   ListSaveToFile(visList, TREEVIEW_FILENAME);
  1238.  
  1239.   DrawStatusBar('');
  1240.   if res then
  1241.   begin
  1242.     ShowMessage('TreeView successfully exported as '+TREEVIEW_FILENAME, 'TREEVIEW EXPORT', true);
  1243.     _Pause;
  1244.   end;
  1245.  
  1246.   TreeViewPreview(visList, targetList);
  1247.  
  1248.   FreeList(visList);
  1249.   FreeList(targetList);
  1250. end;
  1251.  
  1252. procedure OP_MainMenu;
  1253. var
  1254.   menu: PStringList;
  1255.   menuRes, menuLeft, menuTop: integer;
  1256.   menuIdOID, menuIdTree, menuIdExit: integer;
  1257. begin
  1258.   repeat
  1259.     ClrScr;
  1260.  
  1261.     DrawTitleBar('Welcome to OIDplus for DOS', '', '');
  1262.     DrawStatusBar('(C)2020-2022 ViaThinkSoft. Licensed under the terms of the Apache 2.0 license.');
  1263.     GoToXY(ScreenWidth-Length(VERSIONINFO), ScreenHeight-1);
  1264.     Write(VERSIONINFO);
  1265.  
  1266.     CreateList(menu);
  1267.  
  1268.     menuIdOID  := ListAppend(menu, 'Manage OIDs');
  1269.     menuIdTree := ListAppend(menu, 'Export TreeView');
  1270.     menuIdExit := ListAppend(menu, 'Return to DOS');
  1271.  
  1272.     menuLeft := round(ScreenWidth/2 -MAINMENU_WIDTH/2);
  1273.     menuTop  := round(ScreenHeight/2-MAINMENU_HEIGHT/2);
  1274.     menuRes  := DrawSelectionList(menuLeft, menuTop,
  1275.                                   MAINMENU_WIDTH, MAINMENU_HEIGHT,
  1276.                                   menu, true, 'MAIN MENU', 2);
  1277.     FreeList(menu);
  1278.  
  1279.     if menuRes = menuIdOID then
  1280.     begin
  1281.       OP_ManageOIDs;
  1282.     end
  1283.     else if menuRes = menuIdTree then
  1284.     begin
  1285.       OP_Treeview;
  1286.     end;
  1287.   until (menuRes = menuIdExit) or (MAINMENU_ALLOW_ESC and (menuRes = -1));
  1288.  
  1289.   OP_ReturnToMSDOS;
  1290. end;
  1291.  
  1292. begin
  1293.   InitVideo; (* sets ScreenWidth and ScreenHeight *)
  1294.   CursorOff;
  1295.   OP_MainMenu;
  1296.   CursorOn;
  1297.   DoneVideo;
  1298. end.
  1299.