home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / sdktools / windiff / complist.c < prev    next >
C/C++ Source or Header  |  1997-10-05  |  43KB  |  1,221 lines

  1.  
  2. /******************************************************************************\
  3. *       This is a part of the Microsoft Source Code Samples. 
  4. *       Copyright (C) 1993-1997 Microsoft Corporation.
  5. *       All rights reserved. 
  6. *       This source code is only intended as a supplement to 
  7. *       Microsoft Development Tools and/or WinHelp documentation.
  8. *       See these sources for detailed information regarding the 
  9. *       Microsoft samples programs.
  10. \******************************************************************************/
  11.  
  12. /****************************** Module Header *******************************
  13. * Module Name: COMPLIST.C
  14. *
  15. * Supports a list of compitems, where each compitem represents
  16. * a pair of matching files, or an unmatched file.
  17. *
  18. * Functions:
  19. *
  20. * complist_filedialog()
  21. * complist_dirdialog()
  22. * complist_args()
  23. * complist_getitems()
  24. * complist_delete()
  25. * complist_savelist()
  26. * complist_copyfiles()
  27. * complist_dodlg_savelist()
  28. * complist_dodlg_copyfiles()
  29. * complist_match()
  30. * complist_new()
  31. * complist_dodlg_dir()
  32. *
  33. * Comments:
  34. *
  35. * We build lists of filenames from two pathnames (using the
  36. * scandir module) and then traverse the two lists comparing names.
  37. * Where the names match, we create a CompItem from the matching
  38. * names. Where there is an unmatched name, we create a compitem for it.
  39. *
  40. * We may also be asked to create a complist for two individual files:
  41. * here we create a single compitem for them as a matched pair even if
  42. * the names don't match.
  43. *
  44. ****************************************************************************/
  45.  
  46. #include <windows.h>
  47. #include <stdlib.h>
  48. #include <string.h>
  49. #include <dos.h>
  50. #include <direct.h>
  51.  
  52. #include "gutils.h"
  53. #include "state.h"
  54. #include "windiff.h"
  55. #include "wdiffrc.h"
  56. #include "list.h"
  57. #include "line.h"
  58. #include "scandir.h"
  59. #include "file.h"
  60. #include "section.h"
  61. #include "compitem.h"
  62. #include "complist.h"
  63. #include "view.h"
  64.  
  65.  
  66. extern BOOL bAbort;             /* defined in windiff.c  Read only here */
  67.  
  68. /*
  69.  * The COMPLIST handle is typedef-ed to be a pointer to one
  70.  * of these struct complist
  71.  */
  72. struct complist {
  73.         DIRLIST left;           /* left list of files */
  74.         DIRLIST right;          /* right list of files */
  75.         LIST items;             /* list of COMPITEMs */
  76. };
  77.  
  78. /* ---- module-wide data -------------------------------------*/
  79.  
  80. /* data for communicating between the SaveList dlg and complist_savelist() */
  81.  
  82. char dlg_file[256];                /* filename to save to */
  83.  
  84. /* checkbox options */
  85. BOOL dlg_identical, dlg_differ, dlg_left, dlg_right;
  86. BOOL dlg_recursive = FALSE;
  87.  
  88. /* data for Directory and SaveList */
  89. char dialog_leftname[256];
  90. char dialog_rightname[256];
  91.  
  92. /*
  93.  * data used by dodlg_copyfiles
  94.  */
  95. UINT dlg_options;
  96. char dlg_root[256];
  97.  
  98. /*------------------------timing for performance measurements-----------------*/
  99.  
  100. static DWORD TickCount;         /* time operation started, then time taken*/
  101.  
  102.  
  103. int FAR PASCAL complist_dodlg_savelist(HWND hDlg, UINT message,
  104.         UINT wParam, long lParam);
  105. int FAR PASCAL complist_dodlg_copyfiles(HWND hDlg, UINT message,
  106.         UINT wParam, long lParam);
  107. BOOL complist_match(COMPLIST cl, VIEW view, BOOL fDeep, BOOL fExact);
  108. COMPLIST complist_new(void);
  109. int FAR PASCAL complist_dodlg_dir(HWND hDlg, unsigned message,
  110.         WORD wParam, LONG lParam);
  111.  
  112.  
  113.  
  114. /***************************************************************************
  115.  * Function: complist_filedialog
  116.  *
  117.  * Purpose:
  118.  *
  119.  * Builds a complist by putting up two dialogs to allow the user to
  120.  * select two files. This will build a Complist with one CompItem (even
  121.  * if the names don't match).
  122.  *
  123.  ***************************************************************************/
  124. COMPLIST
  125. complist_filedialog(VIEW view)
  126. {
  127.         COMPLIST cl;
  128.         OFSTRUCT os1, os2;
  129.         char fname[256], FileExt[256], FileOpenSpec[256];
  130.  
  131.         /* ask for the filenames */
  132.         lstrcpy(FileExt, ".c");
  133.         lstrcpy(FileOpenSpec, "*.*");
  134.         lstrcpy(fname,"");
  135.  
  136.         if (!complist_open(LoadRcString(IDS_SELECT_FIRST_FILE), FileExt, FileOpenSpec,
  137.                         &os1, fname) )
  138.                 return(NULL);
  139.  
  140.         lstrcpy(FileExt, ".c");
  141.         lstrcpy(FileOpenSpec, "*.*");
  142.         lstrcpy(fname,"");
  143.  
  144.         if (!complist_open(LoadRcString(IDS_SELECT_SECOND_FILE), FileExt, FileOpenSpec,
  145.                         &os2, fname) )
  146.                 return(NULL);
  147.  
  148.         /* alloc a new structure */
  149.         cl = complist_new();
  150.  
  151.         cl->left = dir_buildlist(os1.szPathName, TRUE);
  152.         cl->right = dir_buildlist(os2.szPathName, TRUE);
  153.  
  154.  
  155.         /* register with the view (must be done after the list is non-null) */
  156.         view_setcomplist(view, cl);
  157.  
  158.         complist_match(cl, view, FALSE, TRUE);
  159.  
  160.         return(cl);
  161. }/* complist_filedialog */
  162.  
  163. /***************************************************************************
  164.  * Function: complist_dirdialog
  165.  *
  166.  * Purpose:
  167.  *
  168.  * Builds a new complist by querying the user for two directory
  169.  * names and scanning those in parallel.
  170.  *
  171.  * Names that match in the same directory will be paired - unmatched
  172.  * names will go in a compitem on their own.
  173.  *
  174.  ***************************************************************************/
  175. COMPLIST
  176. complist_dirdialog(VIEW view)
  177. {
  178.         DLGPROC lpProc;
  179.         BOOL fOK;
  180.  
  181.         /* put up a dialog for the two pathnames */
  182.         lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_dir, hInst);
  183.         windiff_UI(TRUE);
  184.         fOK = DialogBox(hInst, "Directory", hwndClient, lpProc);
  185.         windiff_UI(FALSE);
  186.         FreeProcInstance(lpProc);
  187.  
  188.         if (!fOK) {
  189.                 return(NULL);
  190.         }
  191.  
  192.         return complist_args( dialog_leftname, dialog_rightname
  193.                             , view, dlg_recursive);
  194. } /* complist_dirdialog */
  195.  
  196.  
  197. /***************************************************************************
  198.  * Function: complist_args
  199.  *
  200.  * Purpose:
  201.  *
  202.  * Given two pathname strings, scan the directories and traverse them
  203.  * in parallel comparing matching names.
  204.  *
  205.  ***************************************************************************/
  206. COMPLIST
  207. complist_args(LPSTR p1, LPSTR p2, VIEW view, BOOL fDeep)
  208. {
  209.         COMPLIST cl;
  210.         char msg[256];
  211.  
  212.  
  213.         /* alloc a new complist */
  214.         cl = complist_new();
  215.  
  216.         cl->left = dir_buildlist(p1, TRUE);
  217.         /* check that we could find the paths, and report if not */
  218.         if (cl->left == NULL) {
  219.                 wsprintf((LPTSTR)msg, LoadRcString(IDS_COULDNT_FIND), p1);
  220.                 MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP);
  221.                 return(NULL);
  222.         }
  223.  
  224.         cl->right = dir_buildlist(p2, TRUE);
  225.         if (cl->right == NULL) {
  226.                 wsprintf((LPTSTR)msg, LoadRcString(IDS_COULDNT_FIND), p2);
  227.                 MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP);
  228.                 return(NULL);
  229.         }
  230.  
  231.         /* register with the view (must be done after building lists) */
  232.         view_setcomplist(view, cl);
  233.  
  234.         complist_match(cl, view, fDeep, TRUE);
  235.  
  236.         return(cl);
  237. } /* complist_args */
  238.  
  239. /***************************************************************************
  240.  * Function: complist_getitems
  241.  *
  242.  * Purpose:
  243.  *
  244.  * Gets the handle to the list of COMPITEMs. The list continues to be
  245.  * owned by the COMPLIST, so don't delete except by calling complist_delete.
  246.  *
  247.  ***************************************************************************/
  248. LIST
  249. complist_getitems(COMPLIST cl)
  250. {
  251.         if (cl == NULL) {
  252.                 return(NULL);
  253.         }
  254.  
  255.         return(cl->items);
  256. }
  257.  
  258. /***************************************************************************
  259.  * Function: complist_delete
  260.  *
  261.  * Purpose:
  262.  *
  263.  * Deletes a complist and all associated CompItems and DIRLISTs. Note this
  264.  * does not delete any VIEW - the VIEW owns the COMPLIST and not the other
  265.  * way around.
  266.  *
  267.  **************************************************************************/
  268. void
  269. complist_delete(COMPLIST cl)
  270. {
  271.         COMPITEM item;
  272.  
  273.         if (cl == NULL) {
  274.                 return;
  275.         }
  276.  
  277.         /* delete the two directory scan lists */
  278.         dir_delete(cl->left);
  279.         dir_delete(cl->right);
  280.  
  281.         /* delete the compitems in the list */
  282.         List_TRAVERSE(cl->items, item) {
  283.                         compitem_delete(item);
  284.         }
  285.  
  286.         /* delete the list itself */
  287.         List_Destroy(&cl->items);
  288.  
  289.         gmem_free(hHeap, (LPSTR) cl, sizeof(struct complist));
  290.  
  291. }
  292.  
  293. /***************************************************************************
  294.  * Function: complist_savelist
  295.  *
  296.  * Purpose:
  297.  *
  298.  * Writes out to a text file the list of compitems as relative filenames
  299.  * one per line.
  300.  *
  301.  * If savename is non-null, use this as the filename for output; otherwise,
  302.  * query the user via a dialog for the filename and include options.
  303.  *
  304.  **************************************************************************/
  305. void
  306. complist_savelist(COMPLIST cl, LPSTR savename, UINT options)
  307. {
  308.         DLGPROC lpProc;
  309.         static BOOL done_init = FALSE;
  310.         BOOL bOK;
  311.         int fh, state;
  312.         OFSTRUCT os;
  313.         char msg[256];
  314.         HCURSOR hcurs;
  315.         COMPITEM ci;
  316.         LPSTR pstr, lhead, rhead;
  317.         int nFiles = 0;
  318.  
  319.         if (!done_init) {
  320.                 /* init the options once round - but keep the same options
  321.                  * for the rest of the session.
  322.                  */
  323.  
  324.                 /* first init default options */
  325.                 dlg_identical = FALSE;
  326.                 dlg_differ = TRUE;
  327.                 dlg_left = TRUE;
  328.                 dlg_right = FALSE;
  329.  
  330.                 dlg_file[0] = '\0';
  331.  
  332.                 done_init = TRUE;
  333.         }
  334.  
  335.         if (cl == NULL) {
  336.                 return;
  337.         }
  338.  
  339.         if (savename == NULL) {
  340.  
  341.                 /* store the left and right rootnames so that dodlg_savelist
  342.                  * can display them in the dialog.
  343.                  */
  344.                 pstr = dir_getroot_list(cl->left);
  345.                 lstrcpy(dialog_leftname, pstr);
  346.                 dir_freeroot_list(cl->left, pstr);
  347.  
  348.                 pstr = dir_getroot_list(cl->right);
  349.                 lstrcpy(dialog_rightname, pstr);
  350.                 dir_freeroot_list(cl->right, pstr);
  351.  
  352.                 lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_savelist, hInst);
  353.                 windiff_UI(TRUE);
  354.                 bOK = DialogBox(hInst, "SaveList", hwndClient, lpProc);
  355.                 windiff_UI(FALSE);
  356.                 FreeProcInstance(lpProc);
  357.  
  358.                 if (!bOK) {
  359.                         /* user cancelled from dialog box */
  360.                         return;
  361.                 }
  362.                 savename = dlg_file;
  363.  
  364.         } else {
  365.                 dlg_identical = (options & INCLUDE_SAME);
  366.                 dlg_differ = (options & INCLUDE_DIFFER);
  367.                 dlg_left = (options & INCLUDE_LEFTONLY);
  368.                 dlg_right = (options & INCLUDE_RIGHTONLY);
  369.         }
  370.  
  371.  
  372.         /* try to open the file */
  373.         fh = OpenFile(savename, &os, OF_CREATE|OF_READWRITE|OF_SHARE_DENY_WRITE);
  374.         if (fh < 0) {
  375.                 wsprintf((LPTSTR)msg, LoadRcString(IDS_CANT_OPEN), savename);
  376.                 windiff_UI(TRUE);
  377.                 MessageBox(NULL, msg, "Windiff", MB_ICONSTOP|MB_OK);
  378.                 windiff_UI(FALSE);
  379.                 return;
  380.         }
  381.  
  382.         hcurs = SetCursor(LoadCursor(NULL, IDC_WAIT));
  383.  
  384.         /* write out the header line */
  385.         lhead = dir_getroot_list(cl->left);
  386.         rhead = dir_getroot_list(cl->right);
  387. {
  388.         TCHAR szBuf1[20],szBuf2[20],szBuf3[20],szBuf4[20];
  389.         lstrcpy(szBuf1,(LPSTR)(dlg_identical ? LoadRcString(IDS_IDENTICAL_COMMA) : ""));
  390.         lstrcpy(szBuf2,(LPSTR)(dlg_left ? LoadRcString(IDS_LEFT_ONLY_COMMA) : ""));
  391.         lstrcpy(szBuf3,(LPSTR)(dlg_right ? LoadRcString(IDS_RIGHT_ONLY_COMMA) : ""));
  392.         lstrcpy(szBuf4,(LPSTR)(dlg_differ ? LoadRcString(IDS_DIFFERING) : ""));
  393.         wsprintf(msg, LoadRcString(IDS_HEADER_LINE_STR),
  394.                 lhead, rhead, szBuf1, szBuf2, szBuf3, szBuf4);
  395. }
  396.         _lwrite(fh, msg, lstrlen(msg));
  397.         dir_freeroot_list(cl->left, lhead);
  398.         dir_freeroot_list(cl->right, rhead);
  399.  
  400.  
  401.         /* traverse the list of compitems looking for the
  402.          * ones we are supposed to include
  403.          */
  404.         List_TRAVERSE(cl->items, ci) {
  405.  
  406.                 /* check if files of this type are to be listed */
  407.                 state = compitem_getstate(ci);
  408.  
  409.                 if ((state == STATE_SAME) && (!dlg_identical)) {
  410.                         continue;
  411.                 } else if ((state == STATE_DIFFER) && (!dlg_differ)) {
  412.                         continue;
  413.                 } else if ((state == STATE_FILELEFTONLY) && (!dlg_left)) {
  414.                         continue;
  415.                 } else if ((state == STATE_FILERIGHTONLY) && (!dlg_right)) {
  416.                         continue;
  417.                 }
  418.  
  419.                 nFiles++;
  420.  
  421.                 /* output the list line */
  422.                 wsprintf((LPTSTR)msg, "%s\r\n", compitem_gettext_tag(ci));
  423.                 _lwrite(fh, msg, lstrlen(msg));
  424.         }
  425.  
  426.         /* write tail line */
  427.         wsprintf((LPTSTR)msg, LoadRcString(IDS_FILES_LISTED), nFiles);
  428.         _lwrite(fh, msg, lstrlen(msg));
  429.  
  430.         /* - close file and we are finished */
  431.         _lclose(fh);
  432.  
  433.         SetCursor(hcurs);
  434. } /* complist_savelist */
  435.  
  436. /***************************************************************************
  437.  * Function: complist_copyfiles
  438.  *
  439.  * Purpose:
  440.  *
  441.  * To copy files to a new directory newroot. if newroot is NULL, query the user
  442.  * via a dialog to get the new dir name and options.
  443.  *
  444.  * Options are either COPY_FROMLEFT or COPY_FROMRIGHT (indicating which
  445.  * tree is to be the source of the files, plus any or all of
  446.  * INCLUDE_SAME, INCLUDE_DIFFER and INCLUDE_LEFT (INCLUDE_LEFT
  447.  * and INCLUDE_RIGHT are treated the same here since the COPY_FROM* option
  448.  * indicates which side to copy from).
  449.  *
  450.  ***************************************************************************/
  451. void
  452. complist_copyfiles(COMPLIST cl, LPSTR newroot, UINT options)
  453. {
  454.         int nFiles = 0;
  455.         int nFails = 0;
  456.         static BOOL done_init = FALSE;
  457.         LPSTR pstr;
  458.         char buffer[64];
  459.         DIRITEM diritem;
  460.         DLGPROC lpProc;
  461.         BOOL bOK;
  462.         COMPITEM ci;
  463.         int state;
  464.  
  465.         if (!done_init) {
  466.                 /*
  467.                  * one-time initialisation of dialog defaults
  468.                  */
  469.                 dlg_options = COPY_FROMLEFT|INCLUDE_LEFTONLY|INCLUDE_DIFFER;
  470.                 dlg_root[0] = '\0';
  471.                 done_init = TRUE;
  472.         }
  473.  
  474.         if (cl == NULL) {
  475.                 return;
  476.         }
  477.  
  478.  
  479.         if (newroot == NULL) {
  480.                 /*
  481.                  * put up dialog to query rootname and options
  482.                  */
  483.  
  484.                 /* store the left and right rootnames so that the dlg proc
  485.                  * can display them in the dialog.
  486.                  */
  487.                 pstr = dir_getroot_list(cl->left);
  488.                 lstrcpy(dialog_leftname, pstr);
  489.                 dir_freeroot_list(cl->left, pstr);
  490.  
  491.                 pstr = dir_getroot_list(cl->right);
  492.                 lstrcpy(dialog_rightname, pstr);
  493.                 dir_freeroot_list(cl->right, pstr);
  494.  
  495.                 do {
  496.                         lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_copyfiles, hInst);
  497.                         windiff_UI(TRUE);
  498.                         bOK = DialogBox(hInst, "CopyFiles", hwndClient, lpProc);
  499.                         windiff_UI(FALSE);
  500.                         FreeProcInstance(lpProc);
  501.  
  502.                         if (!bOK) {
  503.                                 /* user cancelled from dialog box */
  504.                                 return;
  505.                         }
  506.                         if (lstrlen(dlg_root) == 0) {
  507.                                 windiff_UI(TRUE);
  508.                                 MessageBox(NULL, LoadRcString(IDS_ENTER_DIR_NAME),
  509.                                                 "Windiff", MB_ICONSTOP|MB_OK);
  510.                                 windiff_UI(FALSE);
  511.                         }
  512.                 } while (lstrlen(dlg_root) == 0);
  513.  
  514.         } else {
  515.                 dlg_options = options;
  516.                 lstrcpy(dlg_root, newroot);
  517.         }
  518.  
  519.         TickCount = GetTickCount();
  520.  
  521.         if (dlg_options & COPY_FROMLEFT) {
  522.                 if (!dir_startcopy(cl->left))
  523.                         return;
  524.         } else {
  525.                 if (!dir_startcopy(cl->right))
  526.                         return;
  527.         }
  528.  
  529.         /*
  530.          * traverse the list of compitems copying files as necessary
  531.          */
  532.         List_TRAVERSE(cl->items, ci) {
  533.  
  534.                 if (bAbort){
  535.                         break;  /* fall into end_copy processing */
  536.                 }
  537.                 /* check if files of this type are to be copied */
  538.                 state = compitem_getstate(ci);
  539.  
  540.                 if ((state == STATE_SAME) && !(dlg_options & INCLUDE_SAME)) {
  541.                         continue;
  542.                 } else if ((state == STATE_DIFFER) && !(dlg_options & INCLUDE_DIFFER)) {
  543.                         continue;
  544.                 } else if (state == STATE_FILELEFTONLY) {
  545.                         if (dlg_options & COPY_FROMRIGHT) {
  546.                                 continue;
  547.                         }
  548.                         if ((dlg_options & (INCLUDE_LEFTONLY | INCLUDE_RIGHTONLY)) == 0) {
  549.                                 continue;
  550.                         }
  551.                 } else if (state == STATE_FILERIGHTONLY) {
  552.                         if (dlg_options & COPY_FROMLEFT) {
  553.                                 continue;
  554.                         }
  555.                         if ((dlg_options & (INCLUDE_LEFTONLY | INCLUDE_RIGHTONLY)) == 0) {
  556.                                 continue;
  557.                         }
  558.                 }
  559.  
  560.                 if (dlg_options & COPY_FROMLEFT) {
  561.                         diritem = file_getdiritem(compitem_getleftfile(ci));
  562.                 } else {
  563.                         diritem = file_getdiritem(compitem_getrightfile(ci));
  564.                 }
  565.  
  566.                 /*
  567.                  * copy the file to the new root directory
  568.                  */
  569.                 if (dir_copy(diritem, dlg_root) == FALSE) {
  570.                         nFails++;
  571.                         pstr = dir_getrelname(diritem);
  572.                         wsprintf((LPTSTR)buffer, LoadRcString(IDS_FAILED_TO_COPY), pstr);
  573.                         dir_freerelname(diritem, pstr);
  574.  
  575.                         if (MessageBox(NULL, buffer, NULL, MB_OKCANCEL | MB_ICONSTOP) == IDCANCEL)
  576.                             /* user pressed cancel - abort current operation*/
  577.                             /* fall through to end-copy processing */
  578.                             break;
  579.  
  580.                 } else {
  581.                         nFiles++;
  582.                 }
  583.  
  584.                 wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPYING), nFiles);
  585.                 SetStatus(buffer);
  586.  
  587.  
  588.                 /*
  589.                  * allow user interface to continue
  590.                  */
  591.                 if (Poll()) {
  592.                         /* abort requested */
  593.                         TickCount = GetTickCount()-TickCount;
  594.                         windiff_UI(TRUE);
  595.                         MessageBox(hwndClient, LoadRcString(IDS_COPY_ABORTED),
  596.                                 "WinDiff", MB_OK|MB_ICONINFORMATION);
  597.                         windiff_UI(FALSE);
  598.                         break;
  599.                 }
  600.  
  601.         } /* traverse */
  602.         if (dlg_options & COPY_FROMLEFT) {
  603.                 nFails = dir_endcopy(cl->left);
  604.         } else {
  605.                 nFails = dir_endcopy(cl->right);
  606.         }
  607.         TickCount = GetTickCount()-TickCount;
  608.  
  609.         if (nFails<0) {
  610.                 wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPY_FAILED), -nFails);
  611.         } else {
  612.                 wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPY_COMPLETE), nFails);
  613.         }
  614.         windiff_UI(TRUE);
  615.         MessageBox(hwndClient, buffer, "WinDiff", MB_OK|MB_ICONINFORMATION);
  616.         windiff_UI(FALSE);
  617.  
  618.         buffer[0] = '\0';
  619.         SetStatus(buffer);
  620. } /* complist_copyfiles */
  621.  
  622.  
  623. /***************************************************************************
  624.  * Function: complist_match
  625.  *
  626.  * Purpose:
  627.  *
  628.  * Matches up two lists of filenames
  629.  *
  630.  * Commentsz:
  631.  *
  632.  * We can find out from the DIRLIST handle whether the original list
  633.  * was a file or a directory name.
  634.  * If the user typed:
  635.  *      two file names  - match these two item even if the names differ
  636.  *
  637.  *      two dirs        - match only those items whose names match
  638.  *
  639.  *      one file and one dir
  640.  *                      - try to find a file of that name in the dir.
  641.  *
  642.  * This function returns TRUE if the complist_match was ok, or FALSE if it was
  643.  * aborted in some way.
  644.  *
  645.  ***************************************************************************/
  646. BOOL
  647. complist_match(COMPLIST cl, VIEW view, BOOL fDeep, BOOL fExact)
  648. {
  649.         LPSTR lname;
  650.         LPSTR rname;
  651.         DIRITEM leftitem, rightitem;
  652.         int cmpvalue;
  653.  
  654.         TickCount = GetTickCount();
  655.  
  656.         if (dir_isfile(cl->left) ) {
  657.  
  658.                 if (dir_isfile(cl->right)) {
  659.                         /* two files */
  660.  
  661.                         /* there should be one item in each list - make
  662.                          * a compitem by matching these two and append it to the
  663.                          * list
  664.                          */
  665.                         compitem_new(dir_firstitem(cl->left),
  666.                                        dir_firstitem(cl->right), cl->items, fExact);
  667.  
  668.                         view_newitem(view);
  669.  
  670.                         TickCount = GetTickCount() - TickCount;
  671.                         return TRUE;
  672.                 }
  673.                 /* left is file, right is dir */
  674.                 leftitem = dir_firstitem(cl->left);
  675.                 rightitem = dir_firstitem(cl->right);
  676.                 lname = dir_getrelname(leftitem);
  677.                 while (rightitem != NULL) {
  678.                         rname = dir_getrelname(rightitem);
  679.                         cmpvalue = lstrcmpi(lname, rname);
  680.                         dir_freerelname(rightitem, rname);
  681.  
  682.                         if (cmpvalue == 0) {
  683.                                 /* this is the match */
  684.                                 compitem_new(leftitem, rightitem, cl->items, fExact);
  685.                                 view_newitem(view);
  686.  
  687.                                 dir_freerelname(leftitem, lname);
  688.  
  689.                                 TickCount = GetTickCount() - TickCount;
  690.                                 return(TRUE);
  691.                         }
  692.  
  693.                         rightitem = dir_nextitem(cl->right, rightitem, fDeep);
  694.                 }
  695.                 /* not found */
  696.                 dir_freerelname(leftitem, lname);
  697.                 compitem_new(leftitem, NULL, cl->items, fExact);
  698.                 view_newitem(view);
  699.                 TickCount = GetTickCount() - TickCount;
  700.                 return(TRUE);
  701.  
  702.         } else if (dir_isfile(cl->right)) {
  703.  
  704.                 /* left is dir, right is file */
  705.  
  706.                 /* loop through the left dir, looking for
  707.                  * a file that has the same name as rightitem
  708.                  */
  709.  
  710.                 leftitem = dir_firstitem(cl->left);
  711.                 rightitem = dir_firstitem(cl->right);
  712.                 rname = dir_getrelname(rightitem);
  713.                 while (leftitem != NULL) {
  714.                         lname = dir_getrelname(leftitem);
  715.                         cmpvalue = lstrcmpi(lname, rname);
  716.                         dir_freerelname(leftitem, lname);
  717.  
  718.                         if (cmpvalue == 0) {
  719.                                 /* this is the match */
  720.                                 compitem_new(leftitem, rightitem, cl->items, fExact);
  721.                                 view_newitem(view);
  722.  
  723.                                 dir_freerelname(rightitem, rname);
  724.  
  725.                                 TickCount = GetTickCount() - TickCount;
  726.                                 return(TRUE);
  727.                         }
  728.  
  729.                         leftitem = dir_nextitem(cl->left, leftitem, fDeep);
  730.                 }
  731.                 /* not found */
  732.                 dir_freerelname(rightitem, rname);
  733.                 compitem_new(NULL, rightitem, cl->items, fExact);
  734.                 view_newitem(view);
  735.                 TickCount = GetTickCount() - TickCount;
  736.                 return(TRUE);
  737.         }
  738.  
  739.         /* two directories */
  740.  
  741.         /* traverse the two lists in parallel comparing the relative names*/
  742.  
  743.         leftitem = dir_firstitem(cl->left);
  744.         rightitem = dir_firstitem(cl->right);
  745.         while ((leftitem != NULL) && (rightitem != NULL)) {
  746.  
  747.                 lname = dir_getrelname(leftitem);
  748.                 rname = dir_getrelname(rightitem);
  749.                 cmpvalue = utils_CompPath(lname, rname);
  750.                 dir_freerelname(leftitem, lname);
  751.                 dir_freerelname(rightitem, rname);
  752.  
  753.                 if (cmpvalue == 0) {
  754.                         compitem_new(leftitem, rightitem, cl->items, fExact);
  755.                         if (view_newitem(view)) {
  756.                                 TickCount = GetTickCount() - TickCount;
  757.                                 return(FALSE);
  758.                         }
  759.                         leftitem = dir_nextitem(cl->left, leftitem, fDeep);
  760.                         rightitem = dir_nextitem(cl->right, rightitem, fDeep);
  761.  
  762.                 } else if (cmpvalue < 0) {
  763.                         compitem_new(leftitem, NULL, cl->items, fExact);
  764.                         if (view_newitem(view)) {
  765.                                 TickCount = GetTickCount() - TickCount;
  766.                                 return(FALSE);
  767.                         }
  768.                         leftitem = dir_nextitem(cl->left, leftitem, fDeep);
  769.                 }  else {
  770.                         compitem_new(NULL, rightitem, cl->items, fExact);
  771.                         if (view_newitem(view)) {
  772.                                 TickCount = GetTickCount() - TickCount;
  773.                                 return(FALSE);
  774.                         }
  775.                         rightitem = dir_nextitem(cl->right, rightitem, fDeep);
  776.                 }
  777.         }
  778.  
  779.  
  780.         /* any left over are unmatched */
  781.         while (leftitem != NULL) {
  782.                 compitem_new(leftitem, NULL, cl->items, fExact);
  783.                 if (view_newitem(view)) {
  784.                         TickCount = GetTickCount() - TickCount;
  785.                         return(FALSE);
  786.                 }
  787.                 leftitem = dir_nextitem(cl->left, leftitem, fDeep);
  788.         }
  789.         while (rightitem != NULL) {
  790.                 compitem_new(NULL, rightitem, cl->items, fExact);
  791.                 if (view_newitem(view)) {
  792.                         TickCount = GetTickCount() - TickCount;
  793.                         return(FALSE);
  794.                 }
  795.                 rightitem = dir_nextitem(cl->right, rightitem, fDeep);
  796.         }
  797.         TickCount = GetTickCount() - TickCount;
  798.         return(TRUE);
  799. } /* complist_match */
  800.  
  801. /* return time last operation took in milliseconds */
  802. DWORD complist_querytime(void)
  803. {       return TickCount;
  804. }
  805.  
  806.  
  807. /***************************************************************************
  808.  * Function: complist_dodlg_savelist
  809.  *
  810.  * Purpose:
  811.  *
  812.  * Dialog to query about filename and types of files. Init dlg fields from
  813.  * the dlg_* variables, and save state to the dlg_* variables on dialog
  814.  * close. return TRUE for OK, or FALSE for cancel (from the dialogbox()
  815.  * using EndDialog).
  816.  *
  817.  **************************************************************************/
  818. int FAR PASCAL
  819. complist_dodlg_savelist(HWND hDlg, UINT message, UINT wParam, long lParam)
  820. {
  821.         static char buffer[256];
  822.  
  823.         switch(message) {
  824.  
  825.  
  826.         case WM_INITDIALOG:
  827.                 SendDlgItemMessage(hDlg, IDD_IDENTICAL, BM_SETCHECK,
  828.                         dlg_identical ? 1 : 0, 0);
  829.                 SendDlgItemMessage(hDlg, IDD_DIFFER, BM_SETCHECK,
  830.                         dlg_differ ? 1 : 0, 0);
  831.                 SendDlgItemMessage(hDlg, IDD_LEFT, BM_SETCHECK,
  832.                         dlg_left ? 1 : 0, 0);
  833.                 SendDlgItemMessage(hDlg, IDD_RIGHT, BM_SETCHECK,
  834.                         dlg_right ? 1 : 0, 0);
  835.  
  836.                 SetDlgItemText(hDlg, IDD_FILE, dlg_file);
  837.  
  838.                 /* convert 'left tree' into the right name */
  839.                 wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname);
  840.                 SendDlgItemMessage(hDlg, IDD_LEFT, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
  841.                 /* convert 'right tree' msg into correct path */
  842.                 wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname);
  843.                 SendDlgItemMessage(hDlg, IDD_RIGHT, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer);
  844.  
  845.  
  846.                 return(TRUE);
  847.  
  848.         case WM_COMMAND:
  849.                 switch (GET_WM_COMMAND_ID(wParam, lParam)) {
  850.  
  851.                 case IDOK:
  852.                         dlg_identical = (SendDlgItemMessage(hDlg, IDD_IDENTICAL,
  853.                                         BM_GETCHECK, 0, 0) == 1);
  854.                         dlg_differ = (SendDlgItemMessage(hDlg, IDD_DIFFER,
  855.                                         BM_GETCHECK, 0, 0) == 1);
  856.                         dlg_left = (SendDlgItemMessage(hDlg, IDD_LEFT,
  857.                                         BM_GETCHECK, 0, 0) == 1);
  858.                         dlg_right = (SendDlgItemMessage(hDlg, IDD_RIGHT,
  859.                                         BM_GETCHECK, 0, 0) == 1);
  860.                         GetDlgItemText(hDlg, IDD_FILE, dlg_file, sizeof(dlg_file));
  861.  
  862.                         EndDialog(hDlg, TRUE);
  863.                         break;
  864.  
  865.                 case IDCANCEL:
  866.                         EndDialog(hDlg, FALSE);
  867.                         break;
  868.                 }
  869.         }
  870.         return(FALSE);
  871. } /* complist_dodlg_savelist */
  872.  
  873. /***************************************************************************
  874.  * Function: complist_dodlg_copyfiles
  875.  *
  876.  * Purpose:
  877.  *
  878.  * dialog to get directory name and inclusion options. Init dlg fields from
  879.  * the dlg_* variables, and save state to the dlg_* variables on dialog
  880.  * close. return TRUE for OK, or FALSE for cancel (from the dialogbox()
  881.  * using EndDialog).
  882.  * 
  883.  **************************************************************************/
  884. int FAR PASCAL
  885. complist_dodlg_copyfiles(HWND hDlg, UINT message, UINT wParam, long lParam)
  886. {
  887.         static char buffer[256];
  888.  
  889.         switch(message) {
  890.  
  891.  
  892.         case WM_INITDIALOG:
  893.                 /*
  894.                  * set checkboxes and directory field to defaults
  895.                  */
  896.                 CheckDlgButton(hDlg, IDD_IDENTICAL,
  897.                         (dlg_options & INCLUDE_SAME) ? 1 : 0);
  898.  
  899.                 CheckDlgButton(hDlg, IDD_DIFFER,
  900.                         (dlg_options & INCLUDE_DIFFER) ? 1 : 0);
  901.  
  902.                 CheckDlgButton(hDlg, IDD_LEFT,
  903.                         (dlg_options & (INCLUDE_LEFTONLY|INCLUDE_RIGHTONLY)) ? 1 : 0);
  904.  
  905.                 SetDlgItemText(hDlg, IDD_DIR1, dlg_root);
  906.  
  907.                 /*
  908.                  * set 'copy from' buttons to have the full pathname
  909.                  */
  910.                 SetDlgItemText(hDlg, IDD_FROMLEFT, dialog_leftname);
  911.                 SetDlgItemText(hDlg, IDD_FROMRIGHT, dialog_rightname);
  912.  
  913.                 /*
  914.                  * set default radio button for copy from, and set
  915.                  * the text on the 'files only in...' checkbox to
  916.                  * indicate which path is being selected
  917.                  */
  918.                 if (dlg_options & COPY_FROMLEFT) {
  919.                         CheckRadioButton(hDlg, IDD_FROMLEFT, IDD_FROMRIGHT,
  920.                                         IDD_FROMLEFT);
  921.  
  922.                         wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname);
  923.                         SetDlgItemText(hDlg, IDD_LEFT, buffer);
  924.                 } else {
  925.                         CheckRadioButton(hDlg, IDD_FROMLEFT, IDD_FROMRIGHT,
  926.                                         IDD_FROMRIGHT);
  927.  
  928.                         wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname);
  929.                         SetDlgItemText(hDlg, IDD_LEFT, buffer);
  930.                 }
  931.  
  932.                 return(TRUE);
  933.  
  934.         case WM_COMMAND:
  935.                 switch (GET_WM_COMMAND_ID(wParam, lParam)) {
  936.  
  937.                 case IDD_FROMLEFT:
  938.                         wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname);
  939.                         SetDlgItemText(hDlg, IDD_LEFT, buffer);
  940.  
  941.                         dlg_options &= ~(COPY_FROMRIGHT);
  942.                         dlg_options |= COPY_FROMLEFT;
  943.                         break;
  944.  
  945.                 case IDD_FROMRIGHT:
  946.                         wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname);
  947.                         SetDlgItemText(hDlg, IDD_LEFT, buffer);
  948.  
  949.                         dlg_options &= ~(COPY_FROMLEFT);
  950.                         dlg_options |= COPY_FROMRIGHT;
  951.                         break;
  952.  
  953.                 case IDOK:
  954.                         if (SendDlgItemMessage(hDlg, IDD_IDENTICAL,
  955.                             BM_GETCHECK, 0, 0) == 1) {
  956.                                 dlg_options |= INCLUDE_SAME;
  957.                         } else {
  958.                                 dlg_options &= ~INCLUDE_SAME;
  959.                         }
  960.                         if (SendDlgItemMessage(hDlg, IDD_DIFFER,
  961.                             BM_GETCHECK, 0, 0) == 1) {
  962.                                 dlg_options |= INCLUDE_DIFFER;
  963.                         } else {
  964.                                 dlg_options &= ~INCLUDE_DIFFER;
  965.                         }
  966.                         if (SendDlgItemMessage(hDlg, IDD_LEFT,
  967.                             BM_GETCHECK, 0, 0) == 1) {
  968.                                 dlg_options |= INCLUDE_LEFTONLY;
  969.                         } else {
  970.                                 dlg_options &= ~INCLUDE_LEFTONLY;
  971.                         }
  972.                         GetDlgItemText(hDlg, IDD_DIR1, dlg_root, sizeof(dlg_root));
  973.  
  974.                         EndDialog(hDlg, TRUE);
  975.                         break;
  976.  
  977.                 case IDCANCEL:
  978.                         EndDialog(hDlg, FALSE);
  979.                         break;
  980.                 }
  981.         }
  982.         return(FALSE);
  983. } /* complist_dodlg_copyfiles */
  984.  
  985. /***************************************************************************
  986.  * Function: complist_new
  987.  *
  988.  * Purpose:
  989.  *
  990.  * Allocates a new complist and initialise it 
  991.  *
  992.  **************************************************************************/
  993. COMPLIST
  994. complist_new(void)
  995. {
  996.         COMPLIST cl;
  997.  
  998.         cl = (COMPLIST) gmem_get(hHeap, sizeof(struct complist));
  999.         cl->left = NULL;
  1000.         cl->right = NULL;
  1001.         cl->items = List_Create();
  1002.  
  1003.         return(cl);
  1004. } /* complist_new */
  1005.  
  1006. /***************************************************************************
  1007.  * Function: complist_dodlg_dir
  1008.  *
  1009.  * Purpose:
  1010.  *
  1011.  * Dialog box function to ask for two directory names.
  1012.  * no listing of files etc - just two edit fields  in which the
  1013.  * user can type a file or a directory name.
  1014.  *
  1015.  * Initialises the names from win.ini, and stores them to win.ini first.
  1016.  *
  1017.  **************************************************************************/
  1018. int FAR PASCAL
  1019. complist_dodlg_dir(HWND hDlg, unsigned message, WORD wParam, LONG lParam)
  1020. {
  1021.         static char path[256];
  1022.         static char buffer[256];
  1023.  
  1024.         switch (message) {
  1025.  
  1026.         case WM_INITDIALOG:
  1027.  
  1028.                 /* fill the edit fields with the current
  1029.                  * directory as a good starting point
  1030.                  */
  1031.                 _getcwd(path, sizeof(path));
  1032.                 AnsiLowerBuff(path, strlen(path));
  1033.                 GetProfileString(APPNAME, "NameLeft", path, buffer, 256);
  1034.                 SetDlgItemText(hDlg, IDD_DIR1, buffer);
  1035.                 GetProfileString(APPNAME, "NameRight", path, buffer, 256);
  1036.                 SetDlgItemText(hDlg, IDD_DIR2, buffer);
  1037.                 /* set recursive option to most recent value */
  1038.                 CheckDlgButton(hDlg, IDD_RECURSIVE, dlg_recursive);
  1039.                 return(TRUE);
  1040.  
  1041.         case WM_COMMAND:
  1042.                 switch (LOWORD(wParam)) {
  1043.                 case IDCANCEL:
  1044.                         EndDialog(hDlg, FALSE);
  1045.                         return(TRUE);
  1046.  
  1047.                 case IDOK:
  1048.                         /* fetch the text from the dialog, and remember
  1049.                          * it in win.ini
  1050.                          */
  1051.  
  1052.                         GetDlgItemText(hDlg, IDD_DIR1,
  1053.                                 dialog_leftname, sizeof(dialog_leftname));
  1054.                         WriteProfileString(APPNAME, "NameLeft", dialog_leftname);
  1055.  
  1056.                         GetDlgItemText(hDlg, IDD_DIR2,
  1057.                                 dialog_rightname, sizeof(dialog_rightname));
  1058.                         WriteProfileString(APPNAME, "NameRight", dialog_rightname);
  1059.  
  1060.                         /* fetch recursive option */
  1061.                         dlg_recursive = SendDlgItemMessage(hDlg, IDD_RECURSIVE,
  1062.                                 BM_GETCHECK, 0, 0);
  1063.  
  1064.                         EndDialog(hDlg, TRUE);
  1065.                         return(TRUE);
  1066.                 }
  1067.                 break;
  1068.         }
  1069.         return(FALSE);
  1070. } /* complist_dodlg_dir */
  1071.  
  1072. /***************************************************************************
  1073.  * Function: complist_open
  1074.  *
  1075.  * Purpose:
  1076.  *      
  1077.  * Puts up dialog asking the user to select an existing file to open.
  1078.  *
  1079.  * Parameters:
  1080.  *
  1081.  *      prompt - message to user indicating purpose of file
  1082.  *               (to be displayed somewhere in dialog box.
  1083.  *
  1084.  *      ext    - default file extension if user enters file without
  1085.  *               extension.
  1086.  *
  1087.  *      spec   - default file spec (eg *.*)
  1088.  *
  1089.  *      osp    - OFSTRUCT representing file, if successfully open.
  1090.  *
  1091.  *      fn     - buffer where filename (just final element) is returned.
  1092.  *
  1093.  * Returns:
  1094.  *
  1095.  * TRUE - if file selected and exists (tested with OF_EXIST).
  1096.  *
  1097.  * FALSE - if dialog cancelled. If user selects a file that we cannot
  1098.  *           open, we complain and restart the dialog.
  1099.  *
  1100.  * Comments:
  1101.  *
  1102.  *           if TRUE is returned, the file will have been successfully opened,
  1103.  *           for reading and then closed again.
  1104.  *
  1105.  **************************************************************************/
  1106.  
  1107. BOOL FAR PASCAL
  1108. complist_open(LPSTR prompt, LPSTR ext, LPSTR spec, OFSTRUCT FAR *osp, LPSTR fn)
  1109. {
  1110.     OPENFILENAME ofn;
  1111.     char achFilters[256];
  1112.     char achPath[256];
  1113.     LPSTR chp;
  1114.     int fh;
  1115.  
  1116.     /* build filter-pair buffer to contain one pair - the spec filter,
  1117.      * twice (one of the pair should be the filter, the second should be
  1118.      * the title of the filter - we don't have a title so we use the
  1119.      * filter both times. remember double null at end of list of strings.
  1120.      */
  1121.     lstrcpy(achFilters, spec);             // filter + null
  1122.     chp = &achFilters[lstrlen(achFilters)+1];      //2nd string just after null
  1123.     lstrcpy(chp, spec);                    // filter name (+null)
  1124.     chp[lstrlen(chp)+1] = '\0';            // double null at end of list
  1125.     /*
  1126.      * initialise arguments to dialog proc
  1127.      */
  1128.     ofn.lStructSize = sizeof(OPENFILENAME);
  1129.     ofn.hwndOwner = NULL;
  1130.     ofn.hInstance = NULL;
  1131.     ofn.lpstrFilter = achFilters;
  1132.     ofn.lpstrCustomFilter = (LPSTR)NULL;
  1133.     ofn.nMaxCustFilter = 0L;
  1134.     ofn.nFilterIndex = 1L;              // first filter pair in list
  1135.     achPath[0] = '\0';
  1136.     ofn.lpstrFile = achPath;            // we need to get the full path to open
  1137.     ofn.nMaxFile = sizeof(achPath);
  1138.     ofn.lpstrFileTitle = fn;            // return final elem of name here
  1139.     ofn.nMaxFileTitle = sizeof(fn);
  1140.     ofn.lpstrInitialDir = NULL;
  1141.     ofn.lpstrTitle = prompt;            // dialog title is good place for prompt text
  1142.     ofn.Flags = OFN_FILEMUSTEXIST |
  1143.                 OFN_HIDEREADONLY |
  1144.                 OFN_PATHMUSTEXIST;
  1145.     ofn.lpstrDefExt = ext;
  1146.     ofn.nFileOffset = 0;
  1147.     ofn.nFileExtension = 0;
  1148.     ofn.lCustData = 0;
  1149.  
  1150.     /*
  1151.      * loop until the user cancels, or selects a file that we can open
  1152.      */
  1153.     do {
  1154.         if (!GetOpenFileName(&ofn)) {
  1155.             return(FALSE);
  1156.         }
  1157.  
  1158.         fh = OpenFile(achPath, osp, OF_READ);
  1159.         
  1160.         if (fh == HFILE_ERROR) {
  1161.             if (MessageBox(NULL, LoadRcString(IDS_COULDNT_BE_OPENED), LoadRcString2(IDS_FILEOPEN),
  1162.                             MB_OKCANCEL|MB_ICONSTOP) == IDCANCEL) {
  1163.                 return(FALSE);
  1164.             }
  1165.         }
  1166.     } while (fh == HFILE_ERROR);
  1167.  
  1168.     _lclose(fh);
  1169.  
  1170.     return(TRUE);
  1171. }
  1172.  
  1173. /***************************************************************************
  1174.  * Function: complist_getroot_left
  1175.  *
  1176.  * Purpose:
  1177.  *
  1178.  * Gets the root names of the left tree used to build this complist.
  1179.  *
  1180.  **************************************************************************/
  1181. LPSTR
  1182. complist_getroot_left(COMPLIST cl)
  1183. {
  1184.         return( dir_getroot_list(cl->left));
  1185. }
  1186.  
  1187. /***************************************************************************
  1188.  * Function: complist_getroot_right
  1189.  *
  1190.  * Purpose:
  1191.  *
  1192.  * Gets the root names of the right tree used to build this complist.
  1193.  *
  1194.  **************************************************************************/
  1195. LPSTR
  1196. complist_getroot_right(COMPLIST cl)
  1197. {
  1198.         return( dir_getroot_list(cl->right));
  1199. }
  1200. /***************************************************************************
  1201.  * Function: complist_freeroot_*
  1202.  *
  1203.  * Purpose:
  1204.  *
  1205.  * Frees up memory allocated in a call to complist_getroot*() 
  1206.  *
  1207.  **************************************************************************/
  1208. void
  1209. complist_freeroot_left(COMPLIST cl, LPSTR path)
  1210. {
  1211.         dir_freeroot_list(cl->left, path);
  1212. }
  1213.  
  1214. void
  1215. complist_freeroot_right(COMPLIST cl, LPSTR path)
  1216. {
  1217.         dir_freeroot_list(cl->right, path);
  1218. }
  1219.  
  1220.  
  1221.