/******************************************************************************\ * This is a part of the Microsoft Source Code Samples. * Copyright (C) 1993-1997 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 #include #include #include #include #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; }