/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 Microsoft Corporation.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
\******************************************************************************/
/****************************** Module Header *******************************
* Module Name: 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 <windows.h>
#include <stdlib.h>
#include <commdlg.h>
#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_RIGHTONLY:
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);
}