// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. /* * view.cpp * * map rows in window to items in COMPLIST * * * A view owns a COMPLIST, and talks to a table window. The table window * shows 3 columns: line nr, tag and text. We also need to supply a state * for each row (used to select colour scheme). * * The COMPLIST can give us a list of its COMPITEMs. Each of these can give * us a tag (eg the filenames compared) and the text (usually the compare * result), and the state. We make the line number from the * COMPITEM's place in the list. * * If we are asked to switch to 'expand' mode, we ask the selected COMPITEM * for its composite section list. We can then get the state (and thus * the tag) from each SECTION, and the line nr and text from the LINEs within * each section. * * When moving between expand and outline, and when refreshing the view * for some option change, we have to be careful to keep the current row * and the selected row in the table what the user would expect (!) * * * WIN32: Functions in this module can be called from the UI thread (to refresh * the display) and simultaneously from a worker thread to update the * view mapping (view_setcomplist, view_newitem). We use a critical section * to manage the synchronisation. We need to protect all access/modification * to the view structure elements (particularly bExpand, rows, pLines and * pItems), BUT we must not hold the critical section over any calls * to SendMessage. * */ #include "precomp.h" #include "table.h" #include "state.h" #include "sdkdiff.h" #include "wdiffrc.h" #include "list.h" #include "line.h" #include "scandir.h" #include "file.h" #include "section.h" #include "compitem.h" #include "complist.h" #include "findgoto.h" #include "view.h" /* * data structures */ #ifdef WIN32 #define huge #endif /* in expand mode, we keep an array of one of these per screen line. */ typedef struct viewline { LINE line; /* handle to LINE for this row */ SECTION section; /* handle to section containing this line */ int nr_left; /* line nr in left file */ int nr_right; /* line nr in right file */ } VIEWLINE, * PVIEWLINE; /* * The users VIEW handle is in fact a pointer to this structure */ struct view { HWND hwnd; /* the table window to send notifies to */ COMPLIST cl; /* the complist that we own */ BOOL bExpand; /* true if we are in expand mode */ BOOL bExpanding; /* set by view_expandstart, reset by view_expand interrogated in sdkdiff.cpp, causes keystrokes to be ignored. Protects against mappings being messed up by another thread. (I have doubts about this - Laurie). */ BOOL bExpandGuard; /* Protects against two threads both trying to expand the same item. */ COMPITEM ciSelect; /* selected compitem (in expand mode) */ int rows; /* number of rows in this view */ char nrtext[12]; /* we use this in view_gettext for the line * number column. overwritten on each call */ int maxtag, maxrest;/* column widths in characters for cols 1, 2 */ /* if we are in outline mode, we map the row number to one entry * in this array of COMPITEM handles. this pointer will * be NULL in expand mode */ COMPITEM * pItems; /* in expand mode we use this array of line and section handles */ PVIEWLINE pLines; }; CRITICAL_SECTION CSView; /* also known to Sdkdiff.cpp WM_EXIT processing */ static BOOL bDoneInit = FALSE; #define ViewEnter() EnterCriticalSection(&CSView); #define ViewLeave() LeaveCriticalSection(&CSView); extern long selection; extern long selection_nrows; /*------- forward declaration of internal functions ----------------*/ void view_outline_opt(VIEW view, BOOL bRedraw, COMPITEM ci, int* prow); void view_freemappings(VIEW view); int view_findrow(VIEW view, int number, BOOL bRight); BOOL view_expand_item(VIEW view, COMPITEM ci); /* ----- externally-called functions---------------------------*/ /* view_new * * create a new view. at this point, we are told the table window handle, * and nothing else. * */ VIEW view_new(HWND hwndTable) { VIEW view; if (!bDoneInit) { InitializeCriticalSection(&CSView); bDoneInit = TRUE; } /* alloc the view using HeapAlloc */ view = (VIEW) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct view)); if (view == NULL) { return NULL; } /* set the default fields */ view->hwnd = hwndTable; view->cl = NULL; view->bExpand = FALSE; view->bExpandGuard = FALSE; view->ciSelect = NULL; view->rows = 0; view->pItems = NULL; view->pLines = NULL; // erase the message in the names field - the central box in the status bar SetNames(""); return(view); } /* * view_setcomplist. * * We have to separate view_new and view_setcomplist because we need * to give the view handle to the complist and the complist handle to the * view. So do a view_new to create a null view; then complist_new() to * which you pass a view handle. The complist will then register itself * with the view by calling this function. During the build of the complist, * it will also update us by calling view_additem, so that we can refresh * the display. * * Here we should initialise an outline view of the complist. * * We also talk to the status bar using SetNames to set the names of * the two items. */ BOOL view_setcomplist(VIEW view, COMPLIST cl) { LPSTR both; if (view == NULL) { return(FALSE); } /* there can be only one call to this per VIEW */ if (view->cl != NULL) { return(FALSE); } ViewEnter(); view->cl = cl; /* set names on status bar to root names of left and right trees */ both = complist_getdescription(cl); ViewLeave(); // LKGHACK SetNames(both); ViewEnter(); // LKGHACK complist_freedescription(cl, both); ViewLeave(); view_outline(view); return TRUE; } /* * return a handle to the complist owned by this view */ COMPLIST view_getcomplist(VIEW view) { if (view == NULL) { return(NULL); } return(view->cl); } /* * close a view. notify the table window that this view should be * closed. When the table window has finished with it, it will send * a TQ_CLOSE notify that should result in view_delete being called * and the memory being freed. */ void view_close(VIEW view) { if (view == NULL) { return; } SendMessage(view->hwnd, TM_NEWID, 0, 0); } /* * delete a view and all associated data. * * This function should only be called in response to the table window * sending a TQ_CLOSE message. To close the view, call view_close and * wait for the TQ_CLOSE before calling this. * * We delete the associated COMPLIST and all its associated structures. */ void view_delete(VIEW view) { if (view == NULL) { return; } /* we have two arrays that are used for the mapping - an array * of compitem handles in outline mode, and an array of * VIEWLINE structures in expand mode */ view_freemappings(view); complist_delete(view->cl); HeapFree(GetProcessHeap(), NULL, view); } /* * view_outline * * build an outline mode mapping where one row represents one COMPITEM in * the list. check the global option flag outline_include to see which items * we should include. * * If we were in expand mode, then set as the selection the row in outline mode * that we were expanding. Also remember to free up the expand mode mapping * array * * once we have built the new mapping, notify the table window to * redraw itself. */ void view_outline(VIEW view) { if (view == NULL) { return; } /* all work done by view_outline_opt - this function * gives us the option of not updating the display */ view_outline_opt(view, TRUE, NULL, NULL); } /* * switch to expand mode, expanding the given row into a view * of the differences in that file. * * map the given row nr into a compitem handle, and then * call the internal function with that. * * It is legal (and a no-op) if this function is called with * row==-1. */ BOOL view_expand(VIEW view, long row) { COMPITEM ci; BOOL bRet; if (row<0) return FALSE; ViewEnter(); if ((view == NULL) || (view->bExpand)) { /* no view, or already expanded */ ViewLeave(); return(FALSE); } if (row >= view->rows) { /* no such row */ ViewLeave(); return FALSE; } /* remember the compitem we are expanding */ ci = view->pItems[row]; bRet = view_expand_item(view, ci); return(bRet); } /* * return the text associated with a given column of a given row. * Return a pointer that does not need to be freed after use - ie * a pointer into our data somewhere, not a copy */ LPSTR view_gettext(VIEW view, long row, int col) { int line; int state; LPSTR pstr; HRESULT hr; pstr = NULL; /* kill spurious diagnostic */ if (view == NULL) { return(NULL); } ViewEnter(); if ((0 > row) || (row >= view->rows)) { ViewLeave(); return(NULL); } if (view->bExpand) { /* we are in expand mode */ state = section_getstate(view->pLines[row].section); switch (col) { case 0: /* row nr */ /* line numbers can be from either original file * this is a menu-selectable option */ line = 0; switch (line_numbers) { case IDM_NONRS: pstr = NULL; break; case IDM_LNRS: line = view->pLines[row].nr_left; if (state == STATE_MOVEDRIGHT || state == STATE_SIMILARRIGHT) { line = -line; } break; case IDM_RNRS: line = view->pLines[row].nr_right; if (state == STATE_MOVEDLEFT || state == STATE_SIMILARLEFT) { line = -line; } break; } if (line == 0) { ViewLeave(); return(NULL); } if (line < 0) { /* lines that are moved appear twice. * show the correct-sequence line nr * for the out-of-seq. copy in brackets. */ hr = StringCchPrintf(view->nrtext, 12, "(%d)", abs(line)); if (FAILED(hr)) { OutputError(hr, IDS_SAFE_PRINTF); return(NULL); } } else { hr = StringCchPrintf(view->nrtext, 12, "%d", line); if (FAILED(hr)) { OutputError(hr, IDS_SAFE_PRINTF); return(NULL); } } pstr = view->nrtext; break; case 1: /* tag text - represents the state of the line */ switch (state) { case STATE_SAME: pstr = " "; break; case STATE_LEFTONLY: case STATE_SIMILARLEFT: pstr = " "; break; case STATE_MOVEDLEFT: pstr = " <- "; break; case STATE_MOVEDRIGHT: pstr = " -> "; break; } break; case 2: /* main text - line */ pstr = line_gettext(view->pLines[row].line); break; } } else { /* outline mode */ switch (col) { case 0: /* row number - just the line number */ hr = StringCchPrintf(view->nrtext, 12, "%d", row+1); if (FAILED(hr)) { OutputError(hr, IDS_SAFE_PRINTF); return(NULL); } pstr = view->nrtext; break; case 1: /* tag */ pstr = compitem_gettext_tag(view->pItems[row]); break; case 2: /* result text */ pstr = compitem_gettext_result(view->pItems[row]); break; } } ViewLeave(); return(pstr); } /* * return the text associated with a given column of a given row. * Return a pointer that does not need to be freed after use - ie * a pointer into our data somewhere, not a copy */ LPWSTR view_gettextW(VIEW view, long row, int col) { int state; LPWSTR pwz; pwz = NULL; /* kill spurious diagnostic */ if (view == NULL) { return(NULL); } ViewEnter(); if ((0 > row) || (row >= view->rows)) { ViewLeave(); return(NULL); } if (view->bExpand) { /* we are in expand mode */ state = section_getstate(view->pLines[row].section); switch (col) { case 2: /* main text - line */ pwz = line_gettextW(view->pLines[row].line); break; } } ViewLeave(); return(pwz); } /* * return the line number that this row had in the original left * file. 0 if not in expand mode. 0 if this row was not in the left file. * -(linenr) if this row is a MOVED line, and this is the right file * copy */ int view_getlinenr_left(VIEW view, long row) { int state, line; if ((0> row) || (view == NULL) || (row >= view->rows) || !view->bExpand) { return 0; } ViewEnter(); state = section_getstate(view->pLines[row].section); line = view->pLines[row].nr_left; if (state == STATE_MOVEDRIGHT || state == STATE_SIMILARRIGHT) { line = -line; } ViewLeave(); return(line); } /* * return the line number that this row had in the original right * file. 0 if not in expand mode. 0 if this row was not in the right file. * -(linenr) if this row is a MOVED line, and this is the left file * copy */ int view_getlinenr_right(VIEW view, long row) { int state, line; if ((0 > row) || (view == NULL) || (row > view->rows) || !view->bExpand) { return 0; } ViewEnter(); state = section_getstate(view->pLines[row].section); line = view->pLines[row].nr_right; if (state == STATE_MOVEDLEFT || state == STATE_SIMILARLEFT) { line = -line; } ViewLeave(); return(line); } /* find the maximum width in characters for the given column */ int view_getwidth(VIEW view, int col) { if (view == NULL) { return(0); } switch (col) { case 0: /* line nr column - always 5 characters wide */ return(5); case 1: /* this is a proportional font field, so add on a margin * for error */ return(view->maxtag + (view->maxtag / 20)); case 2: /* this now includes the tab expansion allowance */ return(view->maxrest); default: return(0); } } /* how many rows are there in this view ? */ long view_getrowcount(VIEW view) { if (view == NULL) { return(0); } return(view->rows); } /* return the state for the current row. This is used * to select the text colour for the row * * states for sections are obtained from section_getstate (and apply, and * to all lines in that section. States for compitems are obtained * from compitem_getstate. */ int view_getstate(VIEW view, long row) { int state; if (view == NULL) { return(0); } ViewEnter(); if ( (row >= view->rows) || (row < 0)) { state = 0; } else if (view->bExpand) { /* its a line state that's needed */ state = section_getstate(view->pLines[row].section); } else { /* its a compitem state */ state = compitem_getstate(view->pItems[row]); } ViewLeave(); return(state); } /* * return the marked state for a given row. Only compitems can be marked, * so it will be FALSE unless it is a compitem on which view_setmark or * compitem_setmark have previously set the mark state to TRUE. */ BOOL view_getmarkstate(VIEW view, long row) { BOOL bMark = FALSE; if (view != NULL) { ViewEnter(); if ( (0 < row) && (row < view->rows) && (!view->bExpand)) { bMark = compitem_getmark(view->pItems[row]); } ViewLeave(); } return(bMark); } /* * set the mark state for a given row. This is only possible for compitem rows. * The mark set can be retrieved by view_getmarkstate or compitem_getmark. * * We return FALSE if the state could not be set - eg because the * row to set is not a compitem row. */ BOOL view_setmarkstate(VIEW view, long row, BOOL bMark) { BOOL bOK = FALSE; if (view != NULL) { ViewEnter(); if ( (0 < row) && (0 <= view->rows) && (row < view->rows) && !view->bExpand) { compitem_setmark(view->pItems[row], bMark); bOK = TRUE; } ViewLeave(); } return(bOK); } /* return a handle to the current compitem. in expand mode, * returns the handle to the compitem we are expanding. In outline * mode, returns the handle to the compitem for the given row, if valid, * or NULL otherwise. row is only used if not in expand mode. */ COMPITEM view_getitem(VIEW view, long row) { COMPITEM ci; if (view == NULL) { return(NULL); } ViewEnter(); if (!view->bExpand) { if ((row >= 0) && (row < view->rows)) { ci = view->pItems[row]; } else { ci = NULL; } } else { ci = view->ciSelect; } ViewLeave(); return(ci); } /* * return TRUE if the current mapping is expanded mode */ BOOL view_isexpanded(VIEW view) { if (view == NULL) { return(FALSE); } return(view->bExpand); } /* * return a text string describing the view. This is NULL in outline mode, * or the tag text for the current compitem in expanded mode */ LPSTR view_getcurrenttag(VIEW view) { LPSTR str; if ((view == NULL) || (!view->bExpand)) { return(NULL); } else { ViewEnter(); str = compitem_gettext_tag(view->ciSelect); ViewLeave(); return(str); } } /* notify that CompItems have been added to the complist. * * rebuild the view (if in outline mode), and refresh the table. Use * the table message TM_APPEND if possible (if column widths have not * change). If we have to do TM_NEWLAYOUT, then ensure we scroll * back to the right row afterwards. * * This causes a Poll() to take place. We return TRUE if an abort is * pending - in this case, the caller should abandon the scan loop. * * WIN32: enter the critical section for this function since this can be * called from the worker thread while the UI thread is using the * view that we are about to change. * * We cannot ever call SendMessage from the * worker thread within CSView. If there is conflict, it will hang. * */ BOOL view_newitem(VIEW view) { int maxtag, maxrest; int rownr; TableSelection Select; BOOL bSelect; COMPITEM ciTop = NULL; BOOL bRedraw = FALSE; BOOL bAppend = FALSE; // get the top row before remapping in case we need it /* find the row at the top of the window */ rownr = (long) SendMessage(view->hwnd, TM_TOPROW, FALSE, 0); // also remember the selection bSelect = (BOOL) SendMessage(view->hwnd, TM_GETSELECTION, 0, (LPARAM) &Select); // *important*:no critsec over SendMessage ViewEnter(); if ((view != NULL) && !(view->bExpand) && !(view->bExpanding)) { /* save some state about the present mapping */ maxtag = view->maxtag; maxrest = view->maxrest; // remember the compitem this corresponds to if (view->pItems && (rownr >= 0) && (rownr < view->rows)) { ciTop = view->pItems[rownr]; } // re-do the outline mapping, but don't tell the table class. // ask it to check for the visible row closest to ciTop in case // we need to refresh the display // // since we are holding the critsec, the redraw param // *must* be false. view_outline_opt(view, FALSE, ciTop, &rownr); /* have the column widths changed ? */ if ((maxtag < view->maxtag) || (maxrest < view->maxrest)) { /* yes - need complete redraw */ bRedraw = TRUE; } else { bAppend = TRUE; } } ViewLeave(); if (bRedraw) { /* switch to new mapping */ SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (LPARAM) view); // go to the visible row closest to the old top row if ((rownr >= 0) && (rownr < view->rows)) { SendMessage(view->hwnd, TM_TOPROW, TRUE, rownr); } // select the old selection too (if the table class allowed // us to get it) if (bSelect) { SendMessage(view->hwnd, TM_SELECT,0, (LPARAM) &Select); } } else if (bAppend) { /* we can just append */ /* * in the WIN32 multiple threads case, the mapping may have * changed since we released the critsec. however we are still * safe. The table will not allow us to reduce the number of * rows, so the worst that can happen is that the table will * think there are too many rows, and the table message handler * will handle this correctly (return null for the text). * The only visible effect is therefore that the scrollbar * position is wrong. */ SendMessage(view->hwnd, TM_APPEND, view->rows, (LPARAM) view); } return(Poll()); } /* * the view mapping options (eg outline_include, expand_mode) have changed - * re-do the mapping and then scroll back to the same position in the window * if possible. */ void view_changeviewoptions(VIEW view) { long row; int state, number; BOOL bRight; if (view == NULL) { return; } /* find what row we are currently on. Do this BEFORE we enter CSView */ row = (long) SendMessage(view->hwnd, TM_TOPROW, FALSE, 0); ViewEnter(); if (!view->bExpand) { // view_outline_opt allows us to find the first visible row // after a given COMPITEM. Do this to look for the old top-row // compitem, so that even if it is no longer visible, we can // still go to just after it. INT newrow = -1; if (row < view->rows) { COMPITEM ciTop = view->pItems[row]; view_outline_opt(view, TRUE, ciTop, &newrow); } else { view_outline_opt(view, TRUE, NULL, NULL); } ViewLeave(); // row now has the visible row that corresponds to // ciTop or where it would have been if ((newrow >=0) && (newrow < view->rows)) { SendMessage(view->hwnd, TM_TOPROW, TRUE, newrow); } return; } /* expanded mode */ bRight = FALSE; /* arbitrarily - avoid strange diagnostic */ /* save the line number on one side (and remember which side) */ if (row >= view->rows) { number = -1; } else { state = section_getstate(view->pLines[row].section); if ((state == STATE_MOVEDRIGHT) || (state == STATE_RIGHTONLY)) { bRight = TRUE; number = view->pLines[row].nr_right; } else { bRight = FALSE; number = view->pLines[row].nr_left; } } /* make the new mapping */ view_expand_item(view, view->ciSelect); /* things may happen now due to simultaneous scrolling from * two threads. At least we won't deadlock. */ /* find the nearest row in the new view */ if (number >= 0) { ViewEnter(); row = view_findrow(view, number, bRight); ViewLeave(); /* scroll this row to top of window */ if (row >= 0) { /* things may happen now due to simultaneous scrolling from * two threads. At least we won't deadlock. */ SendMessage(view->hwnd, TM_TOPROW, TRUE, row); return; } } } /* the compare options have changed - re-do the compare completely * and make the new mapping. Retain current position in the file. */ void view_changediffoptions(VIEW view) { int state, number; long row; BOOL bRight = FALSE; LIST li; COMPITEM ci; number = 0; if (view == NULL) { return; } /* * get current row before entering critsec. */ row = (long) SendMessage(view->hwnd, TM_TOPROW, FALSE, 0); ViewEnter(); /* find the current line number so we can go back to it * (only if we are in expanded mode */ if (view->bExpand) { state = section_getstate(view->pLines[row].section); if ((state == STATE_MOVEDRIGHT) || (state == STATE_SIMILARRIGHT) || (state == STATE_RIGHTONLY)) { bRight = TRUE; number = view->pLines[row].nr_right; } else { bRight = FALSE; number = view->pLines[row].nr_left; } } /* to force a recompare using the new options, we must * tell each compitem to discard its current compare result. * we need to traverse the list of compitems calling this * for each compare. */ li = complist_getitems(view->cl); for (ci = (COMPITEM) List_First(li); ci != NULL; ci = (COMPITEM) List_Next(ci)) { compitem_discardsections(ci); } if (!view->bExpand) { ViewLeave(); // we are in outline mode. Refreshing the outline view // will pick up any tag and tag width changes view_outline(view); // now scroll to the previous position if still there if (row < view->rows) { SendMessage(view->hwnd, TM_TOPROW, TRUE, row); } return; } view_expand_item(view, view->ciSelect); /* find the nearest row in the new view */ ViewEnter(); row = view_findrow(view, number, bRight); ViewLeave(); /* scroll this row to top of window */ if (row >= 0) { SendMessage(view->hwnd, TM_TOPROW, TRUE, row); } } /* find the next changed - ie non-same - row in a given direction. * for outline mode we find the next STATE_DIFFER. for expand mode, we * find the next section */ long view_findchange(VIEW view, long startrow, BOOL bForward) { long i; if (view == NULL) { return(0); } if (view->rows <= 0) { return(-1); } ViewEnter(); if (bForward) { if (startrow >= view->rows) { ViewLeave(); return(-1); } if (!view->bExpand) { /* look for next compitem with an expandable state*/ for (i = startrow; i < view->rows; i++) { if (compitem_getstate(view->pItems[i]) == STATE_DIFFER) { ViewLeave(); return(i); } } /* none found */ ViewLeave(); return(-1); } else { /* * find the next line that matches, then go on to the * next line that does not match * */ for (i= startrow; i < view->rows; i++) { if (section_getstate(view->pLines[i].section) == STATE_SAME) { break; } } for ( ; i < view->rows; i++) { if (section_getstate(view->pLines[i].section) != STATE_SAME) { ViewLeave(); return(i); } } ViewLeave(); return(-1); } } else { /* same search backwards */ if (startrow < 0) { ViewLeave(); return(-1); } if (view->bExpand) { /* search backwards for first row that is not * changed (has state SAME). then carry on for * the next changed row. */ for (i = startrow; i >= 0; i--) { if (section_getstate(view->pLines[i].section) == STATE_SAME) { break; } } for ( ; i >= 0; i--) { if (section_getstate(view->pLines[i].section) != STATE_SAME) { ViewLeave(); return(i); } } ViewLeave(); return(-1); } else { for (i = startrow; i >= 0; i--) { if (compitem_getstate(view->pItems[i]) == STATE_DIFFER) { ViewLeave(); return(i); } } ViewLeave(); return(-1); } } } int view_getrowstate(VIEW view, long row) { int state; if (view == NULL) { return(0); } if ((view->rows) <= 0 || (row >= view->rows) || (row <= 0) ) { return(STATE_SAME); } ViewEnter(); state = section_getstate(view->pLines[row].section); ViewLeave(); return state; } /* * the WIN32 multithread version can try view_expand and view_outline * (or view_newitem) at the same time. This is not correctly protected by * the critical section, since there are major restrictions about holding * critsecs when sending messages from the worker thread. * * To avoid contention, we call this function to notify that we are * starting expanding. view_newitem and ToOutline will return without * doing anything if this function has been called and view_expand has not * completed. */ void view_expandstart(VIEW view) { view->bExpanding = TRUE; } // are we in the middle of expansion ? BOOL view_expanding(VIEW view) { return(view->bExpanding); } /* ---- internal functions ------------------------------------------ */ /* find the new row number for the line numbered 'number' * or the nearest line if possible. if bRight is true, number is * a right file number; otherwise it is a left file number. * * we must be in expand mode */ int view_findrow( VIEW view, int number, BOOL bRight ) { int i; if (!view->bExpand) { return(0); } for (i = 0; i < view->rows; i++) { if (bRight) { if (view->pLines[i].nr_right == number) { /* found the exact number */ return(i); } else if (view->pLines[i].nr_right > number) { /* passed our line -stop here */ return(i); } } else { if (view->pLines[i].nr_left == number) { /* found the exact number */ return(i); } else if (view->pLines[i].nr_left > number) { /* passed our line -stop here */ return(i); } } } return(-1); } /* free memory associated with the expand mode or outline mode mappings * called whenever we rebuild the mapping, and on deletion */ void view_freemappings( VIEW view ) { if (view->pLines) { HeapFree(GetProcessHeap(), NULL, view->pLines); view->pLines = NULL; } else if (view->pItems) { /* previous outline mapping array is still there - free it * before we build a new one */ HeapFree(GetProcessHeap(), NULL, view->pItems); view->pItems = NULL; } view->rows = 0; } /* build a view outline to map one row to a COMPITEM handle by traversing * the list of COMPITEMs obtained from our complist. * optionally tell the table class to redraw (if bRedraw), and if so, * scroll the new table to select the row that represents the * file we were expanding, if possible * * *important*: if you are holding the view critsec when you call this, you * must pass bRedraw as FALSE or you could deadlock * * if a COMPITEM ci is passed in, then return in *prow the row number that * corresponds to this item in the new view, or if not visible, the first * visible row after it (to retain current scroll position) */ void view_outline_opt( VIEW view, BOOL bRedraw, COMPITEM ciFind, int * prow ) { int prev_row = -1; /* the row nr of the previously-expanded row*/ int i; /* nr of includable items */ LIST li; COMPITEM ci; int state; TableSelection select; /* * check that view_setcomplist has already been called. if not, * nothing to do */ if (view->cl == NULL) { return; } ViewEnter(); /* clear the mode flag and free up memory associated with expand mode */ view->bExpand = FALSE; view_freemappings(view); /* traverse the list of compitems counting up the number of * includable items */ li = complist_getitems(view->cl); ci = (COMPITEM) List_First(li); for (i = 0; ci != NULL; ci = (COMPITEM) List_Next(ci)) { if ((ciFind != NULL) && (prow != NULL)) { if (ci == ciFind) { // now that we have found the requested item, // the next visible row is the one we want, // whether it is ci or a later one *prow = i; } } state = compitem_getstate(ci); if (((outline_include & INCLUDE_SAME) && (state == STATE_SAME)) || ((outline_include & INCLUDE_DIFFER) && (state == STATE_DIFFER)) || ((outline_include & INCLUDE_LEFTONLY) && (state == STATE_FILELEFTONLY)) || ((outline_include & INCLUDE_RIGHTONLY) && (state == STATE_FILERIGHTONLY))) { if (!compitem_getmark(ci) || !hide_markedfiles) { i++; } } } /* allocate an array big enough for all of these */ { /* DO NOT link in any storage with garbage pointers in it */ COMPITEM * temp; temp = (COMPITEM *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, i * sizeof(COMPITEM)); if (temp == NULL) { return; } view->pItems = temp; } view->rows = i; /* keep track of the column widths */ view->maxtag = 0; view->maxrest = 0; /* loop through again filling the array, and at the same time looking * out for the handle of the previously expanded item */ ci = (COMPITEM) List_First(li); for (i = 0; ci != NULL; ci = (COMPITEM) List_Next(ci)) { state = compitem_getstate(ci); if (((outline_include & INCLUDE_SAME) && (state == STATE_SAME)) || ((outline_include & INCLUDE_DIFFER) && (state == STATE_DIFFER)) || ((outline_include & INCLUDE_LEFTONLY) && (state == STATE_FILELEFTONLY)) || ((outline_include & INCLUDE_RIGHTONLY) && (state == STATE_FILERIGHTONLY))) { if (!compitem_getmark(ci) || !hide_markedfiles) { view->pItems[i] = ci; if (ci == view->ciSelect) { prev_row = i; } /* check the column widths in characters */ view->maxtag = max(view->maxtag, lstrlen(compitem_gettext_tag(ci))); view->maxrest = max(view->maxrest, lstrlen(compitem_gettext_result(ci))); i++; } } } ViewLeave(); /* inform table of new layout of table - force refresh */ if (bRedraw) { SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (LPARAM) view); /* scroll to and highlight the row that represents the file * we were previously expanding */ if (prev_row != -1) { select.startrow = prev_row; select.startcell = 0; select.nrows = 1; select.ncells = 1; SendMessage(view->hwnd, TM_SELECT, 0, (LPARAM) &select); } } } /* expand a view - given the handle to the compitem to expand. * * called from view_expand, and also to re-do an expanded view * after options change in view_changediffoptions and _changeviewoptions * * we get the composite section list from the compitem, * and pick out all the sections that are includable (according * to the global option expand_mode: we include all sections, or * just those in one side left or right). Once we know the count of rows, * allocate the mapping array: in each element of the array we keep * a handle to the section for that row (to get the state and hence the * tag text), and a handle to the line within that section (for the line text). * * We no longer insist on only expanding text files that differ - if the * compitem can give us a composite section list, we will map it. * * We need to be able to give a line number for a line, in either of * the original files according to which option is in force. Each section * can give us its base line number (number of first line in section) in * each of the two files or 0 if not present, and we track these here. * * MUST BE INSIDE CSView BEFORE CALLING HERE. */ BOOL view_expand_item( VIEW view, COMPITEM ci ) { LIST li; SECTION sh; LINE line1, line2; int i, base_left, base_right, state; // We could be on a second thread trying to expand while it's // already going on. That ain't clever! if (view->bExpandGuard) { Trace_Error(NULL, "Expansion in progress. Please wait.", FALSE); ViewLeave(); return FALSE; } // Ensure that the world knows that we are expanding // before we leave the critical section. // This is the only way into getcomposite. view->bExpandGuard = TRUE; // the compitem_getcomposite could take a long time // if the file is large and remote. We need to // release the critsec during this operation. ViewLeave(); /* get the composite section list */ li = compitem_getcomposite(ci); if (li == NULL) { view->bExpanding = FALSE; view->bExpandGuard = FALSE; return FALSE; } ViewEnter(); /* remember the compitem we are expanding */ view->ciSelect = ci; /* switch modes and free the current mapping * * NOTE: must do this AFTER the compitem_getcomposite, * since that can fail: if it fails it could put up a * message box, and that could cause a queued paint message * to be processed, which would cause us to use these mappings * and gpfault if they had been cleared first. */ view->bExpand = TRUE; view->bExpanding = FALSE; view->bExpandGuard = FALSE; view_freemappings(view); /* loop through totalling the lines in sections * that we should include */ view->rows = 0; for ( sh = (SECTION) List_First(li); sh != NULL; sh = (SECTION) List_Next(sh)) { state = section_getstate(sh); if (expand_mode == IDM_RONLY) { if ((state == STATE_LEFTONLY) || (state == STATE_SIMILARLEFT) || (state == STATE_MOVEDLEFT)) { continue; } } else if (expand_mode == IDM_LONLY) { if ((state == STATE_RIGHTONLY) || (state == STATE_SIMILARRIGHT) || (state == STATE_MOVEDRIGHT)) { continue; } } /* include all lines in this section if the section meets the include criteria */ if ( ((state == STATE_SAME) && (expand_include & INCLUDE_SAME)) || ((state == STATE_LEFTONLY) && (expand_include & INCLUDE_LEFTONLY)) || ((state == STATE_RIGHTONLY) && (expand_include & INCLUDE_RIGHTONLY)) || ((state == STATE_MOVEDLEFT) && (expand_include & INCLUDE_MOVEDLEFT)) || ((state == STATE_MOVEDRIGHT) && (expand_include & INCLUDE_MOVEDRIGHT)) || ((state == STATE_SIMILARLEFT) && (expand_include & INCLUDE_SIMILARLEFT)) || ((state == STATE_SIMILARRIGHT) && (expand_include & INCLUDE_SIMILARRIGHT))) { view->rows += section_getlinecount(sh); } } /* allocate the memory for the mapping array */ { /* DO NOT chain in any storage with garbage pointers in it */ PVIEWLINE temp; temp = (PVIEWLINE) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, view->rows * sizeof(VIEWLINE)); if (temp == NULL) { return FALSE; } view->pLines = temp; } /* loop through the sections again filling in the mapping array */ i = 0; view->maxtag = 5; view->maxrest = 0; for (sh = (SECTION) List_First(li); sh != NULL; sh = (SECTION) List_Next(sh)) { state = section_getstate(sh); if (expand_mode == IDM_RONLY) { if ((state == STATE_LEFTONLY) || (state == STATE_SIMILARLEFT) || (state == STATE_MOVEDLEFT)) { continue; } } else if (expand_mode == IDM_LONLY) { if ((state == STATE_RIGHTONLY) || (state == STATE_SIMILARRIGHT) || (state == STATE_MOVEDRIGHT)) { continue; } } /* include all lines in this section if the section meets the include criteria */ if ( ((state == STATE_SAME) && (expand_include & INCLUDE_SAME)) || ((state == STATE_LEFTONLY) && (expand_include & INCLUDE_LEFTONLY)) || ((state == STATE_RIGHTONLY) && (expand_include & INCLUDE_RIGHTONLY)) || ((state == STATE_MOVEDLEFT) && (expand_include & INCLUDE_MOVEDLEFT)) || ((state == STATE_MOVEDRIGHT) && (expand_include & INCLUDE_MOVEDRIGHT)) || ((state == STATE_SIMILARLEFT) && (expand_include & INCLUDE_SIMILARLEFT)) || ((state == STATE_SIMILARRIGHT) && (expand_include & INCLUDE_SIMILARRIGHT))) { /* find the base line number in each file */ base_left = section_getleftbasenr(sh); base_right = section_getrightbasenr(sh); /* add each line in section to the view. section_getfirst() * returns us to a handle that is in a list. We can * call List_Next and will eventually get to the * line returned by section_getlast(). Sections always have * at least one line */ line1 = section_getfirstline(sh); line2 = section_getlastline(sh); for (; line1 != NULL; line1 = (LINE) List_Next(line1)) { view->pLines[i].line = line1; view->pLines[i].section = sh; /* calculate the line number for this line by * incrementing the base nr for this section. * Note SIMILAR_RIGHT (or LEFT) lines DO have * left (or right) numbers, but they are dummies. */ view->pLines[i].nr_left = base_left; if (state!=STATE_SIMILARRIGHT && base_left != 0) { base_left++; } view->pLines[i].nr_right = base_right; if (state!=STATE_SIMILARLEFT && base_right != 0) { base_right++; } /* increment index into view */ i++; /* check the column widths */ view->maxrest = max(view->maxrest, (line_gettabbedlength(line1, g_tabwidth))); /* end of section ? */ if (line1 == line2) { break; } } } } /* We must NOT hold a critical section here as SendMessage may hang */ ViewLeave(); /*inform table window of revised mapping */ SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (LPARAM) view); return(TRUE); } /* * view_gethwnd * */ HWND view_gethwnd(VIEW view) { return(view) ? view->hwnd : NULL; } /* * view_gototableline * */ void view_gototableline(VIEW view, LONG iLine) { if (view) { const LONG cLines = view_getrowcount(view); if ( (iLine >= 0) && (iLine < cLines)) { TableSelection select; memset(&select, 0, sizeof(TableSelection)); select.startrow = iLine; select.nrows = 1L; select.ncells = 1L; SendMessage(view_gethwnd(view), TM_SELECT, 0, (LPARAM) &select); } } } typedef PUCHAR (*STRSUBFUNC)(PUCHAR, PUCHAR, PUCHAR*); extern PUCHAR My_mbsistr(PUCHAR, PUCHAR, PUCHAR*); extern PUCHAR My_mbsstr(PUCHAR, PUCHAR, PUCHAR*); BOOL view_findstring(VIEW view, LONG iCol, LPCSTR pszFind, BOOL fSearchDown, BOOL fMatchCase, BOOL fWholeWord) { const LONG cRows = view_getrowcount(view); BOOL fFound = FALSE; if (cRows > 0) { STRSUBFUNC pfnSub = (fMatchCase) ? My_mbsstr : My_mbsistr; const char *pszRow = NULL; const char *pszFound = NULL; char *pszEnd = NULL; LONG iEnd = 0; LONG iRow = 0; LONG nStep = 0; LONG iWrapAt = 0; LONG iWrapTo = 0; if (fSearchDown) { nStep = 1; iRow = selection + selection_nrows - 1; iWrapAt = cRows; iWrapTo = 0; } else { nStep = -1; iRow = selection; iWrapAt = -1; iWrapTo = cRows - 1; } iRow += nStep; if (iRow < 0 || iRow >= cRows) { iRow = iWrapTo; } iEnd = iRow; for (;;) { pszRow = view_gettext(view, iRow, iCol); if (pszRow) { pszEnd = NULL; pszFound = (const char*)pfnSub((PUCHAR)pszRow, (PUCHAR)pszFind, (PUCHAR*)&pszEnd); if (pszFound) { if (!fWholeWord) { fFound = TRUE; } else { /* check end of string */ if (!pszEnd || !*pszEnd || (!IsDBCSLeadByte(*pszEnd) && !isalpha((UCHAR)*pszEnd) && !isdigit((UCHAR)*pszEnd))) { /* check beginning of string */ if (pszFound == pszRow) { fFound = TRUE; } else { const char *pchT = CharPrev(pszRow, pszFound); if (!pchT || !*pchT || (!IsDBCSLeadByte(*pchT) && !isalpha((UCHAR)*pchT) && !isdigit((UCHAR)*pchT))) { fFound = TRUE; } } } } if (fFound) { view_gototableline(view, iRow); break; } } } iRow += nStep; if (iRow == iWrapAt) { iRow = iWrapTo; } if (iRow == iEnd) { break; } } } return fFound; }