WINDIFF.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: WINDIFF.C 
* 
* File and directory comparisions. 
* 
* Functions: 
* 
* windiff_UI() 
* WinMain() 
* windiff_usage() 
* Poll() 
* DoResize() 
* AboutBox() 
* DoPrint() 
* FindNextChange() 
* FindPrevChange() 
* WriteProfileInt() 
* ToOutline() 
* ToMoved() 
* do_editfile() 
* do_editthread() 
* SetStatus() 
* SetNames() 
* IsBusy() 
* BusyError() 
* StateToColour() 
* SetSelection() 
* do_gethdr() 
* do_getprops() 
* do_getdata() 
* SvrClose() 
* TableServer() 
* wd_dirdialog() 
* wd_copy() 
* InitApplication() 
* InitInstance() 
* CreateTools() 
* DeleteTools() 
* MainWndProc() 
* SetBusy() 
* SetNotBusy() 
* SetSelection() 
* SetButtonText() 
* ToExpand() 
* ParseArgs() 
* wd_initial() 
* 
* Comments: 
* 
* Compare two directories (including all files and subdirs). Look for names 
* that are present in both (report all that are not). For files that 
* are present in both, produce a line-by-line comparison of the differences 
* between the two files (if any). 
* 
* Overview of Windiff internals - the whole program. 
* 
* Windiff is built from several modules (a "module" has a .h file 
* which describes its interface and a .c file which implements it). 
* Apart from THIS comment which tries to give an overview of the whole 
* scheme of things, each module is as self-contained as possible. 
* This is enforced by the use of opaque data types.  Modules cannot 
* see each others' internal data structures.  Modules are abstract 
* data types.  The term "Module" (from Modula2) and "Class" (from C++) 
* are used synonymously. 
* 
*    Windiff  - main program - parse arguments, put up main window, 
*               handle input, calling other modules as needed 
*               invoke table class to create the main display and 
*               service callbacks from the table class. 
*               Contains global flags for options (e.g. ignore_blanks) 
*    list     - (in gutils) a generalised LIST of anything data type 
*               has full set of operations for insert, delete, join etc. 
*    line     - a LINE is a numbered line of text.  Information is kept to 
*               allow fast comparisons of LINEs.  A LINE can hold a 
*               link to another LINE.  The links are used to connect 
*               lines in one file to matching lines in the other file. 
*    file     - a FILEDATA represents a file as a file name in the form 
*               of a DIRITEM and a LIST of LINEs 
*    scandir  - a DIRITEM represents information about a file.  (for 
*               instance its name, whether it has a local copy). 
*    compitem - a COMPITEM is a pair of files together with information 
*               on how they compare in the form of a breakdown of the 
*               files into a LIST of matching or non-matching sections. 
*               Either file can be absent.  This module contains the 
*               file "contrast" algorithm used for the actual comparison 
*    tree       (in gutils) A binary tree.  Important because it is what 
*               gives the file comparison its speed as it makes it 
*               an "N log N" algorithm rather than "N squared" 
*    complist - a COMPLIST is the master data structure.  It has a DIRLIST 
*               of the left hand files, a DIRLIST of the right hand files 
*               and a LIST of COMPITEMs. The left and right hand DIRLISTs 
*               are working data used to produce the COMPLIST.  The LIST 
*               is displayed as the outline table.  Any given COMPITEM can 
*               be displayed as an expanded item. 
*    section  - a SECTION is a section of a file (first line, last line) 
*               and information as to what it matches in the other file. 
*    bar.c    - the picture down the left of the screen 
*               has a WNDPROC.   
*    view     - Although the COMPLIST is the master state, it doesn't do 
*               all the work itself.  The data is actually displayed by 
*               the table class which is highly generalised.  View 
*               owns a COMPLIST (and therefore calls upon the functions 
*               in complist to fill it and interrogate it) and calls 
*               upon (and is called back by) the functions in table to 
*               actually display it.  Read about table in gutils.h 
*    table.c    (in gutils) a highly generalised system for displaying 
*               data in rows and columns.  The interface is in gutils.h. 
*    status.c   (in gutils) the status line at the top. See gutils.h 
************************************************************************* 
* 
* Overview of this file: 
* 
*   We create a table window (gutils.dll) to show the files and the 
*   results of their comparisons. We create a COMPLIST object representing 
*   a list of files and their differences, and a VIEW object to map between 
*   the rows of the table window and the COMPLIST. 
* 
*   This module is responsible for creating and managing the main window, 
*   placing the child windows (table, status window etc) within it, and 
*   handling all menu items. We maintain global option flags set by 
*   menu commands. 
* 
*   Creating a COMPLIST creates a list of unmatched files, and of matching 
*   files that are compared with each other (these are COMPITEMS). 
*   The VIEW provides a mapping between rows on the screen, and items in 
*   the COMPLIST. 
* 
*   This version tries to maintain a responsive user interface by 
*   creating worker threads to do long jobs.  This potentially creates 
*   conflicts between the threads as they will both want to update common 
*   variables (for instance the UI thread may be changing the options to 
*   exclude identical files while the worker thread is adding in the 
*   results of new comparisons).  Critical sections are used to manage 
*   the conflicts. 
* 
*   The Edit options invoke an editor on a separate thread.  This allows 
*   us to repaint our window and thereby allow the user to refer back to 
*   what he saw before invoking the editor.  When he's finished editing, 
*   we would of course like to refresh things and if this is still on the 
*   separate thread it might clash. We avoid this clash by POSTing ourselves 
*   a (WM_COMMAND, IDM_UPDATE) message. 
* 
****************************************************************************/ 
 
#include <windows.h> 
#include <shellapi.h> 
#include <stdlib.h> 
#include <commdlg.h> 
#include <string.h> 
 
#include "gutils.h" 
#include "table.h" 
#include "list.h" 
#include "scandir.h"            /* needed for file.h     */ 
#include "file.h"               /* needed for compitem.h */ 
#include "compitem.h"           /* needed for view.h     */ 
#include "complist.h" 
#include "view.h" 
#include "state.h" 
#include "windiff.h" 
#include "wdiffrc.h" 
 
 
/*--constants and data types--------------------------------------------*/ 
 
int Version = 2; 
int SubVersion = 01; 
 
/* When we print the current table, we pass this id as the table id 
 * When we are queried for the properties of this table, we know they 
 * want the printing properties for the current view. We use this to 
 * select different fonts and colours for the printer. 
 */ 
#define TABID_PRINTER   1 
 
 
 
/* 
 * structure containing args passed to worker thread in initial 
 * case (executing command line instructions).  
 */ 
typedef struct { 
 
        LPSTR first; 
        LPSTR second; 
        LPSTR savelist; 
        UINT saveopts; 
        VIEW view; 
        BOOL fDeep; 
} THREADARGS, FAR * PTHREADARGS; 
 
 
/* Structure containing all the arguments we'd like to give to do_editfile 
   Need a structure because CreateThread only allows for one argument. 
*/ 
typedef struct { 
        VIEW view; 
        int option; 
        int selection; 
} EDITARGS, FAR * PEDITARGS; 
 
/*---- colour scheme------------------------------- */ 
 
/* outline */ 
DWORD rgb_outlinehi = RGB(255, 0, 0);   /* hilighted files in outline mode  */ 
 
/* expand view */ 
DWORD rgb_leftfore =   RGB(  0,   0,   0);         /* foregrnd for left lines */ 
DWORD rgb_leftback  =  RGB(255,   0,   0);         /* backgrnd for left lines */ 
DWORD rgb_rightfore =  RGB(  0,   0,   0);         /* foregrnd for right lines*/ 
DWORD rgb_rightback =  RGB(255, 255,   0);         /* backgrnd for right lines*/ 
 
/* moved lines */ 
DWORD rgb_mleftfore =  RGB(  0,   0, 128);         /* foregrnd for moved-left */ 
DWORD rgb_mleftback =  RGB(255,   0,   0);         /* backgrnd for moved-left */ 
DWORD rgb_mrightfore = RGB(  0,   0, 255);         /* foregrnd for moved-right*/ 
DWORD rgb_mrightback = RGB(255, 255,   0);         /* backgrnd for moved-right*/ 
 
/* bar window */ 
DWORD rgb_barleft =    RGB(255,   0,   0);         /* bar sections in left only  */ 
DWORD rgb_barright =   RGB(255, 255,   0);         /* bar sections in right only */ 
DWORD rgb_barcurrent = RGB(  0,   0, 255);         /* current pos markers in bar */ 
 
 
/* module static data -------------------------------------------------*/ 
 
 
/* current value of window title */ 
char AppTitle[256]; 
 
 
HWND hwndClient;        /* main window */ 
HWND hwndRCD;           /* table window */ 
HWND hwndStatus;        /* status bar across top */ 
HWND hwndBar;           /* graphic of sections as vertical bars */ 
 
HACCEL haccel; 
 
/* The status bar told us it should be this high. Rest of client area 
 * goes to the hwndBar and hwndRCD. 
 */ 
int status_height; 
 
HINSTANCE hInst;   /* handle to current app instance */ 
HMENU hMenu;    /* handle to menu for hwndClient */ 
 
int nMinMax = SW_SHOWNORMAL;         /* default state of window normal */ 
 
/* The message sent to us as a callback by the table window needs to be 
 * registered - table_msgcode is the result of the RegisterMessage call 
 */ 
UINT table_msgcode; 
 
/* True if we are currently doing some scan or comparison. 
 * Must get critical section before checking/changing this (call 
 * SetBusy. 
 */ 
BOOL fBusy = FALSE; 
 
int     selection       =       -1;     /* selected row in table*/ 
 
/* Options for DisplayMode field indicating what is currently shown. 
 * We use this to know whether or not to show the graphic bar window. 
 */ 
#define MODE_NULL       0       /* nothing displayed */ 
#define MODE_OUTLINE    1       /* a list of files displayed */ 
#define MODE_EXPAND     2       /* view is expanded view of one file */ 
 
int DisplayMode = MODE_NULL;    /* indicates whether we are in expand mode */ 
 
VIEW current_view = NULL; 
 
/* command line parameters */ 
extern int __argc; 
extern char ** __argv; 
 
BOOL bAbort = FALSE;    /* set to request abort of current operation */ 
 
char editor_cmdline[256] = "notepad %p";  /* editor cmdline */ 
                          /* slick version is "s %p -#%l" */ 
 
/* app-wide global data --------------------------------------------- */ 
 
/* Handle returned from gmem_init - we use this for all memory allocations */ 
HANDLE hHeap; 
 
/* Current state of menu options */ 
int line_numbers = IDM_LNRS; 
int expand_mode = IDM_BOTHFILES; 
int outline_include = INCLUDE_LEFTONLY|INCLUDE_RIGHTONLY|INCLUDE_SAME|INCLUDE_DIFFER; 
BOOL ignore_blanks = TRUE; 
BOOL picture_mode = TRUE; 
 
/* function prototypes ---------------------------------------------*/ 
 
BOOL InitApplication(HINSTANCE hInstance); 
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow); 
void CreateTools(void); 
void DeleteTools(void); 
long APIENTRY MainWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam); 
BOOL SetBusy(void); 
void SetNotBusy(void); 
void SetSelection(long rownr); 
void SetButtonText(LPSTR cmd); 
BOOL ToExpand(HWND hwnd); 
void ParseArgs(int argc, char ** argv); 
 
 
DWORD wd_initial(LPVOID arg); 
 
static HANDLE ghThread = NULL; 
 
static DWORD gdwMainThreadId;     /* threadid of main (user interface) thread 
                                     initialised in winmain(), thereafter constant. 
                                     See windiff_UI() 
                                  */ 
 
/*************************************************************************** 
 * Function: windiff_UI 
 * 
 * Purpose: 
 * 
 * If you are about to put up a dialog box or in fact process input in any way 
 * on any thread other than the main thread - or if you MIGHT be on a thread other 
 * than the main thread, then you must call this function with TRUE before doing 
 * it and with FALSE immediately afterwards.  Otherwise you will get one of a 
 * number of flavours of not-very-responsiveness 
 */ 
void windiff_UI(BOOL bAttach) 
{ 
        DWORD dwThreadId = GetCurrentThreadId(); 
        if (dwThreadId==gdwMainThreadId) return; 
 
        if (bAttach) GetDesktopWindow(); 
        AttachThreadInput(dwThreadId, gdwMainThreadId, bAttach); 
} /* windiff_UI */ 
 
/*************************************************************************** 
 * Function: WinMain 
 * 
 * Purpose: 
 * 
 * Main entry point. Register window classes, create windows, 
 * parse command line arguments and then perform a message loop 
 */ 
int WINAPI 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
{ 
 
        MSG msg; 
 
        gdwMainThreadId = GetCurrentThreadId(); 
 
        /* create any pens/brushes etc and read in profile defaults */ 
        CreateTools(); 
 
        /* init window class unless other instances running */ 
        if (!hPrevInstance) 
            if (!InitApplication(hInstance)) 
                return(FALSE); 
 
 
        /* init this instance - create all the windows */ 
        if (!InitInstance(hInstance, nCmdShow)) 
            return(FALSE); 
 
        ParseArgs(__argc, __argv); 
 
 
        /* message loop */ 
        while(GetMessage(&msg, NULL, 0, 0)) { 
                if (!TranslateAccelerator(hwndClient, haccel, &msg)) { 
                    TranslateMessage(&msg); 
                    DispatchMessage(&msg); 
                } 
        } 
 
        return (msg.wParam); 
 
} 
 
/*************************************************************************** 
 * Function: InitApplication 
 * 
 * Purpose: 
 * 
 * Register window class for the main window and the bar window. 
 */ 
BOOL 
InitApplication(HINSTANCE hInstance) 
{ 
        WNDCLASS    wc; 
        BOOL resp; 
 
 
        /* register the bar window class */ 
        InitBarClass(hInstance); 
 
        wc.style = 0; 
        wc.lpfnWndProc = MainWndProc; 
        wc.cbClsExtra = 0; 
        wc.cbWndExtra = 0; 
        wc.hInstance = hInstance; 
        wc.hIcon = LoadIcon(hInstance, "WinDiff"); 
        wc.hCursor = LoadCursor(NULL, IDC_ARROW); 
        wc.hbrBackground = NULL; 
        wc.lpszClassName =  "WinDiffViewerClass"; 
        wc.lpszMenuName = NULL; 
 
        resp = RegisterClass(&wc); 
 
        return(resp); 
} 
 
/*************************************************************************** 
 * Function: InitInstance 
 * 
 * Purpose: 
 * 
 * Create and show the windows 
 */ 
BOOL 
InitInstance(HINSTANCE hInstance, int nCmdShow) 
{ 
        RECT rect; 
        HANDLE hstatus; 
        int bar_width; 
        RECT childrc; 
 
        hInst = hInstance; 
 
        /* initialise a heap. we use this one heap throughout 
         * the app. for all memory requirements 
         */ 
        hHeap = gmem_init(); 
        /* initialise the list package */ 
        List_Init(); 
 
 
        hMenu = LoadMenu(hInstance, "WinDiffMenu"); 
        haccel = LoadAccelerators(hInstance, "WinDiffAccel"); 
 
        /* create the main window */ 
        hwndClient = CreateWindow("WinDiffViewerClass", 
                            "WinDiff", 
                            WS_OVERLAPPEDWINDOW, 
                            CW_USEDEFAULT, 
                            CW_USEDEFAULT, 
                            CW_USEDEFAULT, 
                            CW_USEDEFAULT, 
                            NULL, 
                            hMenu, 
                            hInstance, 
                            NULL 
                ); 
 
 
 
        if (!hwndClient) { 
            return(FALSE); 
        } 
 
        /* create 3 child windows, one status, one table and one bar 
         * Initially, the bar window is hidden and covered by the table. 
         */ 
 
        /* create a status bar window as 
         * a child of the main window. 
         */ 
 
        /* build a status struct for two labels and an abort button */ 
        hstatus = StatusAlloc(3); 
        StatusAddItem(hstatus, 0, SF_STATIC, SF_LEFT|SF_VAR|SF_SZMIN, IDL_STATLAB, 14, NULL); 
        StatusAddItem(hstatus, 1, SF_BUTTON, SF_RIGHT|SF_RAISE, IDM_ABORT, 8, 
                LoadRcString(IDS_EXIT)); 
        StatusAddItem(hstatus, 2, SF_STATIC, SF_LOWER|SF_LEFT|SF_VAR, 
                        IDL_NAMES, 60, NULL); 
 
        /* ask the status bar how high it should be for the controls 
         * we have chosen, and save this value for re-sizing. 
         */ 
        status_height = StatusHeight(hstatus); 
 
        /* create a window of this height */ 
        GetClientRect(hwndClient, &rect); 
        childrc = rect; 
        childrc.bottom = status_height; 
        hwndStatus = StatusCreate(hInst, hwndClient, IDC_STATUS, &childrc, 
                        hstatus); 
 
        /* layout constants are stated as percentages of the window width */ 
        bar_width = (rect.right - rect.left) * BAR_WIN_WIDTH / 100; 
 
        /* create the table class covering all the remaining part of 
         * the main window 
         */ 
        hwndRCD = CreateWindow(TableClassName, 
                        NULL, 
                        WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL, 
                        0, 
                        status_height, 
                        (int)(rect.right - rect.left), 
                        (int)(rect.bottom - status_height), 
                        hwndClient, 
                        (HANDLE) IDC_RCDISP1, 
                        hInst, 
                        NULL); 
 
        /* create a bar window as a child of the main window. 
         * this window remains hidden until we switch into MODE_EXPAND 
         */ 
        hwndBar = CreateWindow("BarClass", 
                        NULL, 
                        WS_CHILD | WS_VISIBLE, 
                        0, 
                        status_height, 
                        bar_width, 
                        (int)(rect.bottom - status_height), 
                        hwndClient, 
                        (HANDLE) IDC_BAR, 
                        hInst, 
                        NULL); 
 
        /* nMinMax indicates whether we are to be minimised on startup, 
         * on command line parameters 
         */ 
        ShowWindow(hwndBar, SW_HIDE); 
 
        if (GetProfileInt(APPNAME, "OutlineSaved", 0)) 
        { 
                WINDOWPLACEMENT wp; 
 
                /* restore the previous expanded size and position */ 
                wp.length                  = sizeof( WINDOWPLACEMENT ); 
                wp.flags                   = 0; 
                wp.showCmd                 = GetProfileInt( APPNAME, "OutlineShowCmd", 
                                                            SW_SHOWNORMAL); 
                wp.ptMaxPosition.x         = GetProfileInt( APPNAME, "OutlineMaxX",       0); 
                wp.ptMaxPosition.y         = GetProfileInt( APPNAME, "OutlineMaxY",       0); 
                wp.rcNormalPosition.left   = (int)GetProfileInt( APPNAME, "OutlineNormLeft",  (UINT)(-1)); 
                wp.rcNormalPosition.top    = (int)GetProfileInt( APPNAME, "OutlineNormTop",   (UINT)(-1)); 
                wp.rcNormalPosition.right  = (int)GetProfileInt( APPNAME, "OutlineNormRight", (UINT)(-1)); 
                wp.rcNormalPosition.bottom = (int)GetProfileInt( APPNAME, "OutlineNormBottom",(UINT)(-1)); 
                SetWindowPlacement(hwndClient,&wp); 
        } 
        else ShowWindow(hwndClient, nMinMax); 
 
        UpdateWindow(hwndClient); 
 
 
        /* initialise busy flag and status line to show we are idle 
         * (ie not comparing or scanning) 
         */ 
        SetNotBusy(); 
 
        return(TRUE); 
 
} /* InitInstance */ 
 
/*************************************************************************** 
 * Function: windiff_usage 
 * 
 * Purpose: 
 * 
 * Complain to command line users about poor syntax, 
 * will be replaced by proper help file. 
 */ 
void 
windiff_usage(LPSTR msg) 
{ 
        int retval; 
        TCHAR szBuf[MAX_PATH]; 
 
        if (msg==NULL) 
                msg = LoadRcString(IDS_USAGE_STR); 
 
        LoadString(hInst, IDS_WINDIFF_USAGE, szBuf, sizeof(szBuf)); 
        retval = MessageBox(hwndClient, 
                msg, 
                szBuf, MB_ICONINFORMATION|MB_OKCANCEL); 
        if (retval == IDCANCEL) { 
                exit(1); 
        } 
} 
 
/*************************************************************************** 
 * Function: ParseArgs 
 * 
 * Purpose: 
 * 
 * Parse command line arguments 
 * 
 * The user can give one or two paths. If only one, we assume the second 
 * is '.' for the current directory. If one of the two paths is a directory 
 * and the other a file, we compare a file of the same name in the two dirs. 
 * 
 * The command -s filename causes the outline list to be written to a file 
 * and then the program exits. -s{slrd} filename allows selection of which 
 * files are written out; by default, we assume -sld for files left and different. 
 * 
 * -T means tree.  Go deep. 
 * 
 * The default is Deep, -L overrides and implies shallow, -T overrides -L 
 */ 
void 
ParseArgs(int argc, char ** argv) 
{ 
        int i; 
        LPSTR chp; 
        PTHREADARGS ta; 
        DWORD threadid; 
 
        /* thread args can't be on the stack since the stack will change 
         * before the thread completes execution 
         */ 
        ta = (PTHREADARGS) gmem_get(hHeap, sizeof(THREADARGS)); 
        ta->first = NULL; 
        ta->second = NULL; 
        ta->savelist = NULL; 
        ta->saveopts = 0; 
        ta->fDeep = FALSE;  /* No -T option seen yet */ 
 
        for (i = 1; i < argc; i++) { 
 
                /* is this an option ? */ 
                if ((argv[i][0] == '-') || (argv[i][0] == '/')) { 
                        switch(argv[i][1]) { 
 
                        case 's': 
                        case 'S': 
                                /* read letters for the save option: s,l,r,d */ 
                                for(chp = &argv[i][2]; *chp != '\0'; chp++) { 
                                        switch(*chp) { 
                                        case 's': 
                                        case 'S': 
                                                ta->saveopts |= INCLUDE_SAME; 
                                                break; 
                                        case 'l': 
                                        case 'L': 
                                                ta->saveopts |= INCLUDE_LEFTONLY; 
                                                break; 
                                        case 'r': 
                                        case 'R': 
                                                ta->saveopts |= INCLUDE_RIGHTONLY; 
                                                break; 
                                        case 'd': 
                                        case 'D': 
                                                ta->saveopts |= INCLUDE_DIFFER; 
                                                break; 
                                        default: 
                                                windiff_usage(NULL); 
                                                return; 
                                        } 
                                } 
 
                                if (ta->saveopts == 0) { 
                                        /* default to left and differ */ 
                                        ta->saveopts = (INCLUDE_LEFTONLY) | (INCLUDE_DIFFER); 
                                } 
                                ta->savelist = argv[++i]; 
                                break; 
                        case 't': 
                        case 'T': 
                                ta->fDeep = TRUE; 
                                break; 
                        default: 
                                windiff_usage(NULL); 
                                return; 
                        } 
                } else { 
                        if (ta->first == NULL) { 
                                ta->first = argv[i]; 
                        } else { 
                                ta->second = argv[i]; 
                        } 
                } 
        } 
 
        /* set the correct depth */ 
        if (ta->fDeep) 
                ;                       /* explicitly set -- leave it alone */ 
        else ta->fDeep = TRUE;          /* global default */ 
 
        /* any paths to scan ? */ 
        if (ta->first == NULL) { 
                return; 
        } 
 
        if (ta->second == NULL) { 
                ta->second = "."; 
        } 
 
        SetBusy(); 
 
        /* minimise the window if -s flag given */ 
        if (ta->savelist != NULL) { 
                ShowWindow(hwndClient, SW_MINIMIZE); 
        } 
 
        /* make an empty view */ 
        current_view = view_new(hwndRCD); 
        DisplayMode = MODE_OUTLINE; 
 
        ta->view = current_view; 
 
        /* attempt to create a worker thread */ 
 
        ghThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)wd_initial, (LPVOID) ta, 
                        0, &threadid); 
        if (ghThread == NULL) 
        { 
                wd_initial( (LPVOID) ta); 
        } 
} /* ParseArgs */ 
 
 
/*************************************************************************** 
 * Function: CreateTools 
 * 
 * Purpose: 
 * 
 * Create any pens/brushes, and read defaults 
 * from the profile file for menu settings etc. 
 */ 
void 
CreateTools(void) 
{ 
 
        /* standard message that table class sends us for 
         * notifications and queries. 
         */ 
        table_msgcode = RegisterWindowMessage(TableMessage); 
 
        line_numbers = GetProfileInt(APPNAME, "LineNumbers", line_numbers); 
        outline_include = GetProfileInt(APPNAME, "FileInclude", outline_include); 
        ignore_blanks = GetProfileInt(APPNAME, "Blanks", ignore_blanks); 
        picture_mode = GetProfileInt(APPNAME, "Picture", picture_mode); 
 
        GetProfileString(APPNAME, "Editor", editor_cmdline, (LPTSTR)editor_cmdline, 
                        sizeof(editor_cmdline)); 
        InitializeCriticalSection(&CSWindiff); 
 
} 
 
/*************************************************************************** 
 * Function: DeleteTools 
 * 
 * Purpose: 
 * 
 * Delete any pens or brushes that were created in CreateTools  
 */ 
void 
DeleteTools(void) 
{ 
 
        DeleteCriticalSection(&CSWindiff); 
 
} 
 
 
/*************************************************************************** 
 * Function: 
 * 
 * Purpose: 
 * 
 * Check whether we have had an abort request (IDM_ABORT), and 
 * return TRUE if abort requested, otherwise FALSE 
 */ 
BOOL 
Poll(void) 
{ 
    return(bAbort); 
} 
 
 
/*************************************************************************** 
 * Function: DoResize 
 * 
 * Purpose: 
 * 
 * Position child windows on a resize of the main window 
 */ 
void 
DoResize(HWND hWnd) 
{ 
        RECT rc; 
        int bar_width; 
 
        GetClientRect(hWnd, &rc); 
        MoveWindow(hwndStatus, 0, 0, rc.right - rc.left, status_height, TRUE); 
 
        bar_width = (rc.right - rc.left) * BAR_WIN_WIDTH / 100; 
 
        /* bar window is hidden unless in expand mode */ 
        if ((DisplayMode == MODE_EXPAND) && (picture_mode)) { 
                ShowWindow(hwndBar, SW_SHOW); 
                MoveWindow(hwndBar, 0, status_height, 
                        bar_width, rc.bottom - status_height, TRUE); 
                MoveWindow(hwndRCD, bar_width, status_height, 
                        (rc.right - rc.left) - bar_width, 
                        rc.bottom - status_height, TRUE); 
        } else { 
                MoveWindow(hwndRCD, 0, status_height, (rc.right - rc.left), 
                        rc.bottom - status_height, TRUE); 
                ShowWindow(hwndBar, SW_HIDE); 
        } 
 
} 
/*************************************************************************** 
 * Function: AboutBox 
 * 
 * Purpose: 
 * 
 * Standard processing for About box. 
 */ 
int APIENTRY 
AboutBox(HWND hDlg, unsigned message, UINT wParam, LONG lParam) 
{ 
        char ch[256]; 
 
        switch (message) { 
 
        case WM_INITDIALOG: 
                wsprintf((LPTSTR)ch, "%d.%02d", Version, SubVersion); 
                SetDlgItemText(hDlg, IDD_VERSION, ch); 
return(TRUE); 
 
        case WM_COMMAND: 
                switch (GET_WM_COMMAND_ID(wParam, lParam)) { 
                case IDOK: 
                        EndDialog(hDlg, 0); 
                        return(TRUE); 
                } 
                break; 
        } 
        return(FALSE); 
} 
 
 
/* -- menu commands ---------------------------------------------------*/ 
 
/*************************************************************************** 
 * Function: DoPrint 
 * 
 * Purpose: 
 * 
 * Print the current view  
 */ 
void 
DoPrint(void) 
{ 
        Title head, foot; 
        PrintContext context; 
        TCHAR szPage[50]; 
 
        /* print context contains the header and footer. Use the 
         * default margins and printer selection 
         */ 
 
        /* we set the table id to be TABID_PRINTER. When the table calls 
         * back to get text and properties, we use this to indicate 
         * that the table refered to is the 'current_view', but in print 
         * mode, and thus we will use different colours/fonts. 
         */ 
        context.head = &head; 
        context.foot = &foot; 
        context.margin = NULL; 
        context.pd = NULL; 
        context.id = TABID_PRINTER; 
 
        /* header is filenames or just WinDiff if no names known*/ 
        if (strlen(AppTitle) > 0) { 
                head.ptext = AppTitle; 
        } else { 
                head.ptext = "WinDiff"; 
        } 
 
        /* header is centred, footer is right-aligned and 
         * consists of the page number 
         */ 
        head.props.valid = P_ALIGN; 
        head.props.alignment = P_CENTRE; 
        lstrcpy(szPage,LoadRcString(IDS_PAGE)); 
        foot.ptext = (LPSTR)szPage; 
        foot.props.valid = P_ALIGN; 
        foot.props.alignment = P_RIGHT; 
 
        SendMessage(hwndRCD, TM_PRINT, 0, (DWORD) (LPSTR) &context); 
} 
 
/*************************************************************************** 
 * Function: FindNextChange 
 * 
 * Purpose: 
 * 
 * Find the next line in the current view that is 
 * not STATE_SAME. Start from the current selection, if valid, or 
 * from the top of the window if no selection. 
 * 
 */ 
BOOL 
FindNextChange(void) 
{ 
        long row; 
 
        /* start from the selection or top of the window if no selection */ 
        if (selection >= 0) { 
                row = selection; 
        } else { 
                row = (int) SendMessage(hwndRCD, TM_TOPROW, FALSE, 0); 
        } 
 
 
        /* find the next 'interesting' line */ 
        row = view_findchange(current_view, row, TRUE); 
        if (row >= 0) { 
                SetSelection(row); 
                return(TRUE); 
        } else { 
                windiff_UI(TRUE); 
                MessageBox(hwndClient, LoadRcString(IDS_NO_MORE_CHANGES), "Windiff", 
                        MB_ICONINFORMATION|MB_OK); 
                windiff_UI(FALSE); 
 
                return(FALSE); 
        } 
} 
 
/*************************************************************************** 
 * Function: FindPrevChange 
 * 
 * Purpose: 
 * 
 * Find the previous line in the current view that is not STATE_SAME 
 */ 
BOOL 
FindPrevChange(void) 
{ 
        long row; 
 
        /* start from the selection or top of window if no selection */ 
        if (selection >= 0) { 
                row = selection; 
        } else { 
                row = (int) SendMessage(hwndRCD, TM_TOPROW, FALSE, 0); 
        } 
 
        /* find the previous 'interesting' line */ 
        row = view_findchange(current_view, row, FALSE); 
        if (row >= 0) { 
                SetSelection(row); 
                return(TRUE); 
        } else { 
                windiff_UI(TRUE); 
                MessageBox(hwndClient, LoadRcString(IDS_NO_PREV_CHANGES), "Windiff", 
                        MB_ICONINFORMATION|MB_OK); 
                windiff_UI(FALSE); 
 
                return(FALSE); 
        } 
 
} 
/*************************************************************************** 
 * Function: WriteProfileInt 
 * 
 */ 
  
BOOL WriteProfileInt(LPSTR AppName, LPSTR Key, int Int) 
{       char Str[40]; 
 
        wsprintf((LPTSTR)Str, "%d", Int); 
        return WriteProfileString(AppName, Key, Str); 
 
} /* WriteProfileInt */ 
 
 
/*************************************************************************** 
 * Function: ToExpand 
 * 
 * Purpose: 
 * 
 * Switch to expand view of the selected line  
 */ 
BOOL 
ToExpand(HWND hwnd) 
{ 
        if (selection < 0) { 
                return(FALSE); 
        } 
 
        if (!view_isexpanded(current_view)) { 
                /* save the current outline size and position */ 
                WINDOWPLACEMENT wp; 
                if (GetWindowPlacement(hwndClient,&wp)) { 
                        WriteProfileInt(APPNAME, "OutlineShowCmd", wp.showCmd); 
                        WriteProfileInt(APPNAME, "OutlineMaxX", wp.ptMaxPosition.x); 
                        WriteProfileInt(APPNAME, "OutlineMaxY", wp.ptMaxPosition.y); 
                        WriteProfileInt(APPNAME, "OutlineNormLeft", wp.rcNormalPosition.left); 
                        WriteProfileInt(APPNAME, "OutlineNormTop", wp.rcNormalPosition.top); 
                        WriteProfileInt(APPNAME, "OutlineNormRight", wp.rcNormalPosition.right); 
                        WriteProfileInt(APPNAME, "OutlineNormBottom", wp.rcNormalPosition.bottom); 
                        WriteProfileInt(APPNAME, "OutlineSaved", 1); 
                } 
 
                /* restore the previous expanded size and position, if any */ 
                if (GetProfileInt(APPNAME, "ExpandedSaved", 0)) { 
                        wp.flags                   = 0; 
                        wp.showCmd 
                                = GetProfileInt( APPNAME, "ExpandShowCmd" 
                                               , SW_SHOWMAXIMIZED); 
                        wp.ptMaxPosition.x 
                                = GetProfileInt( APPNAME, "ExpandMaxX", 0); 
                        wp.ptMaxPosition.y 
                                = GetProfileInt( APPNAME, "ExpandMaxY", 0); 
                        wp.rcNormalPosition.left 
                                = GetProfileInt( APPNAME, "ExpandNormLeft" 
                                               , wp.rcNormalPosition.left); 
                        wp.rcNormalPosition.top 
                                = GetProfileInt( APPNAME, "ExpandNormTop" 
                                               , wp.rcNormalPosition.top); 
                        wp.rcNormalPosition.right 
                                = GetProfileInt( APPNAME, "ExpandNormRight" 
                                               , wp.rcNormalPosition.right); 
                        wp.rcNormalPosition.bottom 
                                = GetProfileInt( APPNAME, "ExpandNormBottom" 
                                               , wp.rcNormalPosition.bottom); 
                        SetWindowPlacement(hwndClient,&wp); 
                } 
                else ShowWindow(hwndClient, SW_SHOWMAXIMIZED); 
        } 
 
        /*change the view mapping to expand mode */ 
        if (view_expand(current_view, selection)) { 
 
                /* ok - we now have an expanded view - change status 
                 * to show this 
                 */ 
 
                DisplayMode = MODE_EXPAND; 
 
                /* resize to show the graphic bar picture */ 
                DoResize(hwndClient); 
 
 
                /* change button,status text-if we are not still busy*/ 
                if (!fBusy) { 
                        TCHAR szBuf[10]; 
                        lstrcpy(szBuf,LoadRcString(IDS_OUTLINE)); 
                        /* the status field when we are expanded shows the 
                         * tag field (normally the file name) for the 
                         * item we are expanding 
                         */ 
                        SetStatus(view_getcurrenttag(current_view) ); 
                        SetButtonText(szBuf); 
                } 
 
                return(TRUE); 
        } 
        return(FALSE); 
} /* ToExpand */ 
 
/*************************************************************************** 
 * Function: ToOutline 
 * 
 * Purpose: 
 * 
 * Switch back to outline view - showing just the list of file names. 
 */ 
void 
ToOutline(HWND hwnd) 
{ 
        if (view_isexpanded(current_view)) { 
                /* save the current expanded size and position */ 
                WINDOWPLACEMENT wp; 
                if (GetWindowPlacement(hwndClient,&wp)) { 
                        WriteProfileInt(APPNAME, "ExpandShowCmd", wp.showCmd); 
                        WriteProfileInt(APPNAME, "ExpandMaxX", wp.ptMaxPosition.x); 
                        WriteProfileInt(APPNAME, "ExpandMaxY", wp.ptMaxPosition.y); 
                        WriteProfileInt(APPNAME, "ExpandNormLeft", wp.rcNormalPosition.left); 
                        WriteProfileInt(APPNAME, "ExpandNormTop", wp.rcNormalPosition.top); 
                        WriteProfileInt(APPNAME, "ExpandNormRight", wp.rcNormalPosition.right); 
                        WriteProfileInt(APPNAME, "ExpandNormBottom", wp.rcNormalPosition.bottom); 
                        WriteProfileInt(APPNAME, "ExpandedSaved", 1); 
                } 
 
                /* restore the previous expanded size and position, if any */ 
                if (GetProfileInt(APPNAME, "OutlineSaved", 0))  { 
                        wp.flags = 0; 
                        wp.showCmd 
                                = GetProfileInt( APPNAME, "OutlineShowCmd" 
                                               , SW_SHOWNORMAL); 
                        wp.ptMaxPosition.x 
                                = GetProfileInt( APPNAME, "OutlineMaxX", 0); 
                        wp.ptMaxPosition.y 
                                = GetProfileInt( APPNAME, "OutlineMaxY", 0); 
                        wp.rcNormalPosition.left 
                                = GetProfileInt( APPNAME, "OutlineNormLeft" 
                                               , wp.rcNormalPosition.left); 
                        wp.rcNormalPosition.top 
                                = GetProfileInt( APPNAME, "OutlineNormTop" 
                                               , wp.rcNormalPosition.top); 
                        wp.rcNormalPosition.right 
                                = GetProfileInt( APPNAME, "OutlineNormRight" 
                                               , wp.rcNormalPosition.right); 
                        wp.rcNormalPosition.bottom 
                                = GetProfileInt( APPNAME, "OutlineNormBottom" 
                                               , wp.rcNormalPosition.bottom); 
                        SetWindowPlacement(hwndClient,&wp); 
                } 
                ShowWindow(hwndClient, SW_SHOWNORMAL); 
        } 
 
        DisplayMode = MODE_OUTLINE; 
 
        /* switch mapping back to outline view */ 
        view_outline(current_view); 
 
        /* hide bar window and resize to cover */ 
        DoResize(hwndClient); 
 
 
        /* change label on button */ 
        if (!fBusy) { 
                TCHAR szBuf[8]; 
                lstrcpy(szBuf,LoadRcString(IDS_EXPAND)); 
                SetButtonText(szBuf); 
                SetStatus(NULL); 
        } 
} /* ToOutline */ 
 
/*************************************************************************** 
 * Function: ToMoved 
 * 
 * Purpose: 
 * 
 * If the user clicks on a MOVED line in expand mode, we jump to the 
 * other line. We return TRUE if this was possible,  or FALSE otherwise. 
 */ 
BOOL 
ToMoved(HWND hwnd) 
{ 
        BOOL bIsLeft; 
        int linenr, state; 
        long i, total; 
 
        if (DisplayMode != MODE_EXPAND) { 
                return(FALSE); 
        } 
        if (selection < 0) { 
                return(FALSE); 
        } 
 
        state = view_getstate(current_view, selection); 
        if (state == STATE_MOVEDLEFT) { 
                bIsLeft = TRUE; 
                /* get the linenr of the other copy */ 
                linenr = abs(view_getlinenr_right(current_view, selection)); 
        } else if (state == STATE_MOVEDRIGHT) { 
                bIsLeft = FALSE; 
                /* get the linenr of the other copy */ 
                linenr = abs(view_getlinenr_left(current_view, selection)); 
        } else { 
                /* not a moved line - so we can't find another copy */ 
                return(FALSE); 
        } 
 
        /* search the view for this line nr */ 
        total = view_getrowcount(current_view); 
        for (i = 0; i < total; i++) { 
                if (bIsLeft) { 
                        if (linenr == view_getlinenr_right(current_view, i)) { 
                                /* found it */ 
                                SetSelection(i); 
                                return(TRUE); 
                        } 
                } else { 
                        if (linenr == view_getlinenr_left(current_view, i)) { 
                                SetSelection(i); 
                                return(TRUE); 
                        } 
                } 
        } 
        return(FALSE); 
} 
 
/*************************************************************************** 
 * Function: do_editfile 
 * 
 * Purpose: 
 * 
 * Launch an editor on the current file (the file we are expanding, or 
 * in outline mode the selected row. Option allows selection of the 
 * left file, the right file or the composite view of this item. 
 * pe points to a packet of parameters that must be freed before returning. 
 * The return value is meaningless (just to conform to CreateThread). 
 */ 
LONG 
do_editfile(PEDITARGS pe) 
{ 
        VIEW view = pe->view; 
        int option = pe->option; 
        int selection = pe->selection; 
 
        COMPITEM item; 
        LPSTR fname; 
        char cmdline[256]; 
        int currentline; 
        char * pOut = cmdline; 
        char * pIn = editor_cmdline; 
 
        STARTUPINFO si; 
        PROCESS_INFORMATION pi; 
 
        item = view_getitem(view, selection); 
        if (item == NULL) { 
                return -1; 
        } 
 
        fname = compitem_getfilename(item, option); 
 
        if ( 0 == fname ) 
        { 
            windiff_UI(TRUE); 
            MessageBox(hwndClient, LoadRcString(IDS_FILE_DOESNT_EXIST), 
                       "Windiff", MB_ICONSTOP|MB_OK); 
            windiff_UI(FALSE); 
            goto error; 
        } 
 
       switch ( option ) 
        { 
        case CI_LEFT: 
            currentline = view_getlinenr_left( view, 
                                               selection > 0 ? selection : 1); 
            break; 
 
        case CI_RIGHT: 
            currentline = view_getlinenr_right( view, 
                                                selection > 0 ? selection : 1); 
            break; 
 
        default: 
            currentline = 1; 
            break; 
        } 
 
        while( *pIn ) 
        { 
            switch( *pIn ) 
            { 
            case '%': 
                pIn++; 
                switch ( *pIn ) 
                { 
                case 'p': 
                    lstrcpy( (LPTSTR)pOut, fname ); 
                    while ( *pOut ) 
                        pOut++; 
                    break; 
 
                case 'l': 
                    _ltoa( currentline, pOut, 10 ); 
                    while ( *pOut ) 
                        pOut++; 
                    break; 
 
                default: 
                    if (IsDBCSLeadByte(*pIn) && *(pIn+1)) { 
                        *pOut++ = *pIn++; 
                    } 
                    *pOut++ = *pIn; 
                    break; 
                } 
                pIn++; 
                break; 
 
            default: 
                if (IsDBCSLeadByte(*pIn) && *(pIn+1)) { 
                    *pOut++ = *pIn++; 
                } 
                *pOut++ = *pIn++; 
                break; 
            } 
        } 
 
 
        /* Launch the process and waits for it to complete */ 
 
        si.cb = sizeof(STARTUPINFO); 
        si.lpReserved = NULL; 
        si.lpReserved2 = NULL; 
        si.cbReserved2 = 0; 
        si.lpTitle = (LPSTR)cmdline;  
        si.lpDesktop = (LPTSTR)NULL; 
        si.dwFlags = STARTF_FORCEONFEEDBACK; 
 
 
        if (!CreateProcess(NULL, 
                        cmdline, 
                        NULL, 
                        NULL, 
                        FALSE, 
                        NORMAL_PRIORITY_CLASS, 
                        NULL, 
                        (LPTSTR)NULL, 
                        &si, 
                        &pi)) { 
                windiff_UI(TRUE); 
                MessageBox(hwndClient, LoadRcString(IDS_FAILED_TO_LAUNCH_EDT), 
                        "Windiff", MB_ICONSTOP|MB_OK); 
                windiff_UI(FALSE); 
                goto error; 
        } 
 
        /* wait for completion. */ 
        WaitForSingleObject(pi.hProcess, INFINITE); 
 
        /* close process and thread handles */ 
        CloseHandle(pi.hThread); 
        CloseHandle(pi.hProcess); 
 
        /* finished with the filename. deletes it if it was a temp. */ 
        compitem_freefilename(item, option, fname); 
 
        /* 
         * refresh cached view always .  A common trick is to edit the 
         * composite file and then save it as a new left or right file. 
         * Equally the user can edit the left and save as a new right. 
         */ 
 
        /* We want to force both files to be re-read, but it's not a terribly 
         * good idea to throw the lines away on this thread.  Someone might 
         * be reading them on another thread! 
         */ 
        /* file_discardlines(compitem_getleftfile(item)) */ 
        /* file_discardlines(compitem_getrightfile(item)) */ 
 
        /* force the compare to be re-done */ 
        PostMessage(hwndClient, WM_COMMAND, IDM_UPDATE, (LONG)item); 
error: 
        gmem_free(hHeap, (LPSTR) pe, sizeof(EDITARGS)); 
 
        return 0; 
 
} /* do_editfile */ 
 
 
/*************************************************************************** 
 * Function: do_editthread 
 * 
 * Purpose: 
 * 
 * Launch an editor on a separate thread.  It will actually get a separate 
 * process, but we want our own thread in this process.  This thread will 
 * wait until it's finished and then order up a refresh of the UI. 
 * Need to give it its parameters as a gmem allocated packet because 
 * it IS on a separate thread. 
 */ 
void do_editthread(VIEW view, int option) 
{ 
        PEDITARGS pe; 
        HANDLE thread; 
        DWORD threadid; 
 
        pe = (PEDITARGS) gmem_get(hHeap, sizeof(EDITARGS)); 
        pe->view = view; 
        pe->option = option; 
        pe->selection = selection; 
 
        thread = CreateThread( NULL 
                             , 0 
                             , (LPTHREAD_START_ROUTINE)do_editfile 
                             , (LPVOID) pe 
                             , 0 
                             , &threadid 
                             ); 
        if (thread == NULL) 
        { 
                /* The createthread failed, do without the extra thread - just 
                 * call the function synchronously 
                 */ 
                 do_editfile(pe); 
        } 
        else CloseHandle(thread); 
} /* do_editthread */ 
 
 
/* status bar and busy flags --------------------------------------------*/ 
 
 
/*************************************************************************** 
 * Function: SetButtonText 
 * 
 * Purpose: 
 * 
 * Set the Text on the statusbar button to reflect the current state  
 */ 
void 
SetButtonText(LPSTR cmd) 
{ 
        SendMessage(hwndStatus, SM_SETTEXT, IDM_ABORT, (DWORD) cmd); 
} 
 
/*************************************************************************** 
 * Function: SetStatus 
 * 
 * Purpose: 
 * 
 * Set the status field (left-hand part) of the status bar.  
 */ 
void 
SetStatus(LPSTR cmd) 
{ 
        SendMessage(hwndStatus, SM_SETTEXT, IDL_STATLAB, (DWORD) cmd); 
} 
 
 
/*************************************************************************** 
 * Function: SetNames 
 * 
 * Purpose: 
 * 
 * Set the names field - the central box in the status bar  
 */ 
void 
SetNames(LPSTR names) 
{ 
        SendMessage(hwndStatus, SM_SETTEXT, IDL_NAMES, (DWORD) names); 
        if (names == NULL) { 
                AppTitle[0] = '\0'; 
        } else { 
                strncpy(AppTitle, names, sizeof(AppTitle)); 
        } 
} 
 
/*************************************************************************** 
 * Function: SetBusy 
 * 
 * Purpose: 
 * 
 * If we are not already busy, set the busy flag. 
 * 
 * Enter critical section first. 
 */ 
BOOL 
SetBusy(void) 
{ 
        HMENU hmenu; 
 
 
        WDEnter(); 
 
        if (fBusy) { 
                WDLeave(); 
                return(FALSE); 
        } 
 
 
        fBusy = TRUE; 
 
        SetStatus(LoadRcString(IDS_COMPARING)); 
        /* status also on window text, so that you can see even from 
         * the icon when the scan has finished 
         */ 
        SetWindowText(hwndClient, LoadRcString(IDS_SCANNING)); 
 
        /* disable appropriate parts of menu */ 
        hmenu = GetMenu(hwndClient); 
        EnableMenuItem(hmenu, IDM_FILE,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND); 
        EnableMenuItem(hmenu, IDM_DIR,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND); 
        EnableMenuItem(hmenu, IDM_PRINT,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND); 
 
        /* enable abort only when busy */ 
        EnableMenuItem(hmenu, IDM_ABORT,MF_ENABLED|MF_BYCOMMAND); 
        SetButtonText(LoadRcString(IDS_ABORT));  /* leave DisplayMode unchanged */ 
 
        WDLeave(); 
        return(TRUE); 
} /* SetBusy */ 
/*************************************************************************** 
 * Function: SetNotBusy 
 * 
 * Purpose: 
 * 
 * This function can be called from the worker thread. 
 * Thus we must not cause any SendMessage calls to windows 
 * owned by the main thread while holding the CritSec or we 
 * could cause deadlock. 
 * 
 * The critsec is only needed to protect the fBusy flag - so 
 * clear the busy flag last, and only get the crit sec as needed. 
 */ 
void 
SetNotBusy(void) 
{ 
        HMENU hmenu; 
 
        /* reset button and status bar (clearing out busy flags) */ 
        if (current_view == NULL) { 
                SetButtonText(LoadRcString(IDS_EXIT)); 
                SetStatus(NULL); 
                DisplayMode = MODE_NULL; 
        } else if (view_isexpanded(current_view)) { 
                TCHAR szBuf[10]; 
                lstrcpy(szBuf,LoadRcString(IDS_OUTLINE)); 
                SetButtonText(szBuf); 
                SetStatus(view_getcurrenttag(current_view) ); 
                DisplayMode = MODE_EXPAND; 
        } else { 
                TCHAR szBuf[8]; 
                lstrcpy(szBuf,LoadRcString(IDS_EXPAND)); 
                SetButtonText(szBuf); 
                SetStatus(NULL); 
                DisplayMode = MODE_OUTLINE; 
        } 
 
        SetWindowText(hwndClient, "WinDiff"); 
 
        /* re-enable appropriate parts of menu */ 
        hmenu = GetMenu(hwndClient); 
        EnableMenuItem(hmenu, IDM_FILE,MF_ENABLED|MF_BYCOMMAND); 
        EnableMenuItem(hmenu, IDM_DIR,MF_ENABLED|MF_BYCOMMAND); 
        EnableMenuItem(hmenu, IDM_PRINT,MF_ENABLED|MF_BYCOMMAND); 
 
        /* disable abort now no longer busy */ 
        EnableMenuItem(hmenu, IDM_ABORT,MF_DISABLED|MF_GRAYED|MF_BYCOMMAND); 
 
 
        /* clear the busy flag, protected by critical section */ 
        WDEnter(); 
 
        fBusy = FALSE; 
        bAbort = FALSE; 
 
        if (ghThread!=NULL){ 
            CloseHandle(ghThread); 
            ghThread = NULL; 
        } 
        WDLeave(); 
} /* SetNotBusy */ 
 
/*************************************************************************** 
 * Function: IsBusy 
 * 
 * Purpose: 
 * 
 * Checks whether or not crit sec is open 
 */ 
BOOL 
IsBusy() 
{ 
        BOOL bOK; 
 
        WDEnter(); 
        bOK = fBusy; 
        WDLeave(); 
        return(bOK); 
} /* IsBusy */ 
 
/*************************************************************************** 
 * Function: BusyError 
 * 
 * Purpose: 
 * 
 * Puts up message box that system is busy. 
 */ 
void 
BusyError(void) 
{ 
        windiff_UI(TRUE); 
        MessageBox(hwndClient, 
                LoadRcString(IDS_PLEASE_WAIT), 
                "WinDiff", MB_OK|MB_ICONSTOP); 
        windiff_UI(FALSE); 
} /* BusyError */ 
 
/* --- colour scheme --------------------------------------------------- */ 
 
/*************************************************************************** 
 * Function: StateToColour 
 * 
 * Purpose: 
 * 
 * Map the state given into a foreground and a background colour 
 * for states that are highlighted. Return P_FCOLOUR if the foreground 
 * colour (put in *foreground) is to be used, return P_FCOLOUR|P_BCOLOUR if 
 * both *foreground and *background are to be used, or 0 if the default 
 * colours are to be used. 
 */ 
UINT 
StateToColour(int state, int col, DWORD FAR * foreground, DWORD FAR * background) 
{ 
 
 
        switch (state) { 
 
        case STATE_DIFFER: 
                /* files that differ are picked out in a foreground highlight, 
                 * with the default background 
                 */ 
                *foreground = rgb_outlinehi; 
                return(P_FCOLOUR); 
 
        case STATE_LEFTONLY: 
                /* lines only in the left file */ 
                *foreground = rgb_leftfore; 
                *background = rgb_leftback; 
                return(P_FCOLOUR|P_BCOLOUR); 
 
        case STATE_RIGHTONLY: 
                /* lines only in the right file */ 
                *foreground = rgb_rightfore; 
                *background = rgb_rightback; 
                return(P_FCOLOUR|P_BCOLOUR); 
 
        case STATE_MOVEDLEFT: 
                /* displaced lines in both files - left file version */ 
                *foreground = rgb_mleftfore; 
                *background = rgb_mleftback; 
                return(P_FCOLOUR|P_BCOLOUR); 
 
        case STATE_MOVEDRIGHT: 
                /* displaced lines in both files - right file version */ 
                *foreground = rgb_mrightfore; 
                *background = rgb_mrightback; 
                return(P_FCOLOUR|P_BCOLOUR); 
 
        default: 
 
                /* no highlighting - default colours */ 
                return(0); 
        } 
 
} 
 
/* table window communication routines ---------------------------------*/ 
 
/*************************************************************************** 
 * Function: SetSelection 
 * 
 * Purpose: 
 * 
 * Set a given row as the selected row in the table window  
 */ 
void 
SetSelection(long rownr) 
{ 
        TableSelection select; 
 
        select.startrow = rownr; 
        select.startcell = 0; 
        select.nrows = 1; 
        select.ncells = 1; 
        SendMessage(hwndRCD, TM_SELECT, 0, (long) (LPSTR)&select); 
} 
 
 
/*************************************************************************** 
 * Function: do_gethdr 
 * 
 * Purpose: 
 * 
 * Handle table class call back to get nr of rows and columns, 
 * and properties for the whole table. 
 * The 'table id' is either TABID_PRINTER - meaning we are 
 * printing the current_view, or it is the view to 
 * use for row/column nr information 
 */ 
long 
do_gethdr(HWND hwnd, lpTableHdr phdr) 
{ 
        VIEW view; 
        BOOL bIsPrinter = FALSE; 
 
        if (phdr->id == TABID_PRINTER) { 
                view = current_view; 
                bIsPrinter = TRUE; 
        } else { 
                view = (VIEW) phdr->id; 
        } 
        if (view == NULL) { 
                return(FALSE); 
        } 
 
        phdr->nrows = view_getrowcount(view); 
 
        /*  three columns: line nr, tag and rest of line */ 
 
        /* 
         * if IDM_NONRS (no line numbers) is selected, suppress the 
         * line-nr column entirely to save screen space 
         */ 
        if (line_numbers == IDM_NONRS) { 
                phdr->ncols = 2; 
                phdr->fixedcols = 0; 
        } else { 
                phdr->ncols = 3; 
                phdr->fixedcols = 1; 
        } 
 
        phdr->fixedrows = 0; 
        phdr->fixedselectable = FALSE; 
        phdr->hseparator = TRUE; 
        phdr->vseparator = TRUE; 
 
        phdr->selectmode = TM_ROW | TM_SINGLE; 
        /* 
         * find if we are in expand mode - ask for the item we are expanding. 
         */ 
        if (view_isexpanded(view) == TRUE) { 
 
                /* use focus rect as selection mode in expand mode 
                 * so as not to interfere with background colours. 
                 */ 
                phdr->selectmode |= TM_FOCUS; 
        } else { 
                /* use default solid inversion when possible as it is clearer.*/ 
                phdr->selectmode |= TM_SOLID; 
        } 
 
        /* please send TQ_SCROLL notifications when the table is scrolled */ 
        phdr->sendscroll = TRUE; 
        phdr->props.valid = 0; 
 
        return TRUE; 
} 
 
/*************************************************************************** 
 * Function: do_getprops 
 * 
 * Purpose: 
 * 
 * Respond to table callback asking for the size and properties 
 * of each column. Table id is either TABID_PRINTER (meaning the 
 * current_view, for printing) or it is the view to be used. 
 */ 
long 
do_getprops(HWND hwnd, lpColPropsList propslist) 
{ 
        int i, cell; 
        BOOL bIsPrinter = FALSE; 
        VIEW view; 
 
        if (propslist->id == TABID_PRINTER) { 
                view = current_view; 
                bIsPrinter = TRUE; 
        } else { 
                view = (VIEW) propslist->id; 
        } 
        if (view == NULL) { 
                return(FALSE); 
        } 
 
        /* The table inteface is slightly confused here. we are not 
         * guaranteed which columns we are being asked about, so instead 
         * of just setting each column cols[0], cols[1] etc, we need 
         * to loop through, looking at each column in the table and 
         * seeing which it is. 
         */ 
        for (i = 0; i < propslist->ncols; i++) { 
                cell = i + propslist->startcol; 
                propslist->plist[i].props.valid = 0; 
 
                /* for all column widths, add on 1 for the NULL char. */ 
 
                /* 
                 * skip the line nr column if IDM_NONRS 
                 */ 
                if (line_numbers == IDM_NONRS) { 
                        cell++; 
                } 
 
                if (cell == 0) { 
                        /* properties for line nr column */ 
 
                        propslist->plist[i].nchars = view_getwidth(view, 0)+1; 
                        propslist->plist[i].props.valid |= P_ALIGN; 
                        propslist->plist[i].props.alignment = P_CENTRE; 
                } else if (cell == 1) { 
 
                        /* properties for tag field */ 
                        propslist->plist[i].nchars = view_getwidth(view, 1)+1; 
                        propslist->plist[i].props.valid |= P_ALIGN; 
                        propslist->plist[i].props.alignment = P_LEFT; 
                } else { 
                        /* properties for main text column - 
                         * use a fixed font unless printing (if 
                         * printing, best to use the default font, because 
                         * of resolution differences. 
                         * add on 8 chars to the width to ensure that 
                         * the width of lines beginning with tabs 
                         * works out ok 
                         */ 
                        propslist->plist[i].nchars = view_getwidth(view, 2)+1; 
                        propslist->plist[i].props.valid |= P_ALIGN; 
                        propslist->plist[i].props.alignment = P_LEFT; 
                        if (!bIsPrinter) { 
                                propslist->plist[i].props.valid |= P_FONT; 
                                propslist->plist[i].props.hFont = 
                                        GetStockObject(SYSTEM_FIXED_FONT); 
                        } 
                } 
        } 
        return (TRUE); 
} 
 
/*************************************************************************** 
 * Function: do_getdata 
 * 
 * Purpose: 
 * 
 * Respond to a table callback asking for the contents of individual cells. 
 * table id is either TABID_PRINTER, or it is a pointer to the view 
 * to use for data. If going to the printer, don't set the 
 * colours (stick to black and white). 
 */ 
long 
do_getdata(HWND hwnd, lpCellDataList cdlist) 
{ 
        int start, endcell, col, i; 
        lpCellData cd; 
        VIEW view; 
        LPSTR textp; 
        BOOL bIsPrinter = FALSE; 
 
        if (cdlist->id == TABID_PRINTER) { 
                view = current_view; 
                bIsPrinter = TRUE; 
        } else { 
                view = (VIEW) cdlist->id; 
        } 
 
        start = cdlist->startcell; 
        endcell = cdlist->ncells + start; 
        if (cdlist->row >= view_getrowcount(view)) { 
                return(FALSE); 
        } 
        for (i = start; i < endcell; i++) { 
                cd = &cdlist->plist[i - start]; 
 
 
                /* skip the line number column if IDM_NONRS */ 
                if (line_numbers == IDM_NONRS) { 
                        col = i+1; 
                } else { 
                        col = i; 
                } 
 
                /* set colour of text to mark out 
                 * lines that are changed, if not printer - for the 
                 * printer everything should stay in the default colours 
                 */ 
 
                if (!bIsPrinter) { 
 
                        /* convert the state of the requested row into a 
                         * colour scheme. returns P_FCOLOUR and/or 
                         * P_BCOLOUR if it sets either of the colours 
                         */ 
                        cd->props.valid |= 
                            StateToColour(view_getstate(view, cdlist->row), col, 
                                        &cd->props.forecolour, 
                                        &cd->props.backcolour); 
                } 
 
                textp = view_gettext(view, cdlist->row, col); 
                if (cd->nchars != 0) { 
                        if (textp == NULL) { 
                                cd->ptext[0] = '\0'; 
                        } else { 
                                strncpy(cd->ptext, textp, cd->nchars -1); 
                                cd->ptext[cd->nchars - 1] = '\0'; 
                        } 
                } 
 
        } 
        return(TRUE); 
} 
 
/*************************************************************************** 
 * Function: SvrClose 
 * 
 * Purpose: 
 * 
 * Table window has finished with this view. It can be deleted. 
 */ 
void 
SvrClose(void) 
{ 
        view_delete(current_view); 
        current_view = NULL; 
 
        /* hide picture - only visible when we are in MODE_EXPAND */ 
        DisplayMode = MODE_NULL; 
        DoResize(hwndClient); 
 
        /* if we already busy when closing this view (ie 
         * we are in the process of starting a new scan, 
         * then leave the status bar alone, otherwise 
         * we should clean up the state of the status bar 
         */ 
        if (!fBusy) { 
                SetButtonText(LoadRcString(IDS_EXIT)); 
                SetNames(NULL); 
                SetStatus(NULL); 
 
        } 
 
} /* SvrClose */ 
 
 
/*************************************************************************** 
 * Function: TableServer 
 * 
 * Purpose: 
 * 
 * Handle callbacks and notifications from the table class  
 */ 
long 
TableServer(HWND hwnd, UINT cmd, long lParam) 
{ 
        lpTableHdr phdr; 
        lpColPropsList proplist; 
        lpCellDataList cdlist; 
        lpTableSelection pselect; 
 
        switch(cmd) { 
        case TQ_GETSIZE: 
                /* get the nr of rows and cols in this table */ 
                phdr = (lpTableHdr) lParam; 
                return(do_gethdr(hwnd, phdr)); 
 
        case TQ_GETCOLPROPS: 
                /* get the size and properties of each column */ 
                proplist = (lpColPropsList) lParam; 
                return (do_getprops(hwnd, proplist)); 
 
        case TQ_GETDATA: 
                /* get the contents of individual cells */ 
                cdlist = (lpCellDataList) lParam; 
                return (do_getdata(hwnd, cdlist)); 
 
 
        case TQ_SELECT: 
                /* selection has changed */ 
        case TQ_ENTER: 
                /* user has double-clicked or pressed enter */ 
 
                pselect = (lpTableSelection) lParam; 
 
                /* store location for use in later search (IDM_FCHANGE) */ 
                if (pselect->nrows < 1) { 
                        selection = -1; 
                } else { 
                        selection = (int) pselect->startrow; 
                        if (cmd == TQ_ENTER) { 
                                /* try to expand this row */ 
                                if (!ToExpand(hwnd)) { 
                                        /* expand failed - maybe this 
                                         * is a moved line- show the other 
                                         * copy 
                                         */ 
                                        ToMoved(hwnd); 
                                } 
 
                        } 
                } 
                break; 
 
        case TQ_CLOSE: 
                /* close this table - table class no longer needs data*/ 
                SvrClose(); 
                break; 
 
        case TQ_SCROLL: 
                /* notification that the rows visible in the window 
                 * have changed -change the current position lines in 
                 * the graphic bar view (the sections picture) 
                 */ 
                if (picture_mode) { 
                        BarDrawPosition(hwndBar, NULL, TRUE); 
                } 
                break; 
 
        default: 
                return(FALSE); 
        } 
        return(TRUE); 
} 
 
 
/*************************************************************************** 
 * Function: wd_initial 
 * 
 * Purpose: 
 * 
 * Called on worker thread (not UI thread) to handle the work 
 * requested on the command line.  
 * arg is a pointer to a THREADARGS block allocated from gmem_get(hHeap). This 
 * needs to be freed before exiting. 
 */ 
DWORD 
wd_initial(LPVOID arg) 
{ 
        PTHREADARGS pta = (PTHREADARGS) arg; 
        COMPLIST cl; 
 
 
        /* build a complist from these args, 
         * and register with the view we have made 
         */ 
        cl = complist_args(pta->first, pta->second, pta->view, pta->fDeep); 
 
        if (cl == NULL) { 
                view_close(pta->view); 
                gmem_free(hHeap, (LPSTR) pta, sizeof(THREADARGS)); 
                SetNotBusy(); 
                return 0; 
        } 
 
 
        /* if savelist was selected, write out the list and exit */ 
        if(pta->savelist != NULL) { 
                complist_savelist(cl, pta->savelist, pta->saveopts); 
                gmem_free(hHeap, (LPSTR) pta, sizeof(THREADARGS)); 
                SetNotBusy(); 
                exit(0); 
        } 
 
        /* if there was only one file, expand it */ 
        if (view_getrowcount(pta->view) == 1) { 
                SetSelection(0); 
                ToExpand(hwndClient); 
        } 
 
 
        gmem_free(hHeap, (LPSTR) pta, sizeof(THREADARGS)); 
        SetNotBusy(); 
        return(0); 
} /* wd_initial */ 
 
 
/*************************************************************************** 
 * Function: wd_dirdialog 
 * 
 * Purpose: 
 * 
 * Called on worker thread (not UI thread) to handle a Dir request 
 */ 
DWORD 
wd_dirdialog(LPVOID arg) 
{ 
 
        VIEW view = (VIEW) arg; 
 
        /* make a COMPLIST using the directory dialog, 
         * and notify the view 
         */ 
        if (complist_dirdialog(view) == NULL) { 
                view_close(view); 
        } 
 
        /* all done! */ 
        SetNotBusy(); 
        return(0); 
} 
 
 
/*************************************************************************** 
 * Function: wd_copy 
 * 
 * Purpose: 
 * 
 * Called on worker thread to do a copy-files operation 
 */ 
DWORD 
wd_copy(LPVOID arg) 
{ 
 
        VIEW view = (VIEW) arg; 
 
        complist_copyfiles(view_getcomplist(view), NULL, 0); 
 
        SetNotBusy(); 
 
        return(0); 
} 
 
 
/*************************************************************************** 
 * Function: MainWndProc 
 * 
 * Purpose: 
 * 
 * Window processing for main window 
 */ 
long APIENTRY 
MainWndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam) 
{ 
        char str[32]; 
        long ret; 
        DWORD threadid; 
 
        switch(message) { 
 
 
        case WM_CREATE: 
 
                /* initialise menu options to default/saved 
                 * option settings 
                 */ 
 
                CheckMenuItem(hMenu, IDM_INCSAME, 
                      (outline_include & INCLUDE_SAME) ? 
                                MF_CHECKED:MF_UNCHECKED); 
 
                CheckMenuItem(hMenu, IDM_INCLEFT, 
                      (outline_include & INCLUDE_LEFTONLY) ? 
                                MF_CHECKED:MF_UNCHECKED); 
 
                CheckMenuItem(hMenu, IDM_INCRIGHT, 
                      (outline_include & INCLUDE_RIGHTONLY) ? 
                                MF_CHECKED:MF_UNCHECKED); 
                CheckMenuItem(hMenu, IDM_INCDIFFER, 
                      (outline_include & INCLUDE_DIFFER) ? 
                                MF_CHECKED:MF_UNCHECKED); 
 
                CheckMenuItem(hMenu, line_numbers, MF_CHECKED); 
                CheckMenuItem(hMenu, expand_mode, MF_CHECKED); 
 
                CheckMenuItem(hMenu, IDM_IGNBLANKS, 
                        ignore_blanks ? MF_CHECKED : MF_UNCHECKED); 
                CheckMenuItem(hMenu, IDM_PICTURE, 
                        picture_mode ? MF_CHECKED : MF_UNCHECKED); 
 
                /* nothing currently displayed */ 
                DisplayMode = MODE_NULL; 
 
                break; 
 
 
        case WM_COMMAND: 
                switch (GET_WM_COMMAND_ID(wParam, lParam)) { 
                case IDM_EXIT: 
                        if (ghThread!=NULL) { 
                                extern CRITICAL_SECTION CSView; 
                                /* Stop any other thread from allocating things that we 
                                   want to free!  See the threads DOGMA at the top 
                                   of this file. 
                                */ 
 
                                /* Because the thread that we are about to kill might be in 
                                   a critical section, we first grab them both.  It is 
                                   essential that anyone else who ever does this, does 
                                   so in the same order! 
                                */ 
                                WDEnter(); 
                                EnterCriticalSection(&CSView); 
                                TerminateThread(ghThread, 31); 
                                CloseHandle(ghThread); 
                                ghThread = NULL; 
                                LeaveCriticalSection(&CSView); 
                                WDLeave(); 
                        } 
                        if (!view_isexpanded(current_view)) { 
                                /* save the current outline size and position */ 
                                WINDOWPLACEMENT wp; 
                                if (GetWindowPlacement(hwndClient,&wp)) { 
                                        WriteProfileInt(APPNAME, "OutlineShowCmd", wp.showCmd); 
                                        WriteProfileInt(APPNAME, "OutlineMaxX", wp.ptMaxPosition.x); 
                                        WriteProfileInt(APPNAME, "OutlineMaxY", wp.ptMaxPosition.y); 
                                        WriteProfileInt(APPNAME, "OutlineNormLeft", wp.rcNormalPosition.left); 
                                        WriteProfileInt(APPNAME, "OutlineNormTop", wp.rcNormalPosition.top); 
                                        WriteProfileInt(APPNAME, "OutlineNormRight", wp.rcNormalPosition.right); 
                                        WriteProfileInt(APPNAME, "OutlineNormBottom", wp.rcNormalPosition.bottom); 
                                        WriteProfileInt(APPNAME, "OutlineSaved", 1); 
                                } 
                        } else { 
                                /* save the current expanded size and position */ 
                                WINDOWPLACEMENT wp; 
                                if (GetWindowPlacement(hwndClient,&wp)) { 
                                        WriteProfileInt(APPNAME, "ExpandShowCmd", wp.showCmd); 
                                        WriteProfileInt(APPNAME, "ExpandMaxX", wp.ptMaxPosition.x); 
                                        WriteProfileInt(APPNAME, "ExpandMaxY", wp.ptMaxPosition.y); 
                                        WriteProfileInt(APPNAME, "ExpandNormLeft", wp.rcNormalPosition.left); 
                                        WriteProfileInt(APPNAME, "ExpandNormTop", wp.rcNormalPosition.top); 
                                        WriteProfileInt(APPNAME, "ExpandNormRight", wp.rcNormalPosition.right); 
                                        WriteProfileInt(APPNAME, "ExpandNormBottom", wp.rcNormalPosition.bottom); 
                                        WriteProfileInt(APPNAME, "ExpandedSaved", 1); 
                                } 
                        } 
                        DestroyWindow(hWnd); 
                        break; 
 
                case IDM_ABORT: 
                        /* abort menu item, or status bar button. 
                         * the status bar button text gives the appropriate 
                         * action depending on our state - abort, outline 
                         * or expand. But the command sent is always 
                         * IDM_ABORT. Thus we need to check the state 
                         * to see what to do. If we are busy, set the abort 
                         * flag. If there is nothing to view, 
                         * exit, otherwise switch outline<->expand 
                         */ 
                        if (IsBusy()) { 
                                bAbort = TRUE; 
                                SetStatus(LoadRcString(IDS_ABORT_PENDING)); 
 
                        } else if (DisplayMode == MODE_NULL) { 
                                DestroyWindow(hWnd); 
                        } else if (DisplayMode == MODE_EXPAND) { 
                                ToOutline(hWnd); 
                        } else { 
                                ToExpand(hWnd); 
                        } 
                        break; 
 
                case IDM_FILE: 
                        /* select two files and compare them */ 
                        if (SetBusy()) { 
 
                               /* close the current view */ 
                                view_close(current_view); 
 
                                /* make a new empty view */ 
                                current_view = view_new(hwndRCD); 
 
                                /* make a COMPLIST using the files dialog, 
                                 * and notify the view 
                                 */ 
                                if (complist_filedialog(current_view) == NULL) { 
                                        view_close(current_view); 
                                } 
 
                                /* all done! */ 
                                SetNotBusy(); 
                        } else { 
                                BusyError(); 
                        } 
                        break; 
 
                case IDM_DIR: 
 
                        /* read two directory names, scan them and 
                         * compare all the files and subdirs. 
                         */ 
                        if (SetBusy()) { 
 
                                /* close the current view */ 
                                view_close(current_view); 
 
                                /* make a new empty view */ 
                                current_view = view_new(hwndRCD); 
 
                                ghThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)wd_dirdialog, 
                                        (LPVOID) current_view, 0, &threadid); 
 
                                if (ghThread == NULL) 
                                { 
                                        wd_dirdialog( (LPVOID) current_view); 
                                } 
 
                        } else { 
                                BusyError(); 
                        } 
                        break; 
 
                case IDM_CLOSE: 
                        /* close the output list - 
                         * discard all results so far 
                         */ 
                        if (!IsBusy()) { 
                                view_close(current_view); 
                        } 
                        break; 
 
                case IDM_PRINT: 
                        /* print the current view - 
                         * either the outline list of filenames, 
                         * or the currently expanded file. 
                         */ 
                        if (!IsBusy()) { 
                                DoPrint(); 
                        } else { 
                                BusyError(); 
                        } 
                        break; 
 
                case IDM_TIME: 
                        /* show time it took */ 
                        {       char msg[50]; 
                                DWORD tim; 
                                if (IsBusy()) { 
                                         BusyError(); 
                                } 
                                else{ 
                                        tim = complist_querytime(); 
                                        wsprintf((LPTSTR)msg, LoadRcString(IDS_SECONDS), tim/1000, tim%1000); 
                                } 
                        } 
                        break; 
 
                case IDM_SAVELIST: 
                        /* allow user to save list of same/different files 
                         * to a text file. dialog box to give filename 
                         * and select which types of file to include 
                         */ 
                        complist_savelist(view_getcomplist(current_view), NULL, 0); 
                        break; 
 
                case IDM_COPYFILES: 
                        /* 
                         * copy files that are same/different to a new 
                         * root directory. dialog box allows user 
                         * to select new root and inclusion options 
                         */ 
                        if (current_view == NULL) { 
                                MessageBox(hWnd, 
                                    LoadRcString(IDS_CREATE_DIFF_LIST), 
                                    "WinDiff", MB_OK|MB_ICONSTOP); 
                                break; 
                        } 
 
                        if (SetBusy()) { 
                                ghThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)wd_copy, 
                                        (LPVOID) current_view, 0, &threadid); 
                                if (ghThread == NULL) 
                                { 
                                        wd_copy( (LPVOID) current_view); 
                                } 
 
                        } else { 
                                BusyError(); 
                        } 
 
                        break; 
 
                case IDM_ABOUT: 
 
                        DialogBox( hInst, "About", hWnd, (DLGPROC)AboutBox); 
                        break; 
 
                /* launch an editor on the current item - left, right or 
                 * composite view 
                 */ 
                case IDM_EDITLEFT: 
                        do_editthread(current_view, CI_LEFT); 
                        break; 
 
                case IDM_EDITRIGHT: 
                        do_editthread(current_view, CI_RIGHT); 
                        break; 
 
                case IDM_EDITCOMP: 
                        do_editthread(current_view, CI_COMP); 
                        break; 
 
                /* allow customisation of the editor command line */ 
                case IDM_SETEDIT: 
                        if (StringInput(editor_cmdline, sizeof(editor_cmdline), 
                                        LoadRcString(IDS_ENTER_EDT_CMD_LINE), 
                                        "Windiff", editor_cmdline))  { 
                                WriteProfileString(APPNAME, "Editor", 
                                        (LPCSTR)editor_cmdline); 
                        } 
                        break; 
 
 
                case IDM_LNRS: 
                case IDM_RNRS: 
                case IDM_NONRS: 
 
                        /* option selects whether the line nrs displayed 
                         * in expand mode are the line nrs in the left 
                         * file, the right file or none 
                         */ 
 
                        CheckMenuItem(GetMenu(hWnd), 
                                line_numbers, MF_UNCHECKED); 
                        line_numbers = GET_WM_COMMAND_ID(wParam, lParam); 
                        CheckMenuItem(GetMenu(hWnd), line_numbers, MF_CHECKED); 
                        wsprintf((LPTSTR)str, "%d", line_numbers); 
                        WriteProfileString(APPNAME, "LineNumbers", str); 
 
                        /* change the display to show the line nr style 
                         * chosen 
                         */ 
 
                        view_changeviewoptions(current_view); 
 
 
                        break; 
 
                /* 
                 * options selecting which files to include in the 
                 * outline listing, based on their state 
                 */ 
                case IDM_INCLEFT: 
 
 
                        /* toggle flag in outline_include options */ 
                        outline_include ^= INCLUDE_LEFTONLY; 
 
                        /* check/uncheck as necessary */ 
                        CheckMenuItem(hMenu, IDM_INCLEFT, 
                              (outline_include & INCLUDE_LEFTONLY) ? 
                                        MF_CHECKED:MF_UNCHECKED); 
 
                        wsprintf((LPTSTR)str, "%d", outline_include); 
                        WriteProfileString(APPNAME, "FileInclude", str); 
                        view_changeviewoptions(current_view); 
 
 
                        break; 
 
                case IDM_INCRIGHT: 
 
 
                        outline_include ^= INCLUDE_RIGHTONLY; 
 
                        CheckMenuItem(hMenu, IDM_INCRIGHT, 
                              (outline_include & INCLUDE_RIGHTONLY) ? 
                                        MF_CHECKED:MF_UNCHECKED); 
                        wsprintf((LPTSTR)str, "%d", outline_include); 
                        WriteProfileString(APPNAME, "FileInclude", str); 
                        view_changeviewoptions(current_view); 
 
                        break; 
 
                case IDM_INCSAME: 
 
 
                        outline_include ^= INCLUDE_SAME; 
 
                        CheckMenuItem(hMenu, IDM_INCSAME, 
                              (outline_include & INCLUDE_SAME) ? 
                                        MF_CHECKED:MF_UNCHECKED); 
                        wsprintf((LPTSTR)str, "%d", outline_include); 
                        WriteProfileString(APPNAME, "FileInclude", str); 
                        view_changeviewoptions(current_view); 
 
 
                        break; 
 
 
                case IDM_INCDIFFER: 
 
 
 
                        outline_include ^= INCLUDE_DIFFER; 
 
                        CheckMenuItem(hMenu, IDM_INCDIFFER, 
                              (outline_include & INCLUDE_DIFFER) ? 
                                        MF_CHECKED:MF_UNCHECKED); 
 
                        wsprintf((LPTSTR)str, "%d", outline_include); 
                        WriteProfileString(APPNAME, "FileInclude", str); 
                        view_changeviewoptions(current_view); 
 
 
                        break; 
 
                case IDM_UPDATE: 
                        /* update the display.  Options or files may have changed */ 
                        /* discard lines  (thereby forcing re-read). 
                         */ 
                        file_discardlines(compitem_getleftfile( (COMPITEM)lParam) ); 
                        file_discardlines(compitem_getrightfile( (COMPITEM)lParam) ); 
 
                        view_changediffoptions(current_view); 
 
                        /* force repaint of bar window */ 
                        InvalidateRect(hwndBar, NULL, TRUE); 
                        break; 
 
 
 
                case IDM_LONLY: 
                case IDM_RONLY: 
                case IDM_BOTHFILES: 
                        /* option selects whether the expanded file 
                         * show is the combined file, or just one 
                         * or other of the input files. 
                         * 
                         * if we are not in expand mode, this also 
                         * causes us to expand the selection 
                         */ 
 
 
                        CheckMenuItem(GetMenu(hWnd), expand_mode, MF_UNCHECKED); 
                        expand_mode = GET_WM_COMMAND_ID(wParam, lParam); 
                        CheckMenuItem(GetMenu(hWnd), expand_mode, MF_CHECKED); 
 
                        /* change the current view to show only the lines 
                         * of the selected type. 
                         */ 
                        if (DisplayMode == MODE_OUTLINE) { 
                                ToExpand(hWnd); 
                        } else { 
                                view_changeviewoptions(current_view); 
                        } 
 
 
break; 
 
 
                case IDM_IGNBLANKS: 
 
                        /* if selected, ignore all spaces and tabs on 
                         * comparison - expand view only: outline view 
                         * will still show that 'text files differ' 
                         */ 
 
                        ignore_blanks = !ignore_blanks; 
                        CheckMenuItem(hMenu, IDM_IGNBLANKS, 
                                ignore_blanks? MF_CHECKED:MF_UNCHECKED); 
                        wsprintf((LPTSTR)str, "%d", ignore_blanks); 
                        WriteProfileString(APPNAME, "Blanks", str); 
 
                        /* invalidate all diffs since we have 
                         * changed diff options, and re-do and display the 
                         * current diff if we are in expand mode. 
                         */ 
                        view_changediffoptions(current_view); 
 
                        /* force repaint of bar window */ 
                        InvalidateRect(hwndBar, NULL, TRUE); 
 
                        break; 
 
                case IDM_PICTURE: 
                        /* do we show the bar picture in expand mode ? */ 
                        picture_mode = !picture_mode; 
                        CheckMenuItem(hMenu, IDM_PICTURE, 
                                picture_mode? MF_CHECKED:MF_UNCHECKED); 
                        wsprintf((LPTSTR)str, "%d", picture_mode); 
                        WriteProfileString(APPNAME, "Picture", str); 
                        DoResize(hWnd); 
                        break; 
 
 
                case IDM_EXPAND: 
 
                        /* show the expanded view of the 
                         * selected file 
                         */ 
                        if (current_view != NULL) { 
                                ToExpand(hWnd); 
                        } 
 
                        break; 
 
                case IDM_OUTLINE: 
                        /* return to the outline view (list of filenames) */ 
                        ToOutline(hWnd); 
 
                        break; 
 
                case IDM_FCHANGE: 
                        /* find the next line in the current view 
                         * that is not the same in both files - 
                         * in outline view, finds the next filename that 
                         * is not identical 
                         */ 
                        FindNextChange(); 
 
                        break; 
 
                case IDM_FPCHANGE: 
                        /* same as IDM_FCHANGE, but going backwards from 
                         * current position 
                         */ 
                        FindPrevChange(); 
 
                        break; 
                } 
                break; 
 
        case WM_SIZE: 
                DoResize(hWnd); 
                break; 
 
        case WM_SETFOCUS: 
                /* set the focus on the table class so it can process 
                 * page-up /pagedown keys etc. 
                 */ 
                SetFocus(hwndRCD); 
                break; 
 
        case WM_KEYDOWN: 
                /* although the table window has the focus, he passes 
                 * back to us any keys he doesn't understand 
                 * We handle escape here to mean 'return to outline view' 
                 */ 
                if (wParam == VK_ESCAPE) { 
                        ToOutline(hWnd); 
                } 
                break; 
 
        case WM_CLOSE: 
                /* don't allow close when busy - process this message in 
                 * order to ensure this 
                 */ 
                if (IsBusy()) { 
                        return(TRUE); 
                } else { 
                        return(DefWindowProc(hWnd, message, wParam, lParam)); 
                } 
                break; 
 
        case WM_DESTROY: 
 
                DeleteTools(); 
                PostQuitMessage(0); 
                break; 
 
        case TM_CURRENTVIEW: 
                /* allow other people such as the bar window to query the 
                 * current view 
                 */ 
                return((DWORD) current_view); 
 
        default: 
                /* handle registered table messages */ 
                if (message == table_msgcode) { 
                        ret = TableServer(hWnd, wParam, lParam); 
                        return(ret); 
                } 
                return(DefWindowProc(hWnd, message, wParam, lParam)); 
        } 
        return(0); 
} 
 
/*************************************************************************** 
 * Function: My_mbschr 
 * 
 * Purpose: 
 * 
 * DBCS version of strchr 
 * 
 */ 
unsigned char * _CRTAPI1 My_mbschr( 
    unsigned char *psz, unsigned short uiSep) 
{ 
    while (*psz != '\0' && *psz != uiSep) { 
        psz = CharNext(psz); 
    } 
    return *psz == uiSep ? psz : NULL; 
} 
/*************************************************************************** 
 * Function: My_mbsncpy 
 * 
 * Purpose: 
 * 
 * DBCS version of strncpy 
 * 
 */ 
unsigned char * _CRTAPI1 My_mbsncpy( 
unsigned char *psz1, const unsigned char *psz2, size_t Length) 
{ 
        int nLen = (int)Length; 
unsigned char *pszSv = psz1; 
 
while (0 < nLen) { 
if (*psz2 == '\0') { 
*psz1++ = '\0'; 
nLen--; 
} else if (IsDBCSLeadByte(*psz2)) { 
if (nLen == 1) { 
*psz1 = '\0'; 
} else { 
*psz1++ = *psz2++; 
*psz1++ = *psz2++; 
} 
nLen -= 2; 
} else { 
*psz1++ = *psz2++; 
nLen--; 
} 
} 
return pszSv; 
} 
/*************************************************************************** 
 * Function: My_mbsrchr 
 * 
 * Purpose: 
 * 
 * DBCS version of strrchr 
 * 
 */ 
unsigned char * _CRTAPI1 My_mbsrchr( 
    unsigned char *psz, unsigned short uiSep) 
{ 
    unsigned char *pszHead; 
 
    pszHead = psz; 
 
    while (*psz != '\0') { 
        psz++; 
    } 
    if (uiSep == '\0') { 
        return psz; 
    } 
 
    while (psz > pszHead) { 
        psz = CharPrev(pszHead, psz); 
        if (*psz == uiSep) { 
            break; 
        } 
    } 
    return *psz == uiSep ? psz : NULL; 
} 
/*************************************************************************** 
 * Function: My_mbsncmp 
 * 
 * Purpose: 
 * 
 * DBCS version of strncmp 
 * If 'nLen' splits a DBC, this function compares the DBC's 2nd byte also. 
 * 
 */ 
int _CRTAPI1 My_mbsncmp( 
    const unsigned char *psz1, const unsigned char *psz2, size_t nLen) 
{ 
    int Length = (int)nLen; 
 
    while (0 < Length) { 
        if ('\0' == *psz1 || '\0' == *psz2) { 
            return *psz1 - *psz2; 
        } 
        if (IsDBCSLeadByte(*psz1) || IsDBCSLeadByte(*psz2)) { 
            if (*psz1 != *psz2 || *(psz1+1) != *(psz2+1)) { 
                return *psz1 - *psz2; 
            } 
            psz1 += 2; 
            psz2 += 2; 
            Length -= 2; 
        } else { 
            if (*psz1 != *psz2) { 
                return *psz1 - *psz2; 
            } 
            psz1++; 
            psz2++; 
            Length--; 
        } 
    } 
    return 0; 
}