/******************************************************************************\ * 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: VIEW.C * * Maps rows in window to items in COMPLIST * * Functions: * * view_new() * view_setcomplist() * view_getcomplist() * view_close() * view_delete() * view_outline() * view_expand() * view_gettext() * view_getlinenr_left() * view_getlinenr_right() * view_getwidth() * view_getrowcount() * view_getstate() * view_getitem() * view_isexpanded() * view_getcurrenttag() * view_newitem() * view_changeviewoptions() * view_changediffoptions() * view_findchange() * view_outline_opt() * view_freemappings() * view_findrow() * view_expand_item() * * Comments: * * 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. * * 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. * * We use the global options in windiff.h, and we allocate memory from the * heap hHeap which has been initialised elsewhere. Points in time-intensive * loops call Poll() defined elsewhere. * ****************************************************************************/ #include #include #include #include "gutils.h" #include "table.h" #include "state.h" #include "windiff.h" #include "wdiffrc.h" #include "list.h" #include "line.h" #include "scandir.h" #include "file.h" #include "section.h" #include "compitem.h" #include "complist.h" #include "view.h" /* * data structures */ /* 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, FAR * 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 */ 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 FAR * pItems; /* in expand mode we use this array of line and section handles */ PVIEWLINE pLines; }; CRITICAL_SECTION CSView; static BOOL bDoneInit = FALSE; #define ViewEnter() EnterCriticalSection(&CSView); #define ViewLeave() LeaveCriticalSection(&CSView); void view_outline_opt(VIEW view, BOOL bRedraw); void view_freemappings(VIEW view); int view_findrow(VIEW view, int number, BOOL bRight); BOOL view_expand_item(VIEW view, COMPITEM ci); /*************************************************************************** * Function: view_new * * Purpose: * * 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 from the heap */ view = (VIEW) gmem_get(hHeap, sizeof(struct view)); /* set the default fields */ view->hwnd = hwndTable; view->cl = NULL; view->bExpand = FALSE; view->ciSelect = NULL; view->rows = 0; view->pItems = NULL; view->pLines = NULL; return(view); } /*************************************************************************** * Function: view_setcomplist * * Purpose: * * 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 left, right, 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 */ left = complist_getroot_left(cl); right = complist_getroot_right(cl); both = gmem_get(hHeap, lstrlen(left) + lstrlen(right) +4); wsprintf((LPTSTR)both, "%s : %s", left, right); ViewLeave(); SetNames(both); ViewEnter(); gmem_free(hHeap, both, lstrlen(both)+1); complist_freeroot_left(cl, left); complist_freeroot_right(cl, right); ViewLeave(); view_outline(view); } /*************************************************************************** * Function: view_getcomplist * * Purpose: * * Return a handle to the complist owned by this view */ COMPLIST view_getcomplist(VIEW view) { if (view == NULL) { return(NULL); } return(view->cl); } /*************************************************************************** * Function: view_close * * Purpose: * * 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); } /*************************************************************************** * Function: view_delete * * Purpose: * * 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); gmem_free(hHeap, (LPSTR) view, sizeof(struct view)); } /*************************************************************************** * Function: view_outline * * Purpose: * * 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); } /*************************************************************************** * Function: view_expand * * Purpose: * * 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. */ BOOL view_expand(VIEW view, long row) { COMPITEM ci; BOOL bRet; 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); // view_expand_item does the... // ViewLeave(); return(bRet); } /*************************************************************************** * Function: view_gettext * * Purpose: * * 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; if (view == NULL) { return (NULL); } ViewEnter(); if (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 */ switch(line_numbers) { case IDM_NONRS: pstr = NULL; break; case IDM_LNRS: line = view->pLines[row].nr_left; if (state == STATE_MOVEDRIGHT) { line = -line; } break; case IDM_RNRS: line = view->pLines[row].nr_right; if (state == STATE_MOVEDLEFT) { 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. */ wsprintf((LPTSTR)view->nrtext, "(%d)", abs(line)); } else { wsprintf((LPTSTR)view->nrtext, "%d", line); } pstr = view->nrtext; break; case 1: /* tag text - represents the state of the line */ switch(state) { case STATE_SAME: pstr = " "; break; case STATE_LEFTONLY: 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 */ wsprintf((LPTSTR)view->nrtext, "%d", row+1); 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); } /*************************************************************************** * Function: view_getlinenr_left * * Purpose: * * 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 ((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) { line = -line; } ViewLeave(); return(line); } /*************************************************************************** * Function: view_getlinenr_right * * Purpose: * * 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 ((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) { line = -line; } ViewLeave(); return(line); } /*************************************************************************** * Function: view_getwidth * * Purpose: * * 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); } } /*************************************************************************** * Function: view_getrowcount * * Purpose: * * How many rows are there in this view ? */ long view_getrowcount(VIEW view) { if (view == NULL) { return(0); } return(view->rows); } /*************************************************************************** * Function: view_getstate * * Purpose: * * 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) { 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); } /*************************************************************************** * Function: view_gethandle * * Purpose: * * 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); } /*************************************************************************** * Function: view_isexpanded * * Purpose: * * Return TRUE if the current mapping is expanded mode */ BOOL view_isexpanded(VIEW view) { if (view == NULL) { return(FALSE); } return(view->bExpand); } /*************************************************************************** * Function: view_getcurrenttag * * Purpose: * * 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); } } /*************************************************************************** * Function: view_newitem * * Purpose: * * 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. * * 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. * * EXCEPT THAT WE DON'T DARE. 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; long rownr; if ((view == NULL) || (view->bExpand)) { /* not in outline mode - nothing to do */ return(Poll()); } /* save some state about the present mapping */ maxtag = view->maxtag; maxrest = view->maxrest; /* re-do the outline mapping, but don't tell the table * class. */ view_outline_opt(view, FALSE); /* have the column widths changed ? */ if ((maxtag < view->maxtag) || (maxrest < view->maxrest)) { /* yes - need complete redraw */ /* find the row at the top of the window */ rownr = SendMessage(view->hwnd, TM_TOPROW, FALSE, 0); /* switch to new mapping */ SendMessage(view->hwnd, TM_NEWLAYOUT, 0, (DWORD) view); /* return to old row if possible - we know * that row is still there since we have only added * rows, and not changed any of the existing mapping * * Alas this is no longer true. However the table class * will defend itself against calls for a bogus top row. */ if (rownr >= 0) { SendMessage(view->hwnd, TM_TOPROW, TRUE, rownr); } } else { /* no - we can just append */ /* * 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, (DWORD) view); } /* Poll to keep the UI updated on NT. Returns true if abort pending. */ return(Poll()); } /*************************************************************************** * Function: view_changeviewoptions * * Purpose: * * 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 = SendMessage(view->hwnd, TM_TOPROW, FALSE, 0); ViewEnter(); if (!view->bExpand) { /* outline mode. maintaining current position is * unimportant */ view_outline(view); ViewLeave(); return; } /* expanded mode */ /* 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); /* 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) { SendMessage(view->hwnd, TM_TOPROW, TRUE, row); return; } } } /*************************************************************************** * Function: view_changediffoptions * * Purpose: * * 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; LIST li; COMPITEM ci; if (view == NULL) { return; } /* * get current row before entering critsec. */ row = 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_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 we are in outline mode, we have nothing more to do */ if (!view->bExpand) { ViewLeave(); 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); } } /*************************************************************************** * Function: view_findchange * * Purpose: * * 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); } 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); } } } /*************************************************************************** * Function: view_findrow * * Purpose: * * 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); } /*************************************************************************** * Function: view_freemappings * * Purpose: * * 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) { gmem_free(hHeap, (LPSTR) view->pLines, view->rows * sizeof(VIEWLINE)); view->pLines = NULL; } else if (view->pItems) { /* previous outline mapping array is still there - free it * before we build a new one */ gmem_free(hHeap, (LPSTR) view->pItems, view->rows * sizeof(COMPLIST)); view->pItems = NULL; } } /*************************************************************************** * Function: view_outline_opt * * Purpose: * * 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 */ void view_outline_opt(VIEW view, BOOL bRedraw) { 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)) { 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))) { i++; } } /* allocate an array big enough for all of these */ view->pItems = (COMPITEM FAR *) gmem_get(hHeap, i * sizeof(COMPITEM)); 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))) { 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, (DWORD) 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, (DWORD) (LPSTR) &select); } } } /*************************************************************************** * Function: view_expand_item * * Purpose: * * 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; /* remember the compitem we are expanding */ view->ciSelect = ci; /* get the composite section list */ li = compitem_getcomposite(view->ciSelect); if (li == NULL) { ViewLeave(); return FALSE; } /* 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_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_MOVEDLEFT)) { continue; } } else if (expand_mode == IDM_LONLY) { if ((state == STATE_RIGHTONLY) || (state == STATE_MOVEDRIGHT)) { continue; } } /* include all lines in this section */ view->rows += section_getlinecount(sh); } /* allocate the memory for the mapping array */ view->pLines = (PVIEWLINE) gmem_get(hHeap, view->rows * sizeof(VIEWLINE)); /* 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_MOVEDLEFT)) { continue; } } else if (expand_mode == IDM_LONLY) { if ((state == STATE_RIGHTONLY) || (state == STATE_MOVEDRIGHT)) { continue; } } /* 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 */ view->pLines[i].nr_left = base_left; if (base_left != 0) { base_left++; } view->pLines[i].nr_right = base_right; if (base_right != 0) { base_right++; } /* increment index into view */ i++; /* check the column widths */ view->maxrest = max(view->maxrest, (line_gettabbedlength(line1, 8))); /* 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, (DWORD) view); return(TRUE); }