
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993-1995 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 WINAPI 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,
                "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;
        if (msg==NULL)
                msg = "windiff {-L} path1 {path2} {-s{slrd} savefile}";

        retval = MessageBox(hwndClient,
                msg,
                "Windiff Usage", 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_DIFFER;
                                                break;
                                        case 'r':
                                        case 'R':
                                                ta->saveopts |= INCLUDE_LEFTONLY;
                                                break;
                                        case 'd':
                                        case 'D':
                                                ta->saveopts |= INCLUDE_RIGHTONLY;
                                                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;

        /* 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;
        foot.ptext = "Page # of $";
        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 + 1;
        } 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, "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 - 1;
        } 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, "No Previous 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) {

                        /* 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("Outline");
                }

                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) {
                SetButtonText("Expand");
                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 WINAPI
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, "File does not 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:
                    *pOut++ = *pIn;
                    break;
                }
                pIn++;
                break;

            default:
                *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, "Failed to launch editor",
                        "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("Comparing...");
        /* status also on window text, so that you can see even from
         * the icon when the scan has finished
         */
        SetWindowText(hwndClient, "WinDiff: 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("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("Exit");
                SetStatus(NULL);
                DisplayMode = MODE_NULL;
        } else if (view_isexpanded(current_view)) {
                SetButtonText("Outline");
                SetStatus(view_getcurrenttag(current_view) );
                DisplayMode = MODE_EXPAND;
        } else {
                SetButtonText("Expand");
                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,
                "Please wait for current operation to finish",
                "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("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 WINAPI
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 WINAPI
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 WINAPI
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("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, "%d.%03d 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,
                                    "Please create a diff list first",
                                    "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),
                                        "Please enter the editor command 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);
}

