COMPLIST.C
/******************************************************************************\ 
*       This is a part of the Microsoft Source Code Samples.  
*       Copyright 1993 - 1998 Microsoft Corporation. 
*       All rights reserved.  
*       This source code is only intended as a supplement to  
*       Microsoft Development Tools and/or WinHelp documentation. 
*       See these sources for detailed information regarding the  
*       Microsoft samples programs. 
\******************************************************************************/ 
 
/****************************** Module Header ******************************* 
* Module Name: COMPLIST.C 
* 
* Supports a list of compitems, where each compitem represents 
* a pair of matching files, or an unmatched file. 
* 
* Functions: 
* 
* complist_filedialog() 
* complist_dirdialog() 
* complist_args() 
* complist_getitems() 
* complist_delete() 
* complist_savelist() 
* complist_copyfiles() 
* complist_dodlg_savelist() 
* complist_dodlg_copyfiles() 
* complist_match() 
* complist_new() 
* complist_dodlg_dir() 
* 
* Comments: 
* 
* We build lists of filenames from two pathnames (using the 
* scandir module) and then traverse the two lists comparing names. 
* Where the names match, we create a CompItem from the matching 
* names. Where there is an unmatched name, we create a compitem for it. 
* 
* We may also be asked to create a complist for two individual files: 
* here we create a single compitem for them as a matched pair even if 
* the names don't match. 
* 
****************************************************************************/ 
 
#include <windows.h> 
#include <stdlib.h> 
#include <string.h> 
#include <dos.h> 
#include <direct.h> 
 
#include "gutils.h" 
#include "state.h" 
#include "windiff.h" 
#include "wdiffrc.h" 
#include "list.h" 
#include "line.h" 
#include "scandir.h" 
#include "file.h" 
#include "section.h" 
#include "compitem.h" 
#include "complist.h" 
#include "view.h" 
 
 
extern BOOL bAbort;             /* defined in windiff.c  Read only here */ 
 
/* 
 * The COMPLIST handle is typedef-ed to be a pointer to one 
 * of these struct complist 
 */ 
struct complist { 
        DIRLIST left;           /* left list of files */ 
        DIRLIST right;          /* right list of files */ 
        LIST items;             /* list of COMPITEMs */ 
}; 
 
/* ---- module-wide data -------------------------------------*/ 
 
/* data for communicating between the SaveList dlg and complist_savelist() */ 
 
char dlg_file[256];                /* filename to save to */ 
 
/* checkbox options */ 
BOOL dlg_identical, dlg_differ, dlg_left, dlg_right; 
BOOL dlg_recursive = FALSE; 
 
/* data for Directory and SaveList */ 
char dialog_leftname[256]; 
char dialog_rightname[256]; 
 
/* 
 * data used by dodlg_copyfiles 
 */ 
UINT dlg_options; 
char dlg_root[256]; 
 
/*------------------------timing for performance measurements-----------------*/ 
 
static DWORD TickCount;         /* time operation started, then time taken*/ 
 
 
int FAR PASCAL complist_dodlg_savelist(HWND hDlg, UINT message, 
        UINT wParam, long lParam); 
int FAR PASCAL complist_dodlg_copyfiles(HWND hDlg, UINT message, 
        UINT wParam, long lParam); 
BOOL complist_match(COMPLIST cl, VIEW view, BOOL fDeep, BOOL fExact); 
COMPLIST complist_new(void); 
int FAR PASCAL complist_dodlg_dir(HWND hDlg, unsigned message, 
        WORD wParam, LONG lParam); 
 
 
 
/*************************************************************************** 
 * Function: complist_filedialog 
 * 
 * Purpose: 
 * 
 * Builds a complist by putting up two dialogs to allow the user to 
 * select two files. This will build a Complist with one CompItem (even 
 * if the names don't match). 
 * 
 ***************************************************************************/ 
COMPLIST 
complist_filedialog(VIEW view) 
{ 
        COMPLIST cl; 
        OFSTRUCT os1, os2; 
        char fname[256], FileExt[256], FileOpenSpec[256]; 
 
        /* ask for the filenames */ 
        lstrcpy(FileExt, ".c"); 
        lstrcpy(FileOpenSpec, "*.*"); 
        lstrcpy(fname,""); 
 
        if (!complist_open(LoadRcString(IDS_SELECT_FIRST_FILE), FileExt, FileOpenSpec, 
                        &os1, fname) ) 
                return(NULL); 
 
        lstrcpy(FileExt, ".c"); 
        lstrcpy(FileOpenSpec, "*.*"); 
        lstrcpy(fname,""); 
 
        if (!complist_open(LoadRcString(IDS_SELECT_SECOND_FILE), FileExt, FileOpenSpec, 
                        &os2, fname) ) 
                return(NULL); 
 
        /* alloc a new structure */ 
        cl = complist_new(); 
 
        cl->left = dir_buildlist(os1.szPathName, TRUE); 
        cl->right = dir_buildlist(os2.szPathName, TRUE); 
 
 
        /* register with the view (must be done after the list is non-null) */ 
        view_setcomplist(view, cl); 
 
        complist_match(cl, view, FALSE, TRUE); 
 
        return(cl); 
}/* complist_filedialog */ 
 
/*************************************************************************** 
 * Function: complist_dirdialog 
 * 
 * Purpose: 
 * 
 * Builds a new complist by querying the user for two directory 
 * names and scanning those in parallel. 
 * 
 * Names that match in the same directory will be paired - unmatched 
 * names will go in a compitem on their own. 
 * 
 ***************************************************************************/ 
COMPLIST 
complist_dirdialog(VIEW view) 
{ 
        DLGPROC lpProc; 
        BOOL fOK; 
 
        /* put up a dialog for the two pathnames */ 
        lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_dir, hInst); 
        windiff_UI(TRUE); 
        fOK = DialogBox(hInst, "Directory", hwndClient, lpProc); 
        windiff_UI(FALSE); 
        FreeProcInstance(lpProc); 
 
        if (!fOK) { 
                return(NULL); 
        } 
 
        return complist_args( dialog_leftname, dialog_rightname 
                            , view, dlg_recursive); 
} /* complist_dirdialog */ 
 
 
/*************************************************************************** 
 * Function: complist_args 
 * 
 * Purpose: 
 * 
 * Given two pathname strings, scan the directories and traverse them 
 * in parallel comparing matching names. 
 * 
 ***************************************************************************/ 
COMPLIST 
complist_args(LPSTR p1, LPSTR p2, VIEW view, BOOL fDeep) 
{ 
        COMPLIST cl; 
        char msg[256]; 
 
 
        /* alloc a new complist */ 
        cl = complist_new(); 
 
        cl->left = dir_buildlist(p1, TRUE); 
        /* check that we could find the paths, and report if not */ 
        if (cl->left == NULL) { 
                wsprintf((LPTSTR)msg, LoadRcString(IDS_COULDNT_FIND), p1); 
                MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP); 
                return(NULL); 
        } 
 
        cl->right = dir_buildlist(p2, TRUE); 
        if (cl->right == NULL) { 
                wsprintf((LPTSTR)msg, LoadRcString(IDS_COULDNT_FIND), p2); 
                MessageBox(NULL, msg, NULL, MB_OK | MB_ICONSTOP); 
                return(NULL); 
        } 
 
        /* register with the view (must be done after building lists) */ 
        view_setcomplist(view, cl); 
 
        complist_match(cl, view, fDeep, TRUE); 
 
        return(cl); 
} /* complist_args */ 
 
/*************************************************************************** 
 * Function: complist_getitems 
 * 
 * Purpose: 
 * 
 * Gets the handle to the list of COMPITEMs. The list continues to be 
 * owned by the COMPLIST, so don't delete except by calling complist_delete. 
 * 
 ***************************************************************************/ 
LIST 
complist_getitems(COMPLIST cl) 
{ 
        if (cl == NULL) { 
                return(NULL); 
        } 
 
        return(cl->items); 
} 
 
/*************************************************************************** 
 * Function: complist_delete 
 * 
 * Purpose: 
 * 
 * Deletes a complist and all associated CompItems and DIRLISTs. Note this 
 * does not delete any VIEW - the VIEW owns the COMPLIST and not the other 
 * way around. 
 * 
 **************************************************************************/ 
void 
complist_delete(COMPLIST cl) 
{ 
        COMPITEM item; 
 
        if (cl == NULL) { 
                return; 
        } 
 
        /* delete the two directory scan lists */ 
        dir_delete(cl->left); 
        dir_delete(cl->right); 
 
        /* delete the compitems in the list */ 
        List_TRAVERSE(cl->items, item) { 
                        compitem_delete(item); 
        } 
 
        /* delete the list itself */ 
        List_Destroy(&cl->items); 
 
        gmem_free(hHeap, (LPSTR) cl, sizeof(struct complist)); 
 
} 
 
/*************************************************************************** 
 * Function: complist_savelist 
 * 
 * Purpose: 
 * 
 * Writes out to a text file the list of compitems as relative filenames 
 * one per line. 
 * 
 * If savename is non-null, use this as the filename for output; otherwise, 
 * query the user via a dialog for the filename and include options. 
 * 
 **************************************************************************/ 
void 
complist_savelist(COMPLIST cl, LPSTR savename, UINT options) 
{ 
        DLGPROC lpProc; 
        static BOOL done_init = FALSE; 
        BOOL bOK; 
        int fh, state; 
        OFSTRUCT os; 
        char msg[256]; 
        HCURSOR hcurs; 
        COMPITEM ci; 
        LPSTR pstr, lhead, rhead; 
        int nFiles = 0; 
 
        if (!done_init) { 
                /* init the options once round - but keep the same options 
                 * for the rest of the session. 
                 */ 
 
                /* first init default options */ 
                dlg_identical = FALSE; 
                dlg_differ = TRUE; 
                dlg_left = TRUE; 
                dlg_right = FALSE; 
 
                dlg_file[0] = '\0'; 
 
                done_init = TRUE; 
        } 
 
        if (cl == NULL) { 
                return; 
        } 
 
        if (savename == NULL) { 
 
                /* store the left and right rootnames so that dodlg_savelist 
                 * can display them in the dialog. 
                 */ 
                pstr = dir_getroot_list(cl->left); 
                lstrcpy(dialog_leftname, pstr); 
                dir_freeroot_list(cl->left, pstr); 
 
                pstr = dir_getroot_list(cl->right); 
                lstrcpy(dialog_rightname, pstr); 
                dir_freeroot_list(cl->right, pstr); 
 
                lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_savelist, hInst); 
                windiff_UI(TRUE); 
                bOK = DialogBox(hInst, "SaveList", hwndClient, lpProc); 
                windiff_UI(FALSE); 
                FreeProcInstance(lpProc); 
 
                if (!bOK) { 
                        /* user cancelled from dialog box */ 
                        return; 
                } 
                savename = dlg_file; 
 
        } else { 
                dlg_identical = (options & INCLUDE_SAME); 
                dlg_differ = (options & INCLUDE_DIFFER); 
                dlg_left = (options & INCLUDE_LEFTONLY); 
                dlg_right = (options & INCLUDE_RIGHTONLY); 
        } 
 
 
        /* try to open the file */ 
        fh = OpenFile(savename, &os, OF_CREATE|OF_READWRITE|OF_SHARE_DENY_WRITE); 
        if (fh < 0) { 
                wsprintf((LPTSTR)msg, LoadRcString(IDS_CANT_OPEN), savename); 
                windiff_UI(TRUE); 
                MessageBox(NULL, msg, "Windiff", MB_ICONSTOP|MB_OK); 
                windiff_UI(FALSE); 
                return; 
        } 
 
        hcurs = SetCursor(LoadCursor(NULL, IDC_WAIT)); 
 
        /* write out the header line */ 
        lhead = dir_getroot_list(cl->left); 
        rhead = dir_getroot_list(cl->right); 
{ 
        TCHAR szBuf1[20],szBuf2[20],szBuf3[20],szBuf4[20]; 
        lstrcpy(szBuf1,(LPSTR)(dlg_identical ? LoadRcString(IDS_IDENTICAL_COMMA) : "")); 
        lstrcpy(szBuf2,(LPSTR)(dlg_left ? LoadRcString(IDS_LEFT_ONLY_COMMA) : "")); 
        lstrcpy(szBuf3,(LPSTR)(dlg_right ? LoadRcString(IDS_RIGHT_ONLY_COMMA) : "")); 
        lstrcpy(szBuf4,(LPSTR)(dlg_differ ? LoadRcString(IDS_DIFFERING) : "")); 
        wsprintf(msg, LoadRcString(IDS_HEADER_LINE_STR), 
                lhead, rhead, szBuf1, szBuf2, szBuf3, szBuf4); 
} 
        _lwrite(fh, msg, lstrlen(msg)); 
        dir_freeroot_list(cl->left, lhead); 
        dir_freeroot_list(cl->right, rhead); 
 
 
        /* traverse the list of compitems looking for the 
         * ones we are supposed to include 
         */ 
        List_TRAVERSE(cl->items, ci) { 
 
                /* check if files of this type are to be listed */ 
                state = compitem_getstate(ci); 
 
                if ((state == STATE_SAME) && (!dlg_identical)) { 
                        continue; 
                } else if ((state == STATE_DIFFER) && (!dlg_differ)) { 
                        continue; 
                } else if ((state == STATE_FILELEFTONLY) && (!dlg_left)) { 
                        continue; 
                } else if ((state == STATE_FILERIGHTONLY) && (!dlg_right)) { 
                        continue; 
                } 
 
                nFiles++; 
 
                /* output the list line */ 
                wsprintf((LPTSTR)msg, "%s\r\n", compitem_gettext_tag(ci)); 
                _lwrite(fh, msg, lstrlen(msg)); 
        } 
 
        /* write tail line */ 
        wsprintf((LPTSTR)msg, LoadRcString(IDS_FILES_LISTED), nFiles); 
        _lwrite(fh, msg, lstrlen(msg)); 
 
        /* - close file and we are finished */ 
        _lclose(fh); 
 
        SetCursor(hcurs); 
} /* complist_savelist */ 
 
/*************************************************************************** 
 * Function: complist_copyfiles 
 * 
 * Purpose: 
 * 
 * To copy files to a new directory newroot. if newroot is NULL, query the user 
 * via a dialog to get the new dir name and options. 
 * 
 * Options are either COPY_FROMLEFT or COPY_FROMRIGHT (indicating which 
 * tree is to be the source of the files, plus any or all of 
 * INCLUDE_SAME, INCLUDE_DIFFER and INCLUDE_LEFT (INCLUDE_LEFT 
 * and INCLUDE_RIGHT are treated the same here since the COPY_FROM* option 
 * indicates which side to copy from). 
 * 
 ***************************************************************************/ 
void 
complist_copyfiles(COMPLIST cl, LPSTR newroot, UINT options) 
{ 
        int nFiles = 0; 
        int nFails = 0; 
        static BOOL done_init = FALSE; 
        LPSTR pstr; 
        char buffer[64]; 
        DIRITEM diritem; 
        DLGPROC lpProc; 
        BOOL bOK; 
        COMPITEM ci; 
        int state; 
 
        if (!done_init) { 
                /* 
                 * one-time initialisation of dialog defaults 
                 */ 
                dlg_options = COPY_FROMLEFT|INCLUDE_LEFTONLY|INCLUDE_DIFFER; 
                dlg_root[0] = '\0'; 
                done_init = TRUE; 
        } 
 
        if (cl == NULL) { 
                return; 
        } 
 
 
        if (newroot == NULL) { 
                /* 
                 * put up dialog to query rootname and options 
                 */ 
 
                /* store the left and right rootnames so that the dlg proc 
                 * can display them in the dialog. 
                 */ 
                pstr = dir_getroot_list(cl->left); 
                lstrcpy(dialog_leftname, pstr); 
                dir_freeroot_list(cl->left, pstr); 
 
                pstr = dir_getroot_list(cl->right); 
                lstrcpy(dialog_rightname, pstr); 
                dir_freeroot_list(cl->right, pstr); 
 
                do { 
                        lpProc = (DLGPROC)MakeProcInstance((WNDPROC)complist_dodlg_copyfiles, hInst); 
                        windiff_UI(TRUE); 
                        bOK = DialogBox(hInst, "CopyFiles", hwndClient, lpProc); 
                        windiff_UI(FALSE); 
                        FreeProcInstance(lpProc); 
 
                        if (!bOK) { 
                                /* user cancelled from dialog box */ 
                                return; 
                        } 
                        if (lstrlen(dlg_root) == 0) { 
                                windiff_UI(TRUE); 
                                MessageBox(NULL, LoadRcString(IDS_ENTER_DIR_NAME), 
                                                "Windiff", MB_ICONSTOP|MB_OK); 
                                windiff_UI(FALSE); 
                        } 
                } while (lstrlen(dlg_root) == 0); 
 
        } else { 
                dlg_options = options; 
                lstrcpy(dlg_root, newroot); 
        } 
 
        TickCount = GetTickCount(); 
 
        if (dlg_options & COPY_FROMLEFT) { 
                if (!dir_startcopy(cl->left)) 
                        return; 
        } else { 
                if (!dir_startcopy(cl->right)) 
                        return; 
        } 
 
        /* 
         * traverse the list of compitems copying files as necessary 
         */ 
        List_TRAVERSE(cl->items, ci) { 
 
                if (bAbort){ 
                        break;  /* fall into end_copy processing */ 
                } 
                /* check if files of this type are to be copied */ 
                state = compitem_getstate(ci); 
 
                if ((state == STATE_SAME) && !(dlg_options & INCLUDE_SAME)) { 
                        continue; 
                } else if ((state == STATE_DIFFER) && !(dlg_options & INCLUDE_DIFFER)) { 
                        continue; 
                } else if (state == STATE_FILELEFTONLY) { 
                        if (dlg_options & COPY_FROMRIGHT) { 
                                continue; 
                        } 
                        if ((dlg_options & (INCLUDE_LEFTONLY | INCLUDE_RIGHTONLY)) == 0) { 
                                continue; 
                        } 
                } else if (state == STATE_FILERIGHTONLY) { 
                        if (dlg_options & COPY_FROMLEFT) { 
                                continue; 
                        } 
                        if ((dlg_options & (INCLUDE_LEFTONLY | INCLUDE_RIGHTONLY)) == 0) { 
                                continue; 
                        } 
                } 
 
                if (dlg_options & COPY_FROMLEFT) { 
                        diritem = file_getdiritem(compitem_getleftfile(ci)); 
                } else { 
                        diritem = file_getdiritem(compitem_getrightfile(ci)); 
                } 
 
                /* 
                 * copy the file to the new root directory 
                 */ 
                if (dir_copy(diritem, dlg_root) == FALSE) { 
                        nFails++; 
                        pstr = dir_getrelname(diritem); 
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FAILED_TO_COPY), pstr); 
                        dir_freerelname(diritem, pstr); 
 
                        if (MessageBox(NULL, buffer, NULL, MB_OKCANCEL | MB_ICONSTOP) == IDCANCEL) 
                            /* user pressed cancel - abort current operation*/ 
                            /* fall through to end-copy processing */ 
                            break; 
 
                } else { 
                        nFiles++; 
                } 
 
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPYING), nFiles); 
                SetStatus(buffer); 
 
 
                /* 
                 * allow user interface to continue 
                 */ 
                if (Poll()) { 
                        /* abort requested */ 
                        TickCount = GetTickCount()-TickCount; 
                        windiff_UI(TRUE); 
                        MessageBox(hwndClient, LoadRcString(IDS_COPY_ABORTED), 
                                "WinDiff", MB_OK|MB_ICONINFORMATION); 
                        windiff_UI(FALSE); 
                        break; 
                } 
 
        } /* traverse */ 
        if (dlg_options & COPY_FROMLEFT) { 
                nFails = dir_endcopy(cl->left); 
        } else { 
                nFails = dir_endcopy(cl->right); 
        } 
        TickCount = GetTickCount()-TickCount; 
 
        if (nFails<0) { 
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPY_FAILED), -nFails); 
        } else { 
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_COPY_COMPLETE), nFails); 
        } 
        windiff_UI(TRUE); 
        MessageBox(hwndClient, buffer, "WinDiff", MB_OK|MB_ICONINFORMATION); 
        windiff_UI(FALSE); 
 
        buffer[0] = '\0'; 
        SetStatus(buffer); 
} /* complist_copyfiles */ 
 
 
/*************************************************************************** 
 * Function: complist_match 
 * 
 * Purpose: 
 * 
 * Matches up two lists of filenames 
 * 
 * Commentsz: 
 * 
 * We can find out from the DIRLIST handle whether the original list 
 * was a file or a directory name. 
 * If the user typed: 
 *      two file names  - match these two item even if the names differ 
 * 
 *      two dirs        - match only those items whose names match 
 * 
 *      one file and one dir 
 *                      - try to find a file of that name in the dir. 
 * 
 * This function returns TRUE if the complist_match was ok, or FALSE if it was 
 * aborted in some way. 
 * 
 ***************************************************************************/ 
BOOL 
complist_match(COMPLIST cl, VIEW view, BOOL fDeep, BOOL fExact) 
{ 
        LPSTR lname; 
        LPSTR rname; 
        DIRITEM leftitem, rightitem; 
        int cmpvalue; 
 
        TickCount = GetTickCount(); 
 
        if (dir_isfile(cl->left) ) { 
 
                if (dir_isfile(cl->right)) { 
                        /* two files */ 
 
                        /* there should be one item in each list - make 
                         * a compitem by matching these two and append it to the 
                         * list 
                         */ 
                        compitem_new(dir_firstitem(cl->left), 
                                       dir_firstitem(cl->right), cl->items, fExact); 
 
                        view_newitem(view); 
 
                        TickCount = GetTickCount() - TickCount; 
                        return TRUE; 
                } 
                /* left is file, right is dir */ 
                leftitem = dir_firstitem(cl->left); 
                rightitem = dir_firstitem(cl->right); 
                lname = dir_getrelname(leftitem); 
                while (rightitem != NULL) { 
                        rname = dir_getrelname(rightitem); 
                        cmpvalue = lstrcmpi(lname, rname); 
                        dir_freerelname(rightitem, rname); 
 
                        if (cmpvalue == 0) { 
                                /* this is the match */ 
                                compitem_new(leftitem, rightitem, cl->items, fExact); 
                                view_newitem(view); 
 
                                dir_freerelname(leftitem, lname); 
 
                                TickCount = GetTickCount() - TickCount; 
                                return(TRUE); 
                        } 
 
                        rightitem = dir_nextitem(cl->right, rightitem, fDeep); 
                } 
                /* not found */ 
                dir_freerelname(leftitem, lname); 
                compitem_new(leftitem, NULL, cl->items, fExact); 
                view_newitem(view); 
                TickCount = GetTickCount() - TickCount; 
                return(TRUE); 
 
        } else if (dir_isfile(cl->right)) { 
 
                /* left is dir, right is file */ 
 
                /* loop through the left dir, looking for 
                 * a file that has the same name as rightitem 
                 */ 
 
                leftitem = dir_firstitem(cl->left); 
                rightitem = dir_firstitem(cl->right); 
                rname = dir_getrelname(rightitem); 
                while (leftitem != NULL) { 
                        lname = dir_getrelname(leftitem); 
                        cmpvalue = lstrcmpi(lname, rname); 
                        dir_freerelname(leftitem, lname); 
 
                        if (cmpvalue == 0) { 
                                /* this is the match */ 
                                compitem_new(leftitem, rightitem, cl->items, fExact); 
                                view_newitem(view); 
 
                                dir_freerelname(rightitem, rname); 
 
                                TickCount = GetTickCount() - TickCount; 
                                return(TRUE); 
                        } 
 
                        leftitem = dir_nextitem(cl->left, leftitem, fDeep); 
                } 
                /* not found */ 
                dir_freerelname(rightitem, rname); 
                compitem_new(NULL, rightitem, cl->items, fExact); 
                view_newitem(view); 
                TickCount = GetTickCount() - TickCount; 
                return(TRUE); 
        } 
 
        /* two directories */ 
 
        /* traverse the two lists in parallel comparing the relative names*/ 
 
        leftitem = dir_firstitem(cl->left); 
        rightitem = dir_firstitem(cl->right); 
        while ((leftitem != NULL) && (rightitem != NULL)) { 
 
                lname = dir_getrelname(leftitem); 
                rname = dir_getrelname(rightitem); 
                cmpvalue = utils_CompPath(lname, rname); 
                dir_freerelname(leftitem, lname); 
                dir_freerelname(rightitem, rname); 
 
                if (cmpvalue == 0) { 
                        compitem_new(leftitem, rightitem, cl->items, fExact); 
                        if (view_newitem(view)) { 
                                TickCount = GetTickCount() - TickCount; 
                                return(FALSE); 
                        } 
                        leftitem = dir_nextitem(cl->left, leftitem, fDeep); 
                        rightitem = dir_nextitem(cl->right, rightitem, fDeep); 
 
                } else if (cmpvalue < 0) { 
                        compitem_new(leftitem, NULL, cl->items, fExact); 
                        if (view_newitem(view)) { 
                                TickCount = GetTickCount() - TickCount; 
                                return(FALSE); 
                        } 
                        leftitem = dir_nextitem(cl->left, leftitem, fDeep); 
                }  else { 
                        compitem_new(NULL, rightitem, cl->items, fExact); 
                        if (view_newitem(view)) { 
                                TickCount = GetTickCount() - TickCount; 
                                return(FALSE); 
                        } 
                        rightitem = dir_nextitem(cl->right, rightitem, fDeep); 
                } 
        } 
 
 
        /* any left over are unmatched */ 
        while (leftitem != NULL) { 
                compitem_new(leftitem, NULL, cl->items, fExact); 
                if (view_newitem(view)) { 
                        TickCount = GetTickCount() - TickCount; 
                        return(FALSE); 
                } 
                leftitem = dir_nextitem(cl->left, leftitem, fDeep); 
        } 
        while (rightitem != NULL) { 
                compitem_new(NULL, rightitem, cl->items, fExact); 
                if (view_newitem(view)) { 
                        TickCount = GetTickCount() - TickCount; 
                        return(FALSE); 
                } 
                rightitem = dir_nextitem(cl->right, rightitem, fDeep); 
        } 
        TickCount = GetTickCount() - TickCount; 
        return(TRUE); 
} /* complist_match */ 
 
/* return time last operation took in milliseconds */ 
DWORD complist_querytime(void) 
{       return TickCount; 
} 
 
 
/*************************************************************************** 
 * Function: complist_dodlg_savelist 
 * 
 * Purpose: 
 * 
 * Dialog to query about filename and types of files. Init dlg fields from 
 * the dlg_* variables, and save state to the dlg_* variables on dialog 
 * close. return TRUE for OK, or FALSE for cancel (from the dialogbox() 
 * using EndDialog). 
 * 
 **************************************************************************/ 
int FAR PASCAL 
complist_dodlg_savelist(HWND hDlg, UINT message, UINT wParam, long lParam) 
{ 
        static char buffer[256]; 
 
        switch(message) { 
 
 
        case WM_INITDIALOG: 
                SendDlgItemMessage(hDlg, IDD_IDENTICAL, BM_SETCHECK, 
                        dlg_identical ? 1 : 0, 0); 
                SendDlgItemMessage(hDlg, IDD_DIFFER, BM_SETCHECK, 
                        dlg_differ ? 1 : 0, 0); 
                SendDlgItemMessage(hDlg, IDD_LEFT, BM_SETCHECK, 
                        dlg_left ? 1 : 0, 0); 
                SendDlgItemMessage(hDlg, IDD_RIGHT, BM_SETCHECK, 
                        dlg_right ? 1 : 0, 0); 
 
                SetDlgItemText(hDlg, IDD_FILE, dlg_file); 
 
                /* convert 'left tree' into the right name */ 
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname); 
                SendDlgItemMessage(hDlg, IDD_LEFT, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer); 
/* convert 'right tree' msg into correct path */ 
                wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname); 
                SendDlgItemMessage(hDlg, IDD_RIGHT, WM_SETTEXT, 0, (DWORD) (LPSTR) buffer); 
 
 
                return(TRUE); 
 
        case WM_COMMAND: 
                switch (GET_WM_COMMAND_ID(wParam, lParam)) { 
 
                case IDOK: 
                        dlg_identical = (SendDlgItemMessage(hDlg, IDD_IDENTICAL, 
                                        BM_GETCHECK, 0, 0) == 1); 
                        dlg_differ = (SendDlgItemMessage(hDlg, IDD_DIFFER, 
                                        BM_GETCHECK, 0, 0) == 1); 
                        dlg_left = (SendDlgItemMessage(hDlg, IDD_LEFT, 
                                        BM_GETCHECK, 0, 0) == 1); 
                        dlg_right = (SendDlgItemMessage(hDlg, IDD_RIGHT, 
                                        BM_GETCHECK, 0, 0) == 1); 
                        GetDlgItemText(hDlg, IDD_FILE, dlg_file, sizeof(dlg_file)); 
 
                        EndDialog(hDlg, TRUE); 
                        break; 
 
                case IDCANCEL: 
                        EndDialog(hDlg, FALSE); 
                        break; 
                } 
        } 
        return(FALSE); 
} /* complist_dodlg_savelist */ 
 
/*************************************************************************** 
 * Function: complist_dodlg_copyfiles 
 * 
 * Purpose: 
 * 
 * dialog to get directory name and inclusion options. Init dlg fields from 
 * the dlg_* variables, and save state to the dlg_* variables on dialog 
 * close. return TRUE for OK, or FALSE for cancel (from the dialogbox() 
 * using EndDialog). 
 *  
 **************************************************************************/ 
int FAR PASCAL 
complist_dodlg_copyfiles(HWND hDlg, UINT message, UINT wParam, long lParam) 
{ 
        static char buffer[256]; 
 
        switch(message) { 
 
 
        case WM_INITDIALOG: 
                /* 
                 * set checkboxes and directory field to defaults 
                 */ 
                CheckDlgButton(hDlg, IDD_IDENTICAL, 
                        (dlg_options & INCLUDE_SAME) ? 1 : 0); 
 
                CheckDlgButton(hDlg, IDD_DIFFER, 
                        (dlg_options & INCLUDE_DIFFER) ? 1 : 0); 
 
                CheckDlgButton(hDlg, IDD_LEFT, 
                        (dlg_options & (INCLUDE_LEFTONLY|INCLUDE_RIGHTONLY)) ? 1 : 0); 
 
                SetDlgItemText(hDlg, IDD_DIR1, dlg_root); 
 
                /* 
                 * set 'copy from' buttons to have the full pathname 
                 */ 
                SetDlgItemText(hDlg, IDD_FROMLEFT, dialog_leftname); 
                SetDlgItemText(hDlg, IDD_FROMRIGHT, dialog_rightname); 
 
                /* 
                 * set default radio button for copy from, and set 
                 * the text on the 'files only in...' checkbox to 
                 * indicate which path is being selected 
                 */ 
                if (dlg_options & COPY_FROMLEFT) { 
                        CheckRadioButton(hDlg, IDD_FROMLEFT, IDD_FROMRIGHT, 
                                        IDD_FROMLEFT); 
 
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname); 
                        SetDlgItemText(hDlg, IDD_LEFT, buffer); 
                } else { 
                        CheckRadioButton(hDlg, IDD_FROMLEFT, IDD_FROMRIGHT, 
                                        IDD_FROMRIGHT); 
 
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname); 
                        SetDlgItemText(hDlg, IDD_LEFT, buffer); 
                } 
 
                return(TRUE); 
 
        case WM_COMMAND: 
                switch (GET_WM_COMMAND_ID(wParam, lParam)) { 
 
                case IDD_FROMLEFT: 
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_leftname); 
                        SetDlgItemText(hDlg, IDD_LEFT, buffer); 
 
                        dlg_options &= ~(COPY_FROMRIGHT); 
                        dlg_options |= COPY_FROMLEFT; 
                        break; 
 
                case IDD_FROMRIGHT: 
                        wsprintf((LPTSTR)buffer, LoadRcString(IDS_FILES_ONLY), (LPSTR) dialog_rightname); 
                        SetDlgItemText(hDlg, IDD_LEFT, buffer); 
 
                        dlg_options &= ~(COPY_FROMLEFT); 
                        dlg_options |= COPY_FROMRIGHT; 
                        break; 
 
                case IDOK: 
                        if (SendDlgItemMessage(hDlg, IDD_IDENTICAL, 
                            BM_GETCHECK, 0, 0) == 1) { 
                                dlg_options |= INCLUDE_SAME; 
                        } else { 
                                dlg_options &= ~INCLUDE_SAME; 
                        } 
                        if (SendDlgItemMessage(hDlg, IDD_DIFFER, 
                            BM_GETCHECK, 0, 0) == 1) { 
                                dlg_options |= INCLUDE_DIFFER; 
                        } else { 
                                dlg_options &= ~INCLUDE_DIFFER; 
                        } 
                        if (SendDlgItemMessage(hDlg, IDD_LEFT, 
                            BM_GETCHECK, 0, 0) == 1) { 
                                dlg_options |= INCLUDE_LEFTONLY; 
                        } else { 
                                dlg_options &= ~INCLUDE_LEFTONLY; 
                        } 
                        GetDlgItemText(hDlg, IDD_DIR1, dlg_root, sizeof(dlg_root)); 
 
                        EndDialog(hDlg, TRUE); 
                        break; 
 
                case IDCANCEL: 
                        EndDialog(hDlg, FALSE); 
                        break; 
                } 
        } 
        return(FALSE); 
} /* complist_dodlg_copyfiles */ 
 
/*************************************************************************** 
 * Function: complist_new 
 * 
 * Purpose: 
 * 
 * Allocates a new complist and initialise it  
 * 
 **************************************************************************/ 
COMPLIST 
complist_new(void) 
{ 
        COMPLIST cl; 
 
        cl = (COMPLIST) gmem_get(hHeap, sizeof(struct complist)); 
        cl->left = NULL; 
        cl->right = NULL; 
        cl->items = List_Create(); 
 
        return(cl); 
} /* complist_new */ 
 
/*************************************************************************** 
 * Function: complist_dodlg_dir 
 * 
 * Purpose: 
 * 
 * Dialog box function to ask for two directory names. 
 * no listing of files etc - just two edit fields  in which the 
 * user can type a file or a directory name. 
 * 
 * Initialises the names from win.ini, and stores them to win.ini first. 
 * 
 **************************************************************************/ 
int FAR PASCAL 
complist_dodlg_dir(HWND hDlg, unsigned message, WORD wParam, LONG lParam) 
{ 
        static char path[256]; 
        static char buffer[256]; 
 
        switch (message) { 
 
        case WM_INITDIALOG: 
 
                /* fill the edit fields with the current 
                 * directory as a good starting point 
                 */ 
                _getcwd(path, sizeof(path)); 
                AnsiLowerBuff(path, strlen(path)); 
                GetProfileString(APPNAME, "NameLeft", path, buffer, 256); 
                SetDlgItemText(hDlg, IDD_DIR1, buffer); 
                GetProfileString(APPNAME, "NameRight", path, buffer, 256); 
                SetDlgItemText(hDlg, IDD_DIR2, buffer); 
                /* set recursive option to most recent value */ 
                CheckDlgButton(hDlg, IDD_RECURSIVE, dlg_recursive); 
                return(TRUE); 
 
        case WM_COMMAND: 
                switch (LOWORD(wParam)) { 
                case IDCANCEL: 
                        EndDialog(hDlg, FALSE); 
                        return(TRUE); 
 
                case IDOK: 
                        /* fetch the text from the dialog, and remember 
                         * it in win.ini 
                         */ 
 
                        GetDlgItemText(hDlg, IDD_DIR1, 
                                dialog_leftname, sizeof(dialog_leftname)); 
                        WriteProfileString(APPNAME, "NameLeft", dialog_leftname); 
 
                        GetDlgItemText(hDlg, IDD_DIR2, 
                                dialog_rightname, sizeof(dialog_rightname)); 
                        WriteProfileString(APPNAME, "NameRight", dialog_rightname); 
 
                        /* fetch recursive option */ 
                        dlg_recursive = SendDlgItemMessage(hDlg, IDD_RECURSIVE, 
                                BM_GETCHECK, 0, 0); 
 
                        EndDialog(hDlg, TRUE); 
                        return(TRUE); 
                } 
                break; 
        } 
        return(FALSE); 
} /* complist_dodlg_dir */ 
 
/*************************************************************************** 
 * Function: complist_open 
 * 
 * Purpose: 
 *       
 * Puts up dialog asking the user to select an existing file to open. 
 * 
 * Parameters: 
 * 
 *      prompt - message to user indicating purpose of file 
 *               (to be displayed somewhere in dialog box. 
 * 
 *      ext    - default file extension if user enters file without 
 *               extension. 
 * 
 *      spec   - default file spec (eg *.*) 
 * 
 *      osp    - OFSTRUCT representing file, if successfully open. 
 * 
 *      fn     - buffer where filename (just final element) is returned. 
 * 
 * Returns: 
 * 
 * TRUE - if file selected and exists (tested with OF_EXIST). 
 * 
 * FALSE - if dialog cancelled. If user selects a file that we cannot 
 *           open, we complain and restart the dialog. 
 * 
 * Comments: 
 * 
 *           if TRUE is returned, the file will have been successfully opened, 
 *           for reading and then closed again. 
 * 
 **************************************************************************/ 
 
BOOL FAR PASCAL 
complist_open(LPSTR prompt, LPSTR ext, LPSTR spec, OFSTRUCT FAR *osp, LPSTR fn) 
{ 
    OPENFILENAME ofn; 
    char achFilters[256]; 
    char achPath[256]; 
    LPSTR chp; 
    int fh; 
 
    /* build filter-pair buffer to contain one pair - the spec filter, 
     * twice (one of the pair should be the filter, the second should be 
     * the title of the filter - we don't have a title so we use the 
     * filter both times. remember double null at end of list of strings. 
     */ 
    lstrcpy(achFilters, spec);             // filter + null 
    chp = &achFilters[lstrlen(achFilters)+1];      //2nd string just after null 
    lstrcpy(chp, spec);                    // filter name (+null) 
    chp[lstrlen(chp)+1] = '\0';            // double null at end of list 
    /* 
     * initialise arguments to dialog proc 
     */ 
    ofn.lStructSize = sizeof(OPENFILENAME); 
    ofn.hwndOwner = NULL; 
    ofn.hInstance = NULL; 
    ofn.lpstrFilter = achFilters; 
    ofn.lpstrCustomFilter = (LPSTR)NULL; 
    ofn.nMaxCustFilter = 0L; 
    ofn.nFilterIndex = 1L;              // first filter pair in list 
    achPath[0] = '\0'; 
    ofn.lpstrFile = achPath;            // we need to get the full path to open 
    ofn.nMaxFile = sizeof(achPath); 
    ofn.lpstrFileTitle = fn;            // return final elem of name here 
    ofn.nMaxFileTitle = sizeof(fn); 
    ofn.lpstrInitialDir = NULL; 
    ofn.lpstrTitle = prompt;            // dialog title is good place for prompt text 
    ofn.Flags = OFN_FILEMUSTEXIST | 
                OFN_HIDEREADONLY | 
                OFN_PATHMUSTEXIST; 
    ofn.lpstrDefExt = ext; 
    ofn.nFileOffset = 0; 
    ofn.nFileExtension = 0; 
    ofn.lCustData = 0; 
 
    /* 
     * loop until the user cancels, or selects a file that we can open 
     */ 
    do { 
        if (!GetOpenFileName(&ofn)) { 
            return(FALSE); 
        } 
 
        fh = OpenFile(achPath, osp, OF_READ); 
         
        if (fh == HFILE_ERROR) { 
            if (MessageBox(NULL, LoadRcString(IDS_COULDNT_BE_OPENED), LoadRcString2(IDS_FILEOPEN), 
                            MB_OKCANCEL|MB_ICONSTOP) == IDCANCEL) { 
                return(FALSE); 
            } 
        } 
    } while (fh == HFILE_ERROR); 
 
    _lclose(fh); 
 
    return(TRUE); 
} 
 
/*************************************************************************** 
 * Function: complist_getroot_left 
 * 
 * Purpose: 
 * 
 * Gets the root names of the left tree used to build this complist. 
 * 
 **************************************************************************/ 
LPSTR 
complist_getroot_left(COMPLIST cl) 
{ 
        return( dir_getroot_list(cl->left)); 
} 
 
/*************************************************************************** 
 * Function: complist_getroot_right 
 * 
 * Purpose: 
 * 
 * Gets the root names of the right tree used to build this complist. 
 * 
 **************************************************************************/ 
LPSTR 
complist_getroot_right(COMPLIST cl) 
{ 
        return( dir_getroot_list(cl->right)); 
} 
/*************************************************************************** 
 * Function: complist_freeroot_* 
 * 
 * Purpose: 
 * 
 * Frees up memory allocated in a call to complist_getroot*()  
 * 
 **************************************************************************/ 
void 
complist_freeroot_left(COMPLIST cl, LPSTR path) 
{ 
        dir_freeroot_list(cl->left, path); 
} 
 
void 
complist_freeroot_right(COMPLIST cl, LPSTR path) 
{ 
        dir_freeroot_list(cl->right, path); 
}