Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3539 lines
92 KiB

//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1993-1994
//
// File: recact.c
//
// This file contains the reconciliation-action control class code
//
//
// History:
// 08-12-93 ScottH Created.
//
//---------------------------------------------------------------------------
///////////////////////////////////////////////////// INCLUDES
#include "brfprv.h" // common headers
#include <help.h>
#include "res.h"
#include "recact.h"
#include "dobj.h"
///////////////////////////////////////////////////// CONTROLLING DEFINES
///////////////////////////////////////////////////// DEFINES
// Manifest constants
#define SIDE_INSIDE 0
#define SIDE_OUTSIDE 1
// These should be changed if the bitmap sizes change!!
#define CX_ACTIONBMP 26
#define CY_ACTIONBMP 26
#define RECOMPUTE (-1)
#define X_INCOLUMN (g_cxIcon*2)
// Image indexes
#define II_RIGHT 0
#define II_LEFT 1
#define II_CONFLICT 2
#define II_SKIP 3
#define II_MERGE 4
#define II_SOMETHING 5
#define II_UPTODATE 6
#define II_DELETE 7
// Menu items
//
#define IDM_ACTIONFIRST 100
#define IDM_TOOUT 100
#define IDM_TOIN 101
#define IDM_SKIP 102
#define IDM_MERGE 103
#define IDM_DELETEOUT 104
#define IDM_DELETEIN 105
#define IDM_DONTDELETE 106
#define IDM_ACTIONLAST 106
#define IDM_WHATSTHIS 107
///////////////////////////////////////////////////// TYPEDEFS
typedef struct tagRECACT
{
HWND hwnd;
HWND hwndLB;
HWND hwndTip;
HDC hdcOwn; // Own DC
HMENU hmenu; // Action and help context menu
HFONT hfont;
WNDPROC lpfnLBProc; // Default LB proc
HIMAGELIST himlAction; // imagelist for actions
HIMAGELIST himlCache; // control imagelist cache
HBITMAP hbmpBullet;
HDSA hdsa;
HBRUSH hbrBkgnd;
COLORREF clrBkgnd;
LONG lStyle; // Window style flags
UINT cTipID; // Tip IDs are handed out 2 per item
// Metrics
int cxItem; // Generic width of an item
int cxMenuCheck;
int cyMenuCheck;
int cyText;
int cxEllipses;
} RECACT, * LPRECACT;
#define RecAct_IsNoIcon(this) IsFlagSet((this)->lStyle, RAS_SINGLEITEM)
// Internal item data struct
//
typedef struct tagRA_PRIV
{
UINT uStyle; // One of RAIS_
UINT uAction; // One of RAIA_
FileInfo * pfi;
SIDEITEM siInside;
SIDEITEM siOutside;
LPARAM lParam;
DOBJ rgdobj[4]; // Array of Draw object info
int cx; // Bounding width and height
int cy;
} RA_PRIV, * LPRA_PRIV;
#define IDOBJ_FILE 0
#define IDOBJ_ACTION 1
#define IDOBJ_INSIDE 2
#define IDOBJ_OUTSIDE 3
// RecAction menu item definition structure. Used to define the
// context menu brought up in this control.
//
typedef struct tagRAMID
{
UINT idm; // Menu ID (for MENUITEMINFO struct)
UINT uAction; // One of RAIA_* flags
UINT ids; // Resource string ID
int iImage; // Index into himlAction
RECT rcExtent; // Extent rect of string
} RAMID, * LPRAMID; // RecAction Menu Item Definition
// Help menu item definition structure. Used to define the help
// items in the context menu.
//
typedef struct tagHMID
{
UINT idm;
UINT ids;
} HMID;
///////////////////////////////////////////////////// MACROS
#define RecAct_DefProc DefWindowProc
#define RecActLB_DefProc CallWindowProc
// Instance data pointer macros
//
#define RecAct_GetPtr(hwnd) (LPRECACT)GetWindowLongPtr(hwnd, 0)
#define RecAct_SetPtr(hwnd, lp) (LPRECACT)SetWindowLongPtr(hwnd, 0, (LRESULT)(lp))
#define RecAct_GetCount(this) ListBox_GetCount((this)->hwndLB)
LPCTSTR PRIVATE SkipDisplayJunkHack(LPSIDEITEM psi);
///////////////////////////////////////////////////// MODULE DATA
#ifdef SAVE_FOR_RESIZE
static TCHAR const c_szDateDummy[] = TEXT("99/99/99 99:99PM");
#endif
// Map RAIA_* values to image indexes
//
static UINT const c_mpraiaiImage[] =
{ II_RIGHT,
II_LEFT,
II_SKIP,
II_CONFLICT,
II_MERGE,
II_SOMETHING,
II_UPTODATE,
0,
#ifdef NEW_REC
II_DELETE,
II_DELETE,
II_SKIP
#endif
};
// Map RAIA_* values to menu command positions
//
static UINT const c_mpraiaidmMenu[] =
{ IDM_TOOUT,
IDM_TOIN,
IDM_SKIP,
IDM_SKIP,
IDM_MERGE,
0, 0, 0,
#ifdef NEW_REC
IDM_DELETEOUT,
IDM_DELETEIN,
IDM_DONTDELETE
#endif
};
// Define the context menu layout
//
static RAMID const c_rgramid[] = {
{ IDM_TOOUT, RAIA_TOOUT, IDS_MENU_REPLACE, II_RIGHT, 0 },
{ IDM_TOIN, RAIA_TOIN, IDS_MENU_REPLACE, II_LEFT, 0 },
{ IDM_SKIP, RAIA_SKIP, IDS_MENU_SKIP, II_SKIP, 0 },
// Merge must be the last item!
{ IDM_MERGE, RAIA_MERGE, IDS_MENU_MERGE, II_MERGE, 0 },
};
static RAMID const c_rgramidCreates[] = {
{ IDM_TOOUT, RAIA_TOOUT, IDS_MENU_CREATE, II_RIGHT, 0 },
{ IDM_TOIN, RAIA_TOIN, IDS_MENU_CREATE, II_LEFT, 0 },
};
#ifdef NEW_REC
static RAMID const c_rgramidDeletes[] = {
{ IDM_DELETEOUT, RAIA_DELETEOUT, IDS_MENU_DELETE, II_DELETE, 0 },
{ IDM_DELETEIN, RAIA_DELETEIN, IDS_MENU_DELETE, II_DELETE, 0 },
{ IDM_DONTDELETE, RAIA_DONTDELETE,IDS_MENU_DONTDELETE,II_SKIP, 0 },
};
#endif
// Indexes into c_rgramidCreates
//
#define IRAMID_CREATEOUT 0
#define IRAMID_CREATEIN 1
// Indexes into c_rgramidDeletes
//
#define IRAMID_DELETEOUT 0
#define IRAMID_DELETEIN 1
#define IRAMID_DONTDELETE 2
static HMID const c_rghmid[] = {
{ IDM_WHATSTHIS, IDS_MENU_WHATSTHIS },
};
///////////////////////////////////////////////////// LOCAL PROCEDURES
LRESULT _export CALLBACK RecActLB_LBProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
///////////////////////////////////////////////////// PRIVATE FUNCTIONS
#ifdef DEBUG
LPCTSTR PRIVATE DumpRecAction(
UINT uAction) // RAIA_
{
switch (uAction)
{
DEBUG_CASE_STRING( RAIA_TOOUT );
DEBUG_CASE_STRING( RAIA_TOIN );
DEBUG_CASE_STRING( RAIA_SKIP );
DEBUG_CASE_STRING( RAIA_CONFLICT );
DEBUG_CASE_STRING( RAIA_MERGE );
DEBUG_CASE_STRING( RAIA_SOMETHING );
DEBUG_CASE_STRING( RAIA_NOTHING );
DEBUG_CASE_STRING( RAIA_ORPHAN );
DEBUG_CASE_STRING( RAIA_DELETEOUT );
DEBUG_CASE_STRING( RAIA_DELETEIN );
DEBUG_CASE_STRING( RAIA_DONTDELETE );
default: return TEXT("Unknown");
}
}
LPCTSTR PRIVATE DumpSideItemState(
UINT uState) // SI_
{
switch (uState)
{
DEBUG_CASE_STRING( SI_UNCHANGED );
DEBUG_CASE_STRING( SI_CHANGED );
DEBUG_CASE_STRING( SI_NEW );
DEBUG_CASE_STRING( SI_NOEXIST );
DEBUG_CASE_STRING( SI_UNAVAILABLE );
DEBUG_CASE_STRING( SI_DELETED );
default: return TEXT("Unknown");
}
}
/*----------------------------------------------------------
Purpose: Dumps a twin pair
Returns: --
Cond: --
*/
void PUBLIC DumpTwinPair(
LPRA_ITEM pitem)
{
if (pitem)
{
TCHAR szBuf[MAXMSGLEN];
#define szDump TEXT("Dump TWINPAIR: ")
#define szBlank TEXT(" ")
if (IsFlagClear(g_uDumpFlags, DF_TWINPAIR))
{
return;
}
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.pszName = %s\r\n"), (LPTSTR)szDump, Dbg_SafeStr(pitem->pszName));
OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.uStyle = %lx\r\n"), (LPTSTR)szBlank, pitem->uStyle);
OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.uAction = %s\r\n"), (LPTSTR)szBlank, DumpRecAction(pitem->uAction));
OutputDebugString(szBuf);
#undef szDump
#define szDump TEXT(" Inside: ")
wnsprintf(szBuf, TEXT("%s.pszDir = %s\r\n"), (LPTSTR)szDump, Dbg_SafeStr(pitem->siInside.pszDir));
OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.uState = %s\r\n"), (LPTSTR)szBlank, DumpSideItemState(pitem->siInside.uState));
OutputDebugString(szBuf);
#undef szDump
#define szDump TEXT(" Outside: ")
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.pszDir = %s\r\n"), (LPTSTR)szDump, Dbg_SafeStr(pitem->siOutside.pszDir));
OutputDebugString(szBuf);
wnsprintf(szBuf, ARRAYSIZE(szBuf), TEXT("%s.uState = %s\r\n"), (LPTSTR)szBlank, DumpSideItemState(pitem->siOutside.uState));
OutputDebugString(szBuf);
#undef szDump
#undef szBlank
}
}
#endif
/*----------------------------------------------------------
Purpose: Create a monochrome bitmap of the bullet, so we can
play with the colors later.
Returns: handle to bitmap
Cond: Caller must delete bitmap
*/
HBITMAP PRIVATE CreateBulletBitmap(
LPSIZE psize)
{
HDC hdcMem;
HBITMAP hbmp = NULL;
hdcMem = CreateCompatibleDC(NULL);
if (hdcMem)
{
hbmp = CreateCompatibleBitmap(hdcMem, psize->cx, psize->cy);
if (hbmp)
{
HBITMAP hbmpOld;
RECT rc;
// hbmp is monochrome
hbmpOld = SelectBitmap(hdcMem, hbmp);
rc.left = 0;
rc.top = 0;
rc.right = psize->cx;
rc.bottom = psize->cy;
DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUBULLET);
SelectBitmap(hdcMem, hbmpOld);
}
DeleteDC(hdcMem);
}
return hbmp;
}
/*----------------------------------------------------------
Purpose: Returns the top and bottom indexes of the visible
entries in the listbox
Returns: --
Cond: --
*/
void PRIVATE GetVisibleRange(
HWND hwndLB,
int * piTop,
int * piBottom)
{
int i;
int cel;
int cyMac;
RECT rc;
*piTop = ListBox_GetTopIndex(hwndLB);
cel = ListBox_GetCount(hwndLB);
GetClientRect(hwndLB, &rc);
cyMac = 0;
for (i = *piTop; i < cel; i++)
{
if (cyMac > rc.bottom)
break;
cyMac += ListBox_GetItemHeight(hwndLB, i);
}
*piBottom = i-1;;
}
/*----------------------------------------------------------
Purpose: Returns the top and bottom indexes of the visible
entries in the listbox
Returns: --
Cond: --
*/
int PRIVATE GetHitIndex(
HWND hwndLB,
POINT pt)
{
int i;
int iTop;
int cel;
int cyMac;
int cy;
RECT rc;
iTop = ListBox_GetTopIndex(hwndLB);
cel = ListBox_GetCount(hwndLB);
GetClientRect(hwndLB, &rc);
cyMac = 0;
for (i = iTop; i < cel; i++)
{
cy = ListBox_GetItemHeight(hwndLB, i);
if (InRange(pt.y, cyMac, cyMac + cy))
break;
cyMac += cy;
}
if (i == cel)
return LB_ERR;
return i;
}
/*----------------------------------------------------------
Purpose: Returns the resource ID string given the action
flag.
Returns: IDS_ value
Cond: --
*/
UINT PRIVATE GetActionText(
LPRA_PRIV ppriv)
{
UINT ids;
ASSERT(ppriv);
switch (ppriv->uAction)
{
case RAIA_TOOUT:
if (SI_NEW == ppriv->siInside.uState ||
SI_DELETED == ppriv->siOutside.uState)
{
ids = IDS_STATE_Creates;
}
else
{
ids = IDS_STATE_Replaces;
}
break;
case RAIA_TOIN:
if (SI_NEW == ppriv->siOutside.uState ||
SI_DELETED == ppriv->siInside.uState)
{
ids = IDS_STATE_Creates;
}
else
{
ids = IDS_STATE_Replaces;
}
break;
#ifdef NEW_REC
case RAIA_DONTDELETE:
ASSERT(SI_DELETED == ppriv->siInside.uState ||
SI_DELETED == ppriv->siOutside.uState);
ids = IDS_STATE_DontDelete;
break;
#endif
case RAIA_SKIP:
// Can occur if the user explicitly wants to skip, or if
// one side is unavailable.
ids = IDS_STATE_Skip;
break;
case RAIA_CONFLICT: ids = IDS_STATE_Conflict; break;
case RAIA_MERGE: ids = IDS_STATE_Merge; break;
case RAIA_NOTHING: ids = IDS_STATE_Uptodate; break;
case RAIA_SOMETHING: ids = IDS_STATE_NeedToUpdate; break;
#ifdef NEW_REC
case RAIA_DELETEOUT: ids = IDS_STATE_Delete; break;
case RAIA_DELETEIN: ids = IDS_STATE_Delete; break;
#endif
default: ids = 0; break;
}
return ids;
}
/*----------------------------------------------------------
Purpose: Repaint an item in the listbox
Returns: --
Cond: --
*/
void PRIVATE ListBox_RepaintItemNow(
HWND hwnd,
int iItem,
LPRECT prc, // Relative to individual entry rect. May be NULL
BOOL bEraseBk)
{
RECT rc;
RECT rcItem;
ListBox_GetItemRect(hwnd, iItem, &rcItem);
if (prc)
{
OffsetRect(prc, rcItem.left, rcItem.top);
IntersectRect(&rc, &rcItem, prc);
}
else
rc = rcItem;
InvalidateRect(hwnd, &rc, bEraseBk);
UpdateWindow(hwnd);
}
/*----------------------------------------------------------
Purpose: Determine which DOBJ of the item is going to get the caret.
Returns: pointer to DOBJ
Cond: --
*/
LPDOBJ PRIVATE RecAct_ChooseCaretDobj(
LPRECACT this,
LPRA_PRIV ppriv)
{
// Focus rect on file icon?
if (!RecAct_IsNoIcon(this))
return ppriv->rgdobj; // Yes
else
return &ppriv->rgdobj[IDOBJ_ACTION]; // No
}
/*----------------------------------------------------------
Purpose: Returns the tool tip ID for the visible rectangle
that the given item is currently occupying.
Returns: see above
Cond: --
*/
UINT PRIVATE RecAct_GetTipIDFromItemID(
LPRECACT this,
int itemID)
{
int iTop;
int iBottom;
int idsa;
UINT uID;
GetVisibleRange(this->hwndLB, &iTop, &iBottom);
ASSERT(iTop <= itemID);
ASSERT(itemID <= iBottom);
idsa = itemID - iTop;
if ( !DSA_GetItem(this->hdsa, idsa, &uID) )
{
// This region has not been added yet
uID = this->cTipID;
if (-1 != DSA_SetItem(this->hdsa, idsa, &uID))
{
TOOLINFO ti;
ti.cbSize = sizeof(ti);
ti.uFlags = 0;
ti.hwnd = this->hwndLB;
ti.uId = uID;
ti.lpszText = LPSTR_TEXTCALLBACK;
ti.rect.left = ti.rect.top = ti.rect.bottom = ti.rect.right = 0;
SendMessage(this->hwndTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
ti.uId++;
SendMessage(this->hwndTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
this->cTipID += 2;
}
}
return uID;
}
/*----------------------------------------------------------
Purpose: Finds a listbox item given the tip ID.
Returns: item index
Cond: --
*/
int PRIVATE RecAct_FindItemFromTipID(
LPRECACT this,
UINT uTipID,
BOOL * pbInside)
{
int iTop;
int iBottom;
int iVisibleItem = uTipID / 2;
int iItem;
ASSERT(0 <= iVisibleItem);
GetVisibleRange(this->hwndLB, &iTop, &iBottom);
if (iVisibleItem <= iBottom - iTop)
{
iItem = iTop + iVisibleItem;
if (uTipID % 2)
*pbInside = FALSE;
else
*pbInside = TRUE;
}
else
iItem = LB_ERR;
return iItem;
}
/*----------------------------------------------------------
Purpose: Send selection change notification
Returns:
Cond: --
*/
BOOL PRIVATE RecAct_SendSelChange(
LPRECACT this,
int isel)
{
NM_RECACT nm;
nm.iItem = isel;
nm.mask = 0;
if (isel != -1)
{
LPRA_ITEM pitem;
ListBox_GetText(this->hwndLB, isel, &pitem);
if (!pitem)
return FALSE;
nm.lParam = pitem->lParam;
nm.mask |= RAIF_LPARAM;
}
return !SendNotify(GetParent(this->hwnd), this->hwnd, RN_SELCHANGED, &nm.hdr);
}
/*----------------------------------------------------------
Purpose: Send an action change notification
Returns:
Cond: --
*/
BOOL PRIVATE RecAct_SendItemChange(
LPRECACT this,
int iEntry,
UINT uActionOld)
{
NM_RECACT nm;
nm.iItem = iEntry;
nm.mask = 0;
if (iEntry != -1)
{
LPRA_PRIV ppriv;
ListBox_GetText(this->hwndLB, iEntry, &ppriv);
if (!ppriv)
return FALSE;
nm.mask |= RAIF_LPARAM | RAIF_ACTION;
nm.lParam = ppriv->lParam;
nm.uAction = ppriv->uAction;
nm.uActionOld = uActionOld;
}
return !SendNotify(GetParent(this->hwnd), this->hwnd, RN_ITEMCHANGED, &nm.hdr);
}
/*----------------------------------------------------------
Purpose: Create the action context menu
Returns: TRUE on success
Cond: --
*/
BOOL PRIVATE RecAct_CreateMenu(
LPRECACT this)
{
HMENU hmenu;
hmenu = CreatePopupMenu();
if (hmenu)
{
TCHAR sz[MAXSHORTLEN];
MENUITEMINFO mii;
int i;
// Add the help menu items now, since these will be standard
//
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID;
mii.fType = MFT_STRING;
mii.fState = MFS_ENABLED;
for (i = 0; i < ARRAYSIZE(c_rghmid); i++)
{
mii.wID = c_rghmid[i].idm;
mii.dwTypeData = SzFromIDS(c_rghmid[i].ids, sz, ARRAYSIZE(sz));
InsertMenuItem(hmenu, i, TRUE, &mii);
}
this->hmenu = hmenu;
}
return hmenu != NULL;
}
/*----------------------------------------------------------
Purpose: Add the action menu items to the context menu
Returns: --
Cond: --
*/
void PRIVATE AddActionsToContextMenu(
HMENU hmenu,
UINT idmCheck, // menu item to checkmark
LPRA_PRIV ppriv)
{
MENUITEMINFO mii;
int i;
int cItems = ARRAYSIZE(c_rgramid);
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_DATA;
mii.fType = MFT_OWNERDRAW;
mii.fState = MFS_ENABLED;
// Is merge supported?
if (IsFlagClear(ppriv->uStyle, RAIS_CANMERGE))
{
// No
--cItems;
}
for (i = 0; i < cItems; i++)
{
mii.wID = c_rgramid[i].idm;
mii.dwItemData = (DWORD_PTR)&c_rgramid[i];
InsertMenuItem(hmenu, i, TRUE, &mii);
}
// Add the separator
mii.fMask = MIIM_TYPE;
mii.fType = MFT_SEPARATOR;
InsertMenuItem(hmenu, i, TRUE, &mii);
// Set the initial checkmark.
CheckMenuRadioItem(hmenu, IDM_ACTIONFIRST, IDM_ACTIONLAST, idmCheck,
MF_BYCOMMAND | MF_CHECKED);
// Is the file or its sync copy unavailable?
if (SI_UNAVAILABLE == ppriv->siInside.uState ||
SI_UNAVAILABLE == ppriv->siOutside.uState)
{
// Yes
mii.fMask = MIIM_STATE;
mii.fState = MFS_GRAYED | MFS_DISABLED;
SetMenuItemInfo(hmenu, IDM_TOIN, FALSE, &mii);
SetMenuItemInfo(hmenu, IDM_TOOUT, FALSE, &mii);
SetMenuItemInfo(hmenu, IDM_MERGE, FALSE, &mii);
}
// Is the file being created?
else if (ppriv->siInside.uState == SI_NEW ||
ppriv->siOutside.uState == SI_NEW)
{
// Yes; disable the replace-in-opposite direction
UINT idmDisable;
UINT idmChangeVerb;
if (ppriv->siInside.uState == SI_NEW)
{
idmDisable = IDM_TOIN;
idmChangeVerb = IDM_TOOUT;
i = IRAMID_CREATEOUT;
}
else
{
idmDisable = IDM_TOOUT;
idmChangeVerb = IDM_TOIN;
i = IRAMID_CREATEIN;
}
// Disable one of the directions
mii.fMask = MIIM_STATE;
mii.fState = MFS_GRAYED | MFS_DISABLED;
SetMenuItemInfo(hmenu, idmDisable, FALSE, &mii);
// Change the verb of the other direction
mii.fMask = MIIM_DATA;
mii.dwItemData = (DWORD_PTR)&c_rgramidCreates[i];
SetMenuItemInfo(hmenu, idmChangeVerb, FALSE, &mii);
}
#ifdef NEW_REC
// Is the file being deleted?
else if (SI_DELETED == ppriv->siInside.uState ||
SI_DELETED == ppriv->siOutside.uState)
{
// Yes;
UINT idmCreate;
UINT idmChangeVerb;
UINT iCreate;
if (SI_DELETED == ppriv->siInside.uState)
{
idmCreate = IDM_TOIN;
iCreate = IRAMID_CREATEIN;
idmChangeVerb = IDM_TOOUT;
i = IRAMID_DELETEOUT;
}
else
{
ASSERT(SI_DELETED == ppriv->siOutside.uState);
idmCreate = IDM_TOOUT;
iCreate = IRAMID_CREATEOUT;
idmChangeVerb = IDM_TOIN;
i = IRAMID_DELETEIN;
}
// Change one of the directions to be create
mii.fMask = MIIM_DATA;
mii.dwItemData = (DWORD_PTR)&c_rgramidCreates[iCreate];
SetMenuItemInfo(hmenu, idmCreate, FALSE, &mii);
// Change the verb of the other direction
mii.fMask = MIIM_DATA | MIIM_ID;
mii.wID = c_rgramidDeletes[i].idm;
mii.dwItemData = (DWORD_PTR)&c_rgramidDeletes[i];
SetMenuItemInfo(hmenu, idmChangeVerb, FALSE, &mii);
// Change the skip verb to be "Don't Delete"
mii.fMask = MIIM_DATA | MIIM_ID;
mii.wID = c_rgramidDeletes[IRAMID_DONTDELETE].idm;
mii.dwItemData = (DWORD_PTR)&c_rgramidDeletes[IRAMID_DONTDELETE];
SetMenuItemInfo(hmenu, IDM_SKIP, FALSE, &mii);
}
#endif
}
/*----------------------------------------------------------
Purpose: Clear out the context menu
Returns: --
Cond: --
*/
void PRIVATE ResetContextMenu(
HMENU hmenu)
{
int cnt;
// If there is more than just the help items, remove them
// (but leave the help items)
//
cnt = GetMenuItemCount(hmenu);
if (cnt > ARRAYSIZE(c_rghmid))
{
int i;
cnt -= ARRAYSIZE(c_rghmid);
for (i = 0; i < cnt; i++)
{
DeleteMenu(hmenu, 0, MF_BYPOSITION);
}
}
}
/*----------------------------------------------------------
Purpose: Do the context menu
Returns: --
Cond: --
*/
void PRIVATE RecAct_DoContextMenu(
LPRECACT this,
int x, // in screen coords
int y,
int iEntry,
BOOL bHelpOnly) // TRUE: only show the help items
{
UINT idCmd;
if (this->hmenu)
{
LPRA_PRIV ppriv;
RECT rc;
int idmCheck;
UINT uActionOld;
// Only show help-portion of context menu?
if (bHelpOnly)
{
// Yes
ppriv = NULL;
}
else
{
// No
ListBox_GetText(this->hwndLB, iEntry, &ppriv);
// Determine if this is a help-context menu only.
// It is if this is a folder-item or if there is no action
// to take.
//
ASSERT(ppriv->uAction < ARRAYSIZE(c_mpraiaidmMenu));
idmCheck = c_mpraiaidmMenu[ppriv->uAction];
// Build the context menu
//
if (IsFlagClear(ppriv->uStyle, RAIS_FOLDER) && idmCheck != 0)
{
AddActionsToContextMenu(this->hmenu, idmCheck, ppriv);
}
}
// Show context menu
//
SendMessage(this->hwndTip, TTM_ACTIVATE, FALSE, 0L);
idCmd = TrackPopupMenu(this->hmenu,
TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
x, y, 0, this->hwnd, NULL);
SendMessage(this->hwndTip, TTM_ACTIVATE, TRUE, 0L);
// Clear menu
//
ResetContextMenu(this->hmenu);
if (ppriv)
{
// Save the old action
uActionOld = ppriv->uAction;
}
// Act on whatever the user chose
switch (idCmd)
{
case IDM_TOOUT:
ppriv->uAction = RAIA_TOOUT;
break;
case IDM_TOIN:
ppriv->uAction = RAIA_TOIN;
break;
case IDM_SKIP:
ppriv->uAction = RAIA_SKIP;
break;
case IDM_MERGE:
ppriv->uAction = RAIA_MERGE;
break;
#ifdef NEW_REC
case IDM_DELETEOUT:
ppriv->uAction = RAIA_DELETEOUT;
break;
case IDM_DELETEIN:
ppriv->uAction = RAIA_DELETEIN;
break;
case IDM_DONTDELETE:
ppriv->uAction = RAIA_DONTDELETE;
break;
#endif
case IDM_WHATSTHIS:
WinHelp(this->hwnd, c_szWinHelpFile, HELP_CONTEXTPOPUP, IDH_BFC_UPDATE_SCREEN);
return; // Return now
default:
return; // Return now
}
// Repaint action portion of entry
ppriv->cx = RECOMPUTE;
rc = ppriv->rgdobj[IDOBJ_ACTION].rcBounding;
ListBox_RepaintItemNow(this->hwndLB, iEntry, &rc, TRUE);
// Send a notify message
ASSERT(NULL != ppriv); // uActionOld should be valid
RecAct_SendItemChange(this, iEntry, uActionOld);
}
}
/*----------------------------------------------------------
Purpose: Create the windows for this control
Returns: TRUE on success
Cond: --
*/
BOOL PRIVATE RecAct_CreateWindows(
LPRECACT this,
CREATESTRUCT * lpcs)
{
HWND hwnd = this->hwnd;
HWND hwndLB;
RECT rc;
int cxEdge = GetSystemMetrics(SM_CXEDGE);
int cyEdge = GetSystemMetrics(SM_CYEDGE);
TOOLINFO ti;
// Create listbox
hwndLB = CreateWindowEx(
0,
TEXT("listbox"),
TEXT(""),
WS_CHILD | WS_CLIPSIBLINGS | LBS_SORT | LBS_OWNERDRAWVARIABLE |
WS_VSCROLL | WS_TABSTOP | WS_VISIBLE | LBS_NOINTEGRALHEIGHT |
LBS_NOTIFY,
0, 0, lpcs->cx, lpcs->cy,
hwnd,
NULL,
lpcs->hInstance,
0L);
if (!hwndLB)
return FALSE;
SetWindowFont(hwndLB, this->hfont, FALSE);
this->hwndLB = hwndLB;
// Determine layout of window
GetClientRect(hwnd, &rc);
InflateRect(&rc, -cxEdge, -cyEdge);
SetWindowPos(hwndLB, NULL, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOZORDER);
GetClientRect(hwndLB, &rc);
this->cxItem = rc.right - rc.left;
this->hwndTip = CreateWindow(
TOOLTIPS_CLASS,
c_szNULL,
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hwnd,
NULL,
lpcs->hInstance,
0L);
// Add a dummy tool so the delay is shorter between other tools
ti.cbSize = sizeof(ti);
ti.uFlags = TTF_IDISHWND;
ti.hwnd = this->hwndLB;
ti.uId = (UINT_PTR)this->hwndLB;
ti.lpszText = (LPTSTR)c_szNULL;
ti.rect.left = ti.rect.top = ti.rect.bottom = ti.rect.right = 0;
SendMessage(this->hwndTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
return TRUE;
}
/*----------------------------------------------------------
Purpose: Set the colors of the control
Returns: --
Cond: --
*/
void PRIVATE RecAct_SetColors(
LPRECACT this)
{
int cr;
if (IsFlagClear(this->lStyle, RAS_SINGLEITEM))
{
cr = COLOR_WINDOW;
}
else
{
cr = COLOR_3DFACE;
}
this->clrBkgnd = GetSysColor(cr);
if (this->hbrBkgnd)
DeleteBrush(this->hbrBkgnd);
this->hbrBkgnd = CreateSolidBrush(this->clrBkgnd);
}
/*----------------------------------------------------------
Purpose: Creates an imagelist of the action images
Returns: TRUE on success
Cond: --
*/
BOOL PRIVATE CreateImageList(
HIMAGELIST * phiml,
HDC hdc,
UINT idb,
int cxBmp,
int cyBmp,
int cImage,
UINT flags
)
{
BOOL bRet;
HIMAGELIST himl;
himl = ImageList_Create(cxBmp, cyBmp, flags, cImage, 1);
if (himl)
{
COLORREF clrMask;
HBITMAP hbm;
hbm = LoadBitmap(g_hinst, MAKEINTRESOURCE(idb));
ASSERT(hbm);
if (hbm)
{
HDC hdcMem = CreateCompatibleDC(hdc);
if (hdcMem)
{
HBITMAP hbmSav = SelectBitmap(hdcMem, hbm);
clrMask = GetPixel(hdcMem, 0, 0);
SelectBitmap(hdcMem, hbmSav);
bRet = (0 == ImageList_AddMasked(himl, hbm, clrMask));
DeleteDC(hdcMem);
}
else
bRet = FALSE;
DeleteBitmap(hbm);
}
else
bRet = FALSE;
}
else
bRet = FALSE;
*phiml = himl;
return bRet;
}
/*----------------------------------------------------------
Purpose: WM_CREATE handler
Returns: TRUE on success
Cond: --
*/
BOOL PRIVATE RecAct_OnCreate(
LPRECACT this,
CREATESTRUCT * lpcs)
{
BOOL bRet = FALSE;
HWND hwnd = this->hwnd;
HDC hdc;
TEXTMETRIC tm;
RECT rcT;
LOGFONT lf;
UINT flags = ILC_MASK;
this->lStyle = GetWindowLong(hwnd, GWL_STYLE);
RecAct_SetColors(this);
// Determine some font things
SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE);
this->hfont = CreateFontIndirect(&lf);
// This window is registered with the CS_OWNDC flag
this->hdcOwn = GetDC(hwnd);
ASSERT(this->hdcOwn);
hdc = this->hdcOwn;
SelectFont(hdc, this->hfont);
GetTextMetrics(hdc, &tm);
this->cyText = tm.tmHeight;
// Calculate text extent for sideitems (use the listbox font)
//
SetRectFromExtent(hdc, &rcT, c_szEllipses);
this->cxEllipses = rcT.right - rcT.left;
// Create windows used by control
if (RecAct_CreateWindows(this, lpcs))
{
this->lpfnLBProc = SubclassWindow(this->hwndLB, RecActLB_LBProc);
this->hdsa = DSA_Create(sizeof(int), 16);
if (this->hdsa)
{
// Get the system imagelist cache
this->himlCache = ImageList_Create(g_cxIcon, g_cyIcon, TRUE, 8, 8);
if (this->himlCache)
{
if(IS_WINDOW_RTL_MIRRORED(hwnd))
{
flags |= ILC_MIRROR;
}
if (CreateImageList(&this->himlAction, hdc, IDB_ACTIONS,
CX_ACTIONBMP, CY_ACTIONBMP, 8, flags))
{
SIZE size;
// Get some metrics
this->cxMenuCheck = GetSystemMetrics(SM_CXMENUCHECK);
this->cyMenuCheck = GetSystemMetrics(SM_CYMENUCHECK);
size.cx = this->cxMenuCheck;
size.cy = this->cyMenuCheck;
this->hbmpBullet = CreateBulletBitmap(&size);
if (this->hbmpBullet)
{
bRet = RecAct_CreateMenu(this);
}
}
}
}
}
return bRet;
}
/*----------------------------------------------------------
Purpose: WM_DESTROY Handler
Returns: --
Cond: --
*/
void PRIVATE RecAct_OnDestroy(
LPRECACT this)
{
if (this->himlCache)
{
ImageList_Destroy(this->himlCache);
this->himlCache = NULL;
}
if (this->himlAction)
{
ImageList_Destroy(this->himlAction);
this->himlAction = NULL;
}
if (this->hbmpBullet)
{
DeleteBitmap(this->hbmpBullet);
this->hbmpBullet = NULL;
}
if (this->hmenu)
{
DestroyMenu(this->hmenu);
this->hmenu = NULL;
}
if (this->hbrBkgnd)
DeleteBrush(this->hbrBkgnd);
if (this->hfont)
DeleteFont(this->hfont);
if (this->hdsa)
DSA_Destroy(this->hdsa);
}
/*----------------------------------------------------------
Purpose: WM_COMMAND Handler
Returns: --
Cond: --
*/
VOID PRIVATE RecAct_OnCommand(
LPRECACT this,
int id,
HWND hwndCtl,
UINT uNotifyCode)
{
if (hwndCtl == this->hwndLB)
{
switch (uNotifyCode)
{
case LBN_SELCHANGE:
break;
}
}
}
/*----------------------------------------------------------
Purpose: Handles WM_SYSKEYDOWN
Returns: 0 if we processed it
Cond: --
*/
int PRIVATE RecAct_OnSysKeyDown(
LPRECACT this,
UINT vkey,
LPARAM lKeyData)
{
int nRet = -1;
// Context menu invoked by the keyboard?
if (VK_F10 == vkey && 0 > GetKeyState(VK_SHIFT))
{
// Yes; forward the message
HWND hwndLB = this->hwndLB;
int iCaret = ListBox_GetCurSel(hwndLB);
// Is this in a property page?
if (RecAct_IsNoIcon(this) && 0 > iCaret)
{
// Yes; don't require the item to be selected
iCaret = 0;
}
if (0 <= iCaret)
{
LPRA_PRIV ppriv;
LPDOBJ pdobj;
POINT pt;
RECT rc;
// Determine where to show the context menu
ListBox_GetText(hwndLB, iCaret, &ppriv);
pdobj = RecAct_ChooseCaretDobj(this, ppriv);
ListBox_GetItemRect(hwndLB, iCaret, &rc);
pt.x = pdobj->x + (g_cxIcon / 2) + rc.left;
pt.y = pdobj->y + (g_cyIcon / 2) + rc.top;
ClientToScreen(hwndLB, &pt);
PostMessage(this->hwnd, WM_CONTEXTMENU, (WPARAM)hwndLB, MAKELPARAM(pt.x, pt.y));
}
nRet = 0;
}
return nRet;
}
// ( (4+1) is for ellipses )
#define MAX_HALF (ARRAYSIZE(pttt->szText)/2 - (4+1))
/*----------------------------------------------------------
Purpose: Handles TTN_NEEDTEXT
Returns: --
Cond: --
*/
void PRIVATE RecAct_OnNeedTipText(
LPRECACT this,
LPTOOLTIPTEXT pttt)
{
// Find the visible listbox item associated with this tip ID.
HWND hwndLB = this->hwndLB;
LPRA_PRIV ppriv;
int iItem;
BOOL bInside;
SIDEITEM * psi;
iItem = RecAct_FindItemFromTipID(this, (UINT)pttt->hdr.idFrom, &bInside);
if (LB_ERR != iItem)
{
int cb;
ListBox_GetText(hwndLB, iItem, &ppriv);
if (bInside)
psi = &ppriv->siInside;
else
psi = &ppriv->siOutside;
// Need ellipses?
cb = CbFromCch(lstrlen(psi->pszDir));
if (cb >= sizeof(pttt->szText))
{
// Yes
LPTSTR pszLastHalf;
LPTSTR psz;
LPTSTR pszStart = psi->pszDir;
LPTSTR pszEnd = &psi->pszDir[lstrlen(psi->pszDir)];
for (psz = pszEnd;
psz != pszStart && (pszEnd - psz) < MAX_HALF;
psz = CharPrev(pszStart, psz))
;
pszLastHalf = CharNext(psz);
lstrcpyn(pttt->szText, psi->pszDir, MAX_HALF);
StrCatBuff(pttt->szText, c_szEllipses, ARRAYSIZE(pttt->szText));
StrCatBuff(pttt->szText, pszLastHalf, ARRAYSIZE(pttt->szText));
}
else
lstrcpyn(pttt->szText, psi->pszDir, ARRAYSIZE(pttt->szText));
}
else
*pttt->szText = 0;
}
/*----------------------------------------------------------
Purpose: WM_NOTIFY handler
Returns: varies
Cond: --
*/
LRESULT PRIVATE RecAct_OnNotify(
LPRECACT this,
int idFrom,
NMHDR * lpnmhdr)
{
LRESULT lRet = 0;
switch (lpnmhdr->code)
{
case HDN_BEGINTRACK:
lRet = TRUE; // prevent tracking
break;
default:
break;
}
return lRet;
}
/*----------------------------------------------------------
Purpose: WM_CONTEXTMENU handler
Returns: --
Cond: --
*/
void PRIVATE RecAct_OnContextMenu(
LPRECACT this,
HWND hwnd,
int x,
int y)
{
if (hwnd == this->hwndLB)
{
POINT pt;
int iHitEntry;
BOOL bHelpOnly;
pt.x = x;
pt.y = y;
ScreenToClient(hwnd, &pt);
iHitEntry = GetHitIndex(hwnd, pt);
if (LB_ERR != iHitEntry)
{
ASSERT(iHitEntry < ListBox_GetCount(hwnd));
ListBox_SetCurSel(hwnd, iHitEntry);
ListBox_RepaintItemNow(hwnd, iHitEntry, NULL, FALSE);
bHelpOnly = FALSE;
}
else
bHelpOnly = TRUE;
// Bring up the context menu for the listbox
RecAct_DoContextMenu(this, x, y, iHitEntry, bHelpOnly);
}
}
/*----------------------------------------------------------
Purpose: Calculate the rectangle boundary of a sideitem
Returns: calculated rect
Cond: --
*/
void PRIVATE RecAct_CalcSideItemRect(
LPRECACT this,
int nSide, // SIDE_INSIDE or SIDE_OUTSIDE
int cxFile,
int cxAction,
LPRECT prcOut)
{
int x;
int y = g_cyIconMargin*2;
int cx = ((this->cxItem - cxFile - cxAction) / 2);
switch (nSide)
{
case SIDE_INSIDE:
if (RecAct_IsNoIcon(this))
x = 0;
else
x = cxFile;
break;
case SIDE_OUTSIDE:
if (RecAct_IsNoIcon(this))
x = cx + cxAction;
else
x = cxFile + cx + cxAction;
break;
default:
ASSERT(0);
break;
}
x += g_cxMargin;
prcOut->left = x + g_cxMargin;
prcOut->top = y;
prcOut->right = prcOut->left + (cx - 2*g_cxMargin);
prcOut->bottom = y + (this->cyText * 3);
}
/*----------------------------------------------------------
Purpose: Draw a reconciliation listbox entry
Returns: --
Cond: --
*/
void PRIVATE RecAct_RecomputeItemMetrics(
LPRECACT this,
LPRA_PRIV ppriv)
{
HDC hdc = this->hdcOwn;
LPDOBJ pdobj = ppriv->rgdobj;
RECT rcT;
RECT rcUnion;
TCHAR szIDS[MAXBUFLEN];
UINT ids;
int cyText = this->cyText;
int dx;
int cxFile;
int cxAction;
POINT pt;
// Compute the metrics and dimensions of each of the draw objects
// and store back into the item.
// File icon and label
pt.x = 0;
pt.y = 0;
ComputeImageRects(FIGetDisplayName(ppriv->pfi), hdc, &pt, &rcT,
&pdobj->rcLabel, g_cxIcon, g_cyIcon, g_cxIconSpacing, cyText);
pdobj->uKind = DOK_IMAGE;
pdobj->lpvObject = FIGetDisplayName(ppriv->pfi);
pdobj->uFlags = DOF_DIFFER | DOF_CENTER;
if (RecAct_IsNoIcon(this))
{
SetFlag(pdobj->uFlags, DOF_NODRAW);
cxFile = 0;
}
else
{
cxFile = rcT.right - rcT.left;
}
pdobj->x = pt.x;
pdobj->y = pt.y;
pdobj->himl = this->himlCache;
pdobj->iImage = (UINT)ppriv->pfi->lParam;
pdobj->rcBounding = rcT;
rcUnion = pdobj->rcBounding;
// Action image
ASSERT(ppriv->uAction <= ARRAYSIZE(c_mpraiaiImage));
pdobj++;
ids = GetActionText(ppriv);
pt.x = 0; // (we'll adjust this after the call)
pt.y = 0;
ComputeImageRects(SzFromIDS(ids, szIDS, ARRAYSIZE(szIDS)), hdc, &pt,
&rcT, &pdobj->rcLabel, CX_ACTIONBMP, CY_ACTIONBMP,
g_cxIconSpacing, cyText);
// (Adjust pt and the two rects to be centered in the remaining space)
cxAction = rcT.right - rcT.left;
dx = cxFile + (((this->cxItem - cxFile) / 2) - (cxAction / 2));
pt.x += dx;
OffsetRect(&rcT, dx, 0);
OffsetRect(&pdobj->rcLabel, dx, 0);
pdobj->uKind = DOK_IMAGE;
pdobj->lpvObject = IntToPtr(ids);
pdobj->uFlags = DOF_CENTER | DOF_USEIDS;
if (!RecAct_IsNoIcon(this))
SetFlag(pdobj->uFlags, DOF_IGNORESEL);
pdobj->x = pt.x;
pdobj->y = pt.y;
pdobj->himl = this->himlAction;
pdobj->iImage = c_mpraiaiImage[ppriv->uAction];
pdobj->rcBounding = rcT;
UnionRect(&rcUnion, &rcUnion, &pdobj->rcBounding);
// Sideitem Info (Inside Briefcase)
RecAct_CalcSideItemRect(this, SIDE_INSIDE, cxFile, cxAction, &rcT);
pdobj++;
pdobj->uKind = DOK_SIDEITEM;
pdobj->lpvObject = &ppriv->siInside;
pdobj->uFlags = DOF_LEFT;
pdobj->x = rcT.left;
pdobj->y = rcT.top;
pdobj->rcClip = rcT;
pdobj->rcBounding = rcT;
// Sideitem Info (Outside Briefcase)
RecAct_CalcSideItemRect(this, SIDE_OUTSIDE, cxFile, cxAction, &rcT);
pdobj++;
pdobj->uKind = DOK_SIDEITEM;
pdobj->lpvObject = &ppriv->siOutside;
pdobj->uFlags = DOF_LEFT;
pdobj->x = rcT.left;
pdobj->y = rcT.top;
pdobj->rcClip = rcT;
pdobj->rcBounding = rcT;
UnionRect(&rcUnion, &rcUnion, &rcT);
// Set the bounding rect of this item.
ppriv->cx = rcUnion.right - rcUnion.left;
ppriv->cy = max((rcUnion.bottom - rcUnion.top), g_cyIconSpacing);
}
/*----------------------------------------------------------
Purpose: WM_MEASUREITEM handler
Returns: --
Cond: --
*/
BOOL PRIVATE RecAct_OnMeasureItem(
LPRECACT this,
LPMEASUREITEMSTRUCT lpmis)
{
HDC hdc = this->hdcOwn;
switch (lpmis->CtlType)
{
case ODT_LISTBOX: {
LPRA_PRIV ppriv = (LPRA_PRIV)lpmis->itemData;
// Recompute item metrics?
if (RECOMPUTE == ppriv->cx)
{
RecAct_RecomputeItemMetrics(this, ppriv); // Yes
}
lpmis->itemHeight = ppriv->cy;
}
return TRUE;
case ODT_MENU:
{
int i;
int cxMac = 0;
RECT rc;
TCHAR sz[MAXBUFLEN];
// Calculate based on font and image dimensions.
//
SelectFont(hdc, this->hfont);
cxMac = 0;
for (i = 0; i < ARRAYSIZE(c_rgramid); i++)
{
SzFromIDS(c_rgramid[i].ids, sz, ARRAYSIZE(sz));
SetRectFromExtent(hdc, &rc, sz);
cxMac = max(cxMac,
g_cxMargin + CX_ACTIONBMP + g_cxMargin +
(rc.right-rc.left) + g_cxMargin);
}
lpmis->itemHeight = max(this->cyText, CY_ACTIONBMP);
lpmis->itemWidth = cxMac;
}
return TRUE;
}
return FALSE;
}
/*----------------------------------------------------------
Purpose: Draw a reconciliation listbox entry
Returns: --
Cond: --
*/
void PRIVATE RecAct_DrawLBItem(
LPRECACT this,
const DRAWITEMSTRUCT * lpcdis)
{
LPRA_PRIV ppriv = (LPRA_PRIV)lpcdis->itemData;
HDC hdc = lpcdis->hDC;
RECT rc = lpcdis->rcItem;
POINT ptSav;
LPDOBJ pdobj;
UINT cdobjs;
if (!ppriv)
{
// Empty listbox and we're getting the focus
return;
}
SetBkMode(hdc, TRANSPARENT); // required for Shell_DrawText
SetViewportOrgEx(hdc, rc.left, rc.top, &ptSav);
// The Chicago-look mandates that icon and filename are selected,
// the rest of the entry is normal.
// Recompute item metrics?
if (RECOMPUTE == ppriv->cx)
{
RecAct_RecomputeItemMetrics(this, ppriv); // Yes
}
// Do we need to redraw everything?
if (IsFlagSet(lpcdis->itemAction, ODA_DRAWENTIRE))
{
// Yes
TOOLINFO ti;
cdobjs = ARRAYSIZE(ppriv->rgdobj);
pdobj = ppriv->rgdobj;
// Get the tooltip ID given this ith visible entry
ti.cbSize = sizeof(ti);
ti.uFlags = 0;
ti.hwnd = this->hwndLB;
ti.lpszText = LPSTR_TEXTCALLBACK;
ti.uId = RecAct_GetTipIDFromItemID(this, lpcdis->itemID);
ti.rect = ppriv->rgdobj[IDOBJ_INSIDE].rcBounding;
OffsetRect(&ti.rect, lpcdis->rcItem.left, lpcdis->rcItem.top);
SendMessage(this->hwndTip, TTM_NEWTOOLRECT, 0, (LPARAM)&ti);
ti.uId++;
ti.rect = ppriv->rgdobj[IDOBJ_OUTSIDE].rcBounding;
OffsetRect(&ti.rect, lpcdis->rcItem.left, lpcdis->rcItem.top);
SendMessage(this->hwndTip, TTM_NEWTOOLRECT, 0, (LPARAM)&ti);
}
else
{
// No; should we even draw the file icon or action icon?
if (lpcdis->itemAction & (ODA_FOCUS | ODA_SELECT))
{
cdobjs = 1; // Yes
pdobj = RecAct_ChooseCaretDobj(this, ppriv);
}
else
{
cdobjs = 0; // No
pdobj = ppriv->rgdobj;
}
}
Dobj_Draw(hdc, pdobj, cdobjs, lpcdis->itemState, this->cxEllipses, this->cyText,
this->clrBkgnd);
// Clean up
//
SetViewportOrgEx(hdc, ptSav.x, ptSav.y, NULL);
}
/*----------------------------------------------------------
Purpose: Draw an action menu item
Returns: --
Cond: --
*/
void PRIVATE RecAct_DrawMenuItem(
LPRECACT this,
const DRAWITEMSTRUCT * lpcdis)
{
LPRAMID pramid = (LPRAMID)lpcdis->itemData;
HDC hdc = lpcdis->hDC;
RECT rc = lpcdis->rcItem;
DOBJ dobj;
LPDOBJ pdobj;
POINT ptSav;
MENUITEMINFO mii;
int cx;
int cy;
UINT uFlags;
UINT uFlagsChecked;
ASSERT(pramid);
if (lpcdis->itemID == -1)
return;
SetViewportOrgEx(hdc, rc.left, rc.top, &ptSav);
OffsetRect(&rc, -rc.left, -rc.top);
cx = rc.right - rc.left;
cy = rc.bottom - rc.top;
// Get the menu state
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_STATE | MIIM_CHECKMARKS;
GetMenuItemInfo(this->hmenu, lpcdis->itemID, FALSE, &mii);
uFlagsChecked = IsFlagClear(mii.fState, MFS_CHECKED) ? DOF_NODRAW : 0;
uFlags = DOF_DIFFER | DOF_MENU | DOF_USEIDS;
if (IsFlagSet(mii.fState, MFS_GRAYED))
SetFlag(uFlags, DOF_DISABLED);
// Build the array of DObjs that we want to draw.
// Action image
pdobj = &dobj;
pdobj->uKind = DOK_IMAGE;
pdobj->lpvObject = IntToPtr(pramid->ids);
pdobj->himl = this->himlAction;
pdobj->iImage = pramid->iImage;
pdobj->uFlags = uFlags;
pdobj->x = g_cxMargin;
pdobj->y = (cy - CY_ACTIONBMP) / 2;
pdobj->rcLabel.left = 0;
pdobj->rcLabel.right = cx;
pdobj->rcLabel.top = 0;
pdobj->rcLabel.bottom = cy;
// Draw the entry...
//
Dobj_Draw(hdc, &dobj, 1, lpcdis->itemState, 0, this->cyText, this->clrBkgnd);
// Clean up
//
SetViewportOrgEx(hdc, ptSav.x, ptSav.y, NULL);
}
/*----------------------------------------------------------
Purpose: WM_DRAWITEM handler
Returns: --
Cond: --
*/
BOOL PRIVATE RecAct_OnDrawItem(
LPRECACT this,
const DRAWITEMSTRUCT * lpcdis)
{
switch (lpcdis->CtlType)
{
case ODT_LISTBOX:
RecAct_DrawLBItem(this, lpcdis);
return TRUE;
case ODT_MENU:
RecAct_DrawMenuItem(this, lpcdis);
return TRUE;
}
return FALSE;
}
/*----------------------------------------------------------
Purpose: WM_COMPAREITEM handler
Returns: -1 (item 1 precedes item 2), 0 (equal), 1 (item 2 precedes item 1)
Cond: --
*/
int PRIVATE RecAct_OnCompareItem(
LPRECACT this,
const COMPAREITEMSTRUCT * lpcis)
{
LPRA_PRIV ppriv1 = (LPRA_PRIV)lpcis->itemData1;
LPRA_PRIV ppriv2 = (LPRA_PRIV)lpcis->itemData2;
// We sort based on name of file
//
return lstrcmpi(FIGetPath(ppriv1->pfi), FIGetPath(ppriv2->pfi));
}
/*----------------------------------------------------------
Purpose: WM_DELETEITEM handler
Returns: --
Cond: --
*/
void RecAct_OnDeleteLBItem(
LPRECACT this,
const DELETEITEMSTRUCT * lpcdis)
{
switch (lpcdis->CtlType)
{
case ODT_LISTBOX:
{
LPRA_PRIV ppriv = (LPRA_PRIV)lpcdis->itemData;
ASSERT(ppriv);
if (ppriv)
{
FIFree(ppriv->pfi);
GFree(ppriv->siInside.pszDir);
GFree(ppriv->siOutside.pszDir);
GFree(ppriv);
}
}
break;
}
}
/*----------------------------------------------------------
Purpose: WM_CTLCOLORLISTBOX handler
Returns: --
Cond: --
*/
HBRUSH PRIVATE RecAct_OnCtlColorListBox(
LPRECACT this,
HDC hdc,
HWND hwndLB,
int nType)
{
return this->hbrBkgnd;
}
/*----------------------------------------------------------
Purpose: WM_PAINT handler
Returns: --
Cond: --
*/
void RecAct_OnPaint(
LPRECACT this)
{
HWND hwnd = this->hwnd;
PAINTSTRUCT ps;
RECT rc;
HDC hdc;
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
if (IsFlagSet(this->lStyle, RAS_SINGLEITEM))
{
DrawEdge(hdc, &rc, BDR_SUNKENINNER, BF_TOPLEFT);
DrawEdge(hdc, &rc, BDR_SUNKENOUTER, BF_BOTTOMRIGHT);
}
else
{
DrawEdge(hdc, &rc, EDGE_SUNKEN, BF_RECT);
}
EndPaint(hwnd, &ps);
}
/*----------------------------------------------------------
Purpose: WM_SETFONT handler
Returns: --
Cond: --
*/
void RecAct_OnSetFont(
LPRECACT this,
HFONT hfont,
BOOL bRedraw)
{
this->hfont = hfont;
FORWARD_WM_SETFONT(this->hwnd, hfont, bRedraw, RecAct_DefProc);
}
/*----------------------------------------------------------
Purpose: WM_SETFOCUS handler
Returns: --
Cond: --
*/
void RecAct_OnSetFocus(
LPRECACT this,
HWND hwndOldFocus)
{
SetFocus(this->hwndLB);
}
/*----------------------------------------------------------
Purpose: WM_SYSCOLORCHANGE handler
Returns: --
Cond: --
*/
void RecAct_OnSysColorChange(
LPRECACT this)
{
RecAct_SetColors(this);
InvalidateRect(this->hwnd, NULL, TRUE);
}
/*----------------------------------------------------------
Purpose: Insert item
Returns: index
Cond: --
*/
int PRIVATE RecAct_OnInsertItem(
LPRECACT this,
const LPRA_ITEM pitem)
{
HWND hwndLB = this->hwndLB;
LPRA_PRIV pprivNew;
TCHAR szPath[MAXPATHLEN];
int iRet = -1;
int iItem = LB_ERR;
ASSERT(pitem);
ASSERT(pitem->siInside.pszDir);
ASSERT(pitem->siOutside.pszDir);
ASSERT(pitem->pszName);
pprivNew = GAlloc(sizeof(*pprivNew));
if (pprivNew)
{
SetWindowRedraw(hwndLB, FALSE);
// Fill the prerequisite fields first
//
pprivNew->uStyle = pitem->uStyle;
pprivNew->uAction = pitem->uAction;
// Set the fileinfo stuff and large icon system-cache index.
// If we can't get the fileinfo of the inside file, get the outside
// file. If neither can be found, then we fail
//
lstrcpyn(szPath, SkipDisplayJunkHack(&pitem->siInside), ARRAYSIZE(szPath));
if (IsFlagClear(pitem->uStyle, RAIS_FOLDER))
PathAppend(szPath, pitem->pszName);
if (FAILED(FICreate(szPath, &pprivNew->pfi, FIF_ICON)))
{
// Try the outside file
//
lstrcpyn(szPath, SkipDisplayJunkHack(&pitem->siOutside), ARRAYSIZE(szPath));
if (IsFlagClear(pitem->uStyle, RAIS_FOLDER))
PathAppend(szPath, pitem->pszName);
if (FAILED(FICreate(szPath, &pprivNew->pfi, FIF_ICON)))
{
// Don't try to touch the file
if (FAILED(FICreate(szPath, &pprivNew->pfi, FIF_ICON | FIF_DONTTOUCH)))
goto Insert_Cleanup;
}
}
ASSERT(pprivNew->pfi);
pprivNew->pfi->lParam = (LPARAM)ImageList_AddIcon(this->himlCache, pprivNew->pfi->hicon);
// Fill in the rest of the fields
//
lstrcpyn(szPath, pitem->siInside.pszDir, ARRAYSIZE(szPath));
if (IsFlagSet(pitem->uStyle, RAIS_FOLDER))
PathRemoveFileSpec(szPath);
if (!GSetString(&pprivNew->siInside.pszDir, szPath))
goto Insert_Cleanup;
pprivNew->siInside.uState = pitem->siInside.uState;
pprivNew->siInside.fs = pitem->siInside.fs;
pprivNew->siInside.ichRealPath = pitem->siInside.ichRealPath;
lstrcpyn(szPath, pitem->siOutside.pszDir, ARRAYSIZE(szPath));
if (IsFlagSet(pitem->uStyle, RAIS_FOLDER))
PathRemoveFileSpec(szPath);
if (!GSetString(&pprivNew->siOutside.pszDir, szPath))
goto Insert_Cleanup;
pprivNew->siOutside.uState = pitem->siOutside.uState;
pprivNew->siOutside.fs = pitem->siOutside.fs;
pprivNew->siOutside.ichRealPath = pitem->siOutside.ichRealPath;
pprivNew->lParam = pitem->lParam;
pprivNew->cx = RECOMPUTE;
// We know we're doing a redundant sorted add if the element
// needs to be inserted at the end of the list, but who cares.
//
if (pitem->iItem >= RecAct_GetCount(this))
iItem = ListBox_AddString(hwndLB, pprivNew);
else
iItem = ListBox_InsertString(hwndLB, pitem->iItem, pprivNew);
if (iItem == LB_ERR)
goto Insert_Cleanup;
SetWindowRedraw(hwndLB, TRUE);
iRet = iItem;
}
goto Insert_End;
Insert_Cleanup:
// Have DeleteString handler clean up field allocations
// of pitem.
//
if (iItem != LB_ERR)
ListBox_DeleteString(hwndLB, iItem);
else
{
FIFree(pprivNew->pfi);
GFree(pprivNew);
}
SetWindowRedraw(hwndLB, TRUE);
Insert_End:
return iRet;
}
/*----------------------------------------------------------
Purpose: Delete item
Returns: count of items left
Cond: --
*/
int PRIVATE RecAct_OnDeleteItem(
LPRECACT this,
int i)
{
HWND hwndLB = this->hwndLB;
return ListBox_DeleteString(hwndLB, i);
}
/*----------------------------------------------------------
Purpose: Delete all items
Returns: TRUE
Cond: --
*/
BOOL PRIVATE RecAct_OnDeleteAllItems(
LPRECACT this)
{
ListBox_ResetContent(this->hwndLB);
return TRUE;
}
/*----------------------------------------------------------
Purpose: Get item
Returns: TRUE on success
Cond: --
*/
BOOL PRIVATE RecAct_OnGetItem(
LPRECACT this,
LPRA_ITEM pitem)
{
LPRA_PRIV ppriv;
HWND hwndLB = this->hwndLB;
UINT uMask;
int iItem;
if (!pitem)
return FALSE;
iItem = pitem->iItem;
uMask = pitem->mask;
ListBox_GetText(hwndLB, iItem, &ppriv);
if (uMask & RAIF_ACTION)
pitem->uAction = ppriv->uAction;
if (uMask & RAIF_NAME)
pitem->pszName = FIGetPath(ppriv->pfi);
if (uMask & RAIF_STYLE)
pitem->uStyle = ppriv->uStyle;
if (uMask & RAIF_INSIDE)
pitem->siInside = ppriv->siInside;
if (uMask & RAIF_OUTSIDE)
pitem->siOutside = ppriv->siOutside;
if (uMask & RAIF_LPARAM)
pitem->lParam = ppriv->lParam;
return TRUE;
}
/*----------------------------------------------------------
Purpose: Set item
Returns: TRUE on success
Cond: --
*/
BOOL PRIVATE RecAct_OnSetItem(
LPRECACT this,
LPRA_ITEM pitem)
{
LPRA_PRIV ppriv;
HWND hwndLB = this->hwndLB;
UINT uMask;
int iItem;
if (!pitem)
return FALSE;
uMask = pitem->mask;
iItem = pitem->iItem;
ListBox_GetText(hwndLB, iItem, &ppriv);
if (uMask & RAIF_ACTION)
ppriv->uAction = pitem->uAction;
if (uMask & RAIF_STYLE)
ppriv->uStyle = pitem->uStyle;
if (uMask & RAIF_NAME)
{
if (!FISetPath(&ppriv->pfi, pitem->pszName, FIF_ICON))
return FALSE;
ppriv->pfi->lParam = (LPARAM)ImageList_AddIcon(this->himlCache, ppriv->pfi->hicon);
}
if (uMask & RAIF_INSIDE)
{
if (!GSetString(&ppriv->siInside.pszDir, pitem->siInside.pszDir))
return FALSE;
ppriv->siInside.uState = pitem->siInside.uState;
ppriv->siInside.fs = pitem->siInside.fs;
ppriv->siInside.ichRealPath = pitem->siInside.ichRealPath;
}
if (uMask & RAIF_OUTSIDE)
{
if (!GSetString(&ppriv->siOutside.pszDir, pitem->siOutside.pszDir))
return FALSE;
ppriv->siOutside.uState = pitem->siOutside.uState;
ppriv->siOutside.fs = pitem->siOutside.fs;
ppriv->siOutside.ichRealPath = pitem->siOutside.ichRealPath;
}
if (uMask & RAIF_LPARAM)
ppriv->lParam = pitem->lParam;
return TRUE;
}
/*----------------------------------------------------------
Purpose: Get the current selection
Returns: index
Cond: --
*/
int PRIVATE RecAct_OnGetCurSel(
LPRECACT this)
{
return ListBox_GetCurSel(this->hwndLB);
}
/*----------------------------------------------------------
Purpose: Set the current selection
Returns: --
Cond: --
*/
int PRIVATE RecAct_OnSetCurSel(
LPRECACT this,
int i)
{
int iRet = ListBox_SetCurSel(this->hwndLB, i);
if (iRet != LB_ERR)
RecAct_SendSelChange(this, i);
return iRet;
}
/*----------------------------------------------------------
Purpose: Find an item
Returns: TRUE on success
Cond: --
*/
int PRIVATE RecAct_OnFindItem(
LPRECACT this,
int iStart,
const RA_FINDITEM * prafi)
{
HWND hwndLB = this->hwndLB;
UINT uMask = prafi->flags;
LPRA_PRIV ppriv;
BOOL bPass;
int i;
int cItems = ListBox_GetCount(hwndLB);
for (i = iStart+1; i < cItems; i++)
{
bPass = TRUE; // assume we pass
ListBox_GetText(hwndLB, i, &ppriv);
if (uMask & RAFI_NAME &&
!IsSzEqual(FIGetPath(ppriv->pfi), prafi->psz))
bPass = FALSE;
if (uMask & RAFI_ACTION && ppriv->uAction != prafi->uAction)
bPass = FALSE;
if (uMask & RAFI_LPARAM && ppriv->lParam != prafi->lParam)
bPass = FALSE;
if (bPass)
break; // found it
}
return i == cItems ? -1 : i;
}
///////////////////////////////////////////////////// EXPORTED FUNCTIONS
/*----------------------------------------------------------
Purpose: RecAct window proc
Returns: varies
Cond: --
*/
LRESULT CALLBACK RecAct_WndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
LPRECACT this = RecAct_GetPtr(hwnd);
if (this == NULL)
{
if (msg == WM_NCCREATE)
{
this = GAlloc(sizeof(*this));
ASSERT(this);
if (!this)
return 0L; // OOM failure
this->hwnd = hwnd;
RecAct_SetPtr(hwnd, this);
}
else
{
return RecAct_DefProc(hwnd, msg, wParam, lParam);
}
}
if (msg == WM_NCDESTROY)
{
GFree(this);
RecAct_SetPtr(hwnd, NULL);
}
switch (msg)
{
HANDLE_MSG(this, WM_CREATE, RecAct_OnCreate);
HANDLE_MSG(this, WM_DESTROY, RecAct_OnDestroy);
HANDLE_MSG(this, WM_SETFONT, RecAct_OnSetFont);
HANDLE_MSG(this, WM_COMMAND, RecAct_OnCommand);
HANDLE_MSG(this, WM_NOTIFY, RecAct_OnNotify);
HANDLE_MSG(this, WM_MEASUREITEM, RecAct_OnMeasureItem);
HANDLE_MSG(this, WM_DRAWITEM, RecAct_OnDrawItem);
HANDLE_MSG(this, WM_COMPAREITEM, RecAct_OnCompareItem);
HANDLE_MSG(this, WM_DELETEITEM, RecAct_OnDeleteLBItem);
HANDLE_MSG(this, WM_CONTEXTMENU, RecAct_OnContextMenu);
HANDLE_MSG(this, WM_SETFOCUS, RecAct_OnSetFocus);
HANDLE_MSG(this, WM_CTLCOLORLISTBOX, RecAct_OnCtlColorListBox);
HANDLE_MSG(this, WM_PAINT, RecAct_OnPaint);
HANDLE_MSG(this, WM_SYSCOLORCHANGE, RecAct_OnSysColorChange);
case WM_HELP:
WinHelp(this->hwnd, c_szWinHelpFile, HELP_CONTEXTPOPUP, IDH_BFC_UPDATE_SCREEN);
return 0;
case RAM_GETITEMCOUNT:
return (LRESULT)RecAct_GetCount(this);
case RAM_GETITEM:
return (LRESULT)RecAct_OnGetItem(this, (LPRA_ITEM)lParam);
case RAM_SETITEM:
return (LRESULT)RecAct_OnSetItem(this, (const LPRA_ITEM)lParam);
case RAM_INSERTITEM:
return (LRESULT)RecAct_OnInsertItem(this, (const LPRA_ITEM)lParam);
case RAM_DELETEITEM:
return (LRESULT)RecAct_OnDeleteItem(this, (int)wParam);
case RAM_DELETEALLITEMS:
return (LRESULT)RecAct_OnDeleteAllItems(this);
case RAM_GETCURSEL:
return (LRESULT)RecAct_OnGetCurSel(this);
case RAM_SETCURSEL:
return (LRESULT)RecAct_OnSetCurSel(this, (int)wParam);
case RAM_FINDITEM:
return (LRESULT)RecAct_OnFindItem(this, (int)wParam, (const RA_FINDITEM *)lParam);
case RAM_REFRESH:
RedrawWindow(this->hwndLB, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
default:
return RecAct_DefProc(hwnd, msg, wParam, lParam);
}
}
///////////////////////////////////////////////////// PUBLIC FUNCTIONS
/*----------------------------------------------------------
Purpose: Initialize the reconciliation-action window class
Returns: TRUE on success
Cond: --
*/
BOOL PUBLIC RecAct_Init(
HINSTANCE hinst)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_DBLCLKS | CS_OWNDC;
wc.lpfnWndProc = RecAct_WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(LPRECACT);
wc.hInstance = hinst;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground= NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName= WC_RECACT;
wc.hIconSm = NULL;
return RegisterClassEx(&wc) != 0;
}
/*----------------------------------------------------------
Purpose: Clean up RecAct window class
Returns: --
Cond: --
*/
void PUBLIC RecAct_Term(
HINSTANCE hinst)
{
UnregisterClass(WC_RECACT, hinst);
}
/*----------------------------------------------------------
Purpose: Special sub-class listbox proc
Returns: varies
Cond: --
*/
LRESULT _export CALLBACK RecActLB_LBProc(
HWND hwnd, // window handle
UINT uMsg, // window message
WPARAM wparam, // varies
LPARAM lparam) // varies
{
LRESULT lRet;
LPRECACT lpra = NULL;
// Get the instance data for the control
lpra = RecAct_GetPtr(GetParent(hwnd));
ASSERT(lpra);
switch (uMsg)
{
case WM_NOTIFY: {
NMHDR * pnmhdr = (NMHDR *)lparam;
if (TTN_NEEDTEXT == pnmhdr->code)
{
RecAct_OnNeedTipText(lpra, (LPTOOLTIPTEXT)pnmhdr);
}
}
break;
case WM_SYSKEYDOWN: {
lRet = RecAct_OnSysKeyDown(lpra, (UINT)wparam, lparam);
if (0 != lRet)
lRet = RecActLB_DefProc(lpra->lpfnLBProc, hwnd, uMsg, wparam, lparam);
}
break;
case WM_MOUSEMOVE: {
MSG msg;
ASSERT(hwnd == lpra->hwndLB);
msg.lParam = lparam;
msg.wParam = wparam;
msg.message = uMsg;
msg.hwnd = hwnd;
SendMessage(lpra->hwndTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
lRet = RecActLB_DefProc(lpra->lpfnLBProc, hwnd, uMsg, wparam, lparam);
}
break;
default:
lRet = RecActLB_DefProc(lpra->lpfnLBProc, hwnd, uMsg, wparam, lparam);
break;
}
return lRet;
}
//---------------------------------------------------------------------------
//
//---------------------------------------------------------------------------
/*----------------------------------------------------------
Purpose: Converts a recnode state to a sideitem state
Returns: see above
Cond: --
*/
UINT PRIVATE SiFromRns(
RECNODESTATE rnstate)
{
switch (rnstate)
{
#ifdef NEW_REC
case RNS_NEVER_RECONCILED: return SI_CHANGED;
#endif
case RNS_UNAVAILABLE: return SI_UNAVAILABLE;
case RNS_DOES_NOT_EXIST: return SI_NOEXIST;
case RNS_DELETED: return SI_DELETED;
case RNS_NOT_RECONCILED: return SI_UNCHANGED;
case RNS_UP_TO_DATE: return SI_UNCHANGED;
case RNS_CHANGED: return SI_CHANGED;
default:
ASSERT(0);
return SI_UNCHANGED;
}
}
/*----------------------------------------------------------
Purpose: Hack to skip potential volume name.
Returns: pointer to beginning of pathname in sideitem
Cond: --
*/
LPCTSTR PRIVATE SkipDisplayJunkHack(
LPSIDEITEM psi)
{
UINT ich;
ASSERT(psi);
ASSERT(psi->pszDir);
ASSERT(TEXT('(') == *psi->pszDir && 0 < psi->ichRealPath ||
0 == psi->ichRealPath);
ASSERT(psi->ichRealPath <= (UINT)lstrlen(psi->pszDir));
// Paranoid checking here. This function is being added close
// to RTM, so as an added safety net, we're adding this min()
// check. For Nashville, after we're sure that there is no
// problem with ichRealPath, we can remove the min() function.
ich = min(psi->ichRealPath, (UINT)lstrlen(psi->pszDir));
return &psi->pszDir[ich];
}
/*----------------------------------------------------------
Purpose: Returns a path that uses the share name of the hvid,
or the machine name if that is not available.
Returns: Pointer to buffer
Cond: --
*/
LPTSTR PRIVATE GetAlternativePath(
LPTSTR pszBuf, // Must be MAX_PATH in length
int cchMax, // Must be equal to MAX_PATH
LPCTSTR pszPath,
HVOLUMEID hvid,
LPUINT pichRealPath)
{
TWINRESULT tr;
VOLUMEDESC vd;
ASSERT(pichRealPath);
*pichRealPath = 0;
vd.ulSize = sizeof(vd);
tr = Sync_GetVolumeDescription(hvid, &vd);
if (TR_SUCCESS == tr)
{
// Is a share name available?
if (IsFlagSet(vd.dwFlags, VD_FL_NET_RESOURCE_VALID))
{
// Yes; use that
lstrcpyn(pszBuf, vd.rgchNetResource, cchMax);
PathAppend(pszBuf, PathFindEndOfRoot(pszPath));
PathMakePresentable(pszBuf);
}
else if (IsFlagSet(vd.dwFlags, VD_FL_VOLUME_LABEL_VALID))
{
// No; use volume label
LPTSTR pszMsg;
PathMakePresentable(vd.rgchVolumeLabel);
if (ConstructMessage(&pszMsg, g_hinst, MAKEINTRESOURCE(IDS_ALTNAME),
vd.rgchVolumeLabel, pszPath))
{
lstrcpyn(pszBuf, pszMsg, cchMax);
GFree(pszMsg);
}
else
lstrcpyn(pszBuf, pszPath, cchMax);
*pichRealPath = 3 + lstrlen(vd.rgchVolumeLabel);
PathMakePresentable(&pszBuf[*pichRealPath]);
}
else
{
lstrcpyn(pszBuf, pszPath, cchMax);
PathMakePresentable(pszBuf);
}
}
else
{
lstrcpyn(pszBuf, pszPath, cchMax);
PathMakePresentable(pszBuf);
}
return pszBuf;
}
/*----------------------------------------------------------
Purpose: Constructs a path that would be appropriate for
the sideitem structure. The path is placed in the
provided buffer.
Typically the path will simply be the folder path in
the recnode. In cases when the recnode is unavailable,
this function prepends the machine name (or share name)
to the path.
Returns: --
Cond: --
*/
void PRIVATE PathForSideItem(
LPTSTR pszBuf, // Must be MAX_PATH in length
int cchMax, // Must be equal to MAX_PATH
HVOLUMEID hvid,
LPCTSTR pszFolder,
RECNODESTATE rns,
LPUINT pichRealPath)
{
ASSERT(pszBuf);
ASSERT(pszFolder);
ASSERT(pichRealPath);
if (RNS_UNAVAILABLE == rns)
GetAlternativePath(pszBuf, cchMax, pszFolder, hvid, pichRealPath);
else
{
lstrcpyn(pszBuf, pszFolder, cchMax);
PathMakePresentable(pszBuf);
*pichRealPath = 0;
}
MyPathRemoveBackslash(pszBuf);
}
/*----------------------------------------------------------
Purpose: Determines the recact action based on the combination
of the inside and outside recnode actions
Returns: FALSE if this pair seems like an unlikely match.
(This can occur if there are two recnodes inside the
briefcase and we choose the wrong one such that the
pair consists of two destinations but no source.)
Cond: --
*/
BOOL PRIVATE DeriveFileAction(
RA_ITEM * pitem,
RECNODEACTION rnaInside,
RECNODEACTION rnaOutside)
{
BOOL bRet = TRUE;
if (RNA_COPY_FROM_ME == rnaInside &&
RNA_COPY_TO_ME == rnaOutside)
{
pitem->uAction = RAIA_TOOUT;
}
else if (RNA_COPY_TO_ME == rnaInside &&
RNA_COPY_FROM_ME == rnaOutside)
{
pitem->uAction = RAIA_TOIN;
}
#ifdef NEW_REC
else if (RNA_DELETE_ME == rnaInside)
{
pitem->uAction = RAIA_DELETEIN;
}
else if (RNA_DELETE_ME == rnaOutside)
{
pitem->uAction = RAIA_DELETEOUT;
}
#endif
else if (RNA_MERGE_ME == rnaInside &&
RNA_MERGE_ME == rnaOutside)
{
pitem->uAction = RAIA_MERGE;
}
else if (RNA_COPY_TO_ME == rnaInside &&
RNA_MERGE_ME == rnaOutside)
{
// (This is the merge-first-then-copy to third
// file case. We sorta punt because we're not
// showing the implicit merge.)
pitem->uAction = RAIA_TOIN;
}
else if (RNA_MERGE_ME == rnaInside &&
RNA_COPY_TO_ME == rnaOutside)
{
// (This is the merge-first-then-copy to third
// file case. We sorta punt because we're not
// showing the implicit merge.)
pitem->uAction = RAIA_TOOUT;
}
else if (RNA_NOTHING == rnaInside)
{
// Is one side unavailable?
if (SI_UNAVAILABLE == pitem->siInside.uState ||
SI_UNAVAILABLE == pitem->siOutside.uState)
{
// Yes; force a skip
pitem->uAction = RAIA_SKIP;
}
else if (SI_DELETED == pitem->siOutside.uState)
{
// No; the outside was deleted and the user had previously
// said don't delete, so it is an orphan now.
pitem->uAction = RAIA_ORPHAN;
}
else
{
// No; it is up-to-date or both sides don't exist
pitem->uAction = RAIA_NOTHING;
}
}
else
{
pitem->uAction = RAIA_TOIN;
bRet = FALSE;
}
return bRet;
}
/*----------------------------------------------------------
Purpose: Determines the action and possibly a better inside
path if there are multiple nodes to pick from.
Returns: better (or same) inside path
Cond: --
*/
PCHOOSESIDE PRIVATE DeriveFileActionAndSide(
RA_ITEM * pitem,
HDSA hdsa,
PCHOOSESIDE pchsideInside,
PCHOOSESIDE pchsideOutside, // May be NULL
BOOL bKeepFirstChoice)
{
ASSERT(pchsideInside);
if (pchsideOutside)
{
PRECNODE prnInside = pchsideInside->prn;
PRECNODE prnOutside = pchsideOutside->prn;
PRECITEM pri = prnInside->priParent;
#ifndef NEW_REC
// Was the original deleted?
if (RNS_DELETED == prnOutside->rnstate)
{
// Yes; make this an orphan
TRACE_MSG(TF_GENERAL, TEXT("Found outside path to be deleted"));
pitem->uAction = RAIA_ORPHAN;
}
else
#endif
{
// No
BOOL bDoAgain;
PCHOOSESIDE pchside = pchsideInside;
// Determine the action based on the currently
// chosen inside and outside pair. If DeriveFileAction
// determines that the current inside selection is
// unlikely, we get the next best choice and try
// again.
do
{
BOOL bGetNextBest = !DeriveFileAction(pitem,
pchside->prn->rnaction,
prnOutside->rnaction);
bDoAgain = FALSE;
if (!bKeepFirstChoice)
{
if (bGetNextBest &&
2 < pri->ulcNodes)
{
TRACE_MSG(TF_GENERAL, TEXT("Getting next best node"));
if (!ChooseSide_GetNextBest(hdsa, &pchside))
break;
bDoAgain = TRUE;
}
else if (!bGetNextBest)
pchsideInside = pchside;
else
ASSERT(0);
}
} while (bDoAgain);
// Is this a broken merge?
if (RIA_BROKEN_MERGE == pri->riaction)
{
// Yes; override and say it is a conflict
pitem->uAction = RAIA_CONFLICT;
}
}
}
else
{
TRACE_MSG(TF_GENERAL, TEXT("Outside path doesn't exist in recitem"));
pitem->uAction = RAIA_ORPHAN;
}
return pchsideInside;
}
/*----------------------------------------------------------
Purpose: Updates *prns and *prna based on given pchside, or
leaves them alone.
Returns: --
Cond: --
*/
void PRIVATE DeriveFolderStateAndAction(
PCHOOSESIDE pchside,
RECNODESTATE * prns,
UINT * puAction)
{
PRECNODE prn;
ASSERT(pchside);
ASSERT(prns);
ASSERT(puAction);
ASSERT(RAIA_SOMETHING == *puAction || RAIA_NOTHING == *puAction ||
RAIA_SKIP == *puAction);
prn = pchside->prn;
ASSERT(prn);
switch (prn->rnstate)
{
case RNS_UNAVAILABLE:
*prns = RNS_UNAVAILABLE;
*puAction = RAIA_SKIP; // (Always takes precedence)
break;
#ifdef NEW_REC
case RNS_NEVER_RECONCILED:
#endif
case RNS_CHANGED:
*prns = RNS_CHANGED;
if (RAIA_NOTHING == *puAction)
*puAction = RAIA_SOMETHING;
break;
case RNS_DELETED:
#ifdef NEW_REC
if (RNA_DELETE_ME == prn->rnaction)
{
*prns = RNS_CHANGED;
if (RAIA_NOTHING == *puAction)
*puAction = RAIA_SOMETHING;
}
#else
// Leave the state as it is
#endif
break;
case RNS_DOES_NOT_EXIST:
case RNS_UP_TO_DATE:
case RNS_NOT_RECONCILED:
switch (prn->rnaction)
{
case RNA_COPY_TO_ME:
#ifdef NEW_REC
if (RAIA_NOTHING == *puAction)
*puAction = RAIA_SOMETHING;
#else
// Poor man's tombstoning. Don't say the folder
// needs updating if files have been deleted or
// the whole folder has been deleted.
//
if (!PathExists(prn->pcszFolder))
{
// Folder is gone. Say this is an orphan now.
*prns = RNS_DELETED;
}
else if (RAIA_NOTHING == *puAction)
{
*puAction = RAIA_SOMETHING;
}
#endif
break;
#ifdef NEW_REC
case RNA_DELETE_ME:
#endif
case RNA_MERGE_ME:
if (RAIA_NOTHING == *puAction)
*puAction = RAIA_SOMETHING;
break;
}
break;
default:
ASSERT(0);
break;
}
}
/*----------------------------------------------------------
Purpose: Determine the recnode state of a folder that has
no intersecting recnodes.
Returns: recnode state
Cond: --
*/
RECNODESTATE PRIVATE DeriveFolderState(
PCHOOSESIDE pchside)
{
FOLDERTWINSTATUS uStatus;
RECNODESTATE rns;
Sync_GetFolderTwinStatus((HFOLDERTWIN)pchside->htwin, NULL, 0, &uStatus);
if (FTS_UNAVAILABLE == uStatus)
rns = RNS_UNAVAILABLE;
else
rns = RNS_UP_TO_DATE;
return rns;
}
/*----------------------------------------------------------
Purpose: Initialize a paired-twin structure assuming pszPath
is a file.
Returns: standard result
Cond: --
*/
HRESULT PRIVATE RAI_InitAsRecItem(
LPRA_ITEM pitem,
LPCTSTR pszBrfPath,
LPCTSTR pszPath, // May be NULL
PRECITEM pri,
BOOL bKeepFirstChoice)
{
HRESULT hres;
HDSA hdsa;
ASSERT(pitem);
ASSERT(pszBrfPath);
ASSERT(pri);
hres = ChooseSide_CreateAsFile(&hdsa, pri);
if (SUCCEEDED(hres))
{
TCHAR sz[MAX_PATH];
PCHOOSESIDE pchside;
PCHOOSESIDE pchsideOutside;
UINT ichRealPath;
DEBUG_CODE( Sync_DumpRecItem(TR_SUCCESS, pri, TEXT("RAI_InitAsFile")); )
pitem->mask = RAIF_ALL & ~RAIF_LPARAM;
if (!GSetString(&pitem->pszName, pri->pcszName))
goto Error;
PathMakePresentable(pitem->pszName);
// Default style
if (RIA_MERGE == pri->riaction)
pitem->uStyle = RAIS_CANMERGE;
else
pitem->uStyle = 0;
// Is there an outside file?
if (ChooseSide_GetBest(hdsa, pszBrfPath, NULL, &pchside))
{
// Yes
RECNODESTATE rns = pchside->prn->rnstate;
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
pitem->siOutside.uState = SiFromRns(rns);
PathForSideItem(sz, ARRAYSIZE(sz), pchside->hvid, pchside->pszFolder, rns, &ichRealPath);
if (!GSetString(&pitem->siOutside.pszDir, sz))
goto Error;
pitem->siOutside.fs = pchside->prn->fsCurrent;
pitem->siOutside.ichRealPath = ichRealPath;
}
else
{
// No; this is an orphan
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
if (!GSetString(&pitem->siOutside.pszDir, c_szNULL))
goto Error;
pitem->siOutside.uState = SI_NOEXIST;
pitem->siOutside.ichRealPath = 0;
}
pchsideOutside = pchside;
// Make sure we have some fully qualified folder on which
// to base our decision for an inside path
if (pszPath)
{
lstrcpyn(sz, pszPath, ARRAYSIZE(sz));
PathRemoveFileSpec(sz);
}
else
lstrcpyn(sz, pszBrfPath, ARRAYSIZE(sz)); // (best we can do...)
// Get the inside folder
if (ChooseSide_GetBest(hdsa, pszBrfPath, sz, &pchside))
{
RECNODESTATE rns;
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
pchside = DeriveFileActionAndSide(pitem, hdsa, pchside, pchsideOutside, bKeepFirstChoice);
// Determine status of inside file
rns = pchside->prn->rnstate;
pitem->siInside.uState = SiFromRns(rns);
PathForSideItem(sz, ARRAYSIZE(sz), pchside->hvid, pchside->pszFolder, rns, &ichRealPath);
GSetString(&pitem->siInside.pszDir, sz);
pitem->siInside.fs = pchside->prn->fsCurrent;
pitem->siInside.ichRealPath = ichRealPath;
// Is there a node for the outside?
if (pchsideOutside)
{
// Yes; special case. If a single side does not exist
// then say the existing side is new.
if (SI_NOEXIST == pitem->siInside.uState &&
SI_NOEXIST == pitem->siOutside.uState)
; // Do nothing special
else if (SI_NOEXIST == pitem->siInside.uState)
{
ASSERT(SI_NOEXIST != pitem->siOutside.uState);
pitem->siOutside.uState = SI_NEW;
}
else if (SI_NOEXIST == pitem->siOutside.uState)
{
ASSERT(SI_NOEXIST != pitem->siInside.uState);
pitem->siInside.uState = SI_NEW;
}
}
// Save away twin handle. Use the inside htwin because
// we want to always delete from inside the briefcase
// (it's all in your perspective...)
pitem->htwin = (HTWIN)pchside->prn->hObjectTwin;
}
else
{
// It is relatively bad to be here
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
ASSERT(0);
hres = E_FAIL;
}
DEBUG_CODE( DumpTwinPair(pitem); )
ChooseSide_Free(hdsa);
hdsa = NULL;
}
else
{
hdsa = NULL;
Error:
hres = E_OUTOFMEMORY;
}
if (FAILED(hres))
{
ChooseSide_Free(hdsa);
}
return hres;
}
/*----------------------------------------------------------
Purpose: Choose a recitem whose name matches the given name.
Returns: A pointer to the recitem in the given reclist
NULL if filespec is not found
Cond: --
*/
PRECITEM PRIVATE ChooseRecItem(
PRECLIST prl,
LPCTSTR pszName)
{
PRECITEM pri;
for (pri = prl->priFirst; pri; pri = pri->priNext)
{
if (IsSzEqual(pri->pcszName, pszName))
return pri;
}
return NULL;
}
/*----------------------------------------------------------
Purpose: Initialize a paired-twin structure assuming pszPath
is a file.
Returns: standard result
Cond: --
*/
HRESULT PRIVATE RAI_InitAsFile(
LPRA_ITEM pitem,
LPCTSTR pszBrfPath,
LPCTSTR pszPath,
PRECLIST prl)
{
HRESULT hres;
PRECITEM pri;
ASSERT(pitem);
ASSERT(pszBrfPath);
ASSERT(pszPath);
ASSERT(prl);
if (pszPath)
{
LPCTSTR pszFile;
pszFile = PathFindFileName(pszPath);
pri = ChooseRecItem(prl, pszFile);
ASSERT(pri);
}
else
{
pri = NULL;
}
if (pri)
{
hres = RAI_InitAsRecItem(pitem, pszBrfPath, pszPath, pri, TRUE);
}
else
{
hres = E_OUTOFMEMORY;
}
return hres;
}
/*----------------------------------------------------------
Purpose: Initialize a paired-twin structure assuming pszPath
is a file.
Returns: standard result
Cond: --
*/
HRESULT PRIVATE RAI_InitAsFolder(
LPRA_ITEM pitem,
LPCTSTR pszBrfPath,
LPCTSTR pszPath, // Should be inside the briefcase
PRECLIST prl,
PFOLDERTWINLIST pftl)
{
HRESULT hres;
HDSA hdsa;
ASSERT(pitem);
ASSERT(pszBrfPath);
ASSERT(pszPath);
ASSERT(prl);
ASSERT(pftl);
ASSERT(0 < pftl->ulcItems);
pitem->mask = RAIF_ALL & ~RAIF_LPARAM;
DEBUG_CODE( Sync_DumpRecList(TR_SUCCESS, prl, TEXT("RAI_InitAsFolder")); )
DEBUG_CODE( Sync_DumpFolderTwinList(pftl, NULL); )
// We only need to flag RAIS_FOLDER for the folder case.
// (Context menu isn't available for folders, so RAIS_CANMERGE is
// unnecessary.)
//
pitem->uStyle = RAIS_FOLDER;
hres = ChooseSide_CreateEmpty(&hdsa);
if (SUCCEEDED(hres))
{
PRECITEM pri;
RECNODESTATE rnsInside;
RECNODESTATE rnsOutside;
PCHOOSESIDE pchside;
// Set starting defaults
pitem->uAction = RAIA_NOTHING;
rnsInside = RNS_UP_TO_DATE;
rnsOutside = RNS_UP_TO_DATE;
// Iterate thru reclist, choosing recnode pairs and dynamically
// updating rnsInside, rnsOutside and pitem->uAction.
for (pri = prl->priFirst; pri; pri = pri->priNext)
{
ChooseSide_InitAsFile(hdsa, pri);
// Get the inside item
if (ChooseSide_GetBest(hdsa, pszBrfPath, pszPath, &pchside))
{
DeriveFolderStateAndAction(pchside, &rnsInside, &pitem->uAction);
}
else
ASSERT(0);
// Get the outside item
if (ChooseSide_GetBest(hdsa, pszBrfPath, NULL, &pchside))
{
DeriveFolderStateAndAction(pchside, &rnsOutside, &pitem->uAction);
}
else
ASSERT(0);
}
ChooseSide_Free(hdsa);
// Finish up
hres = ChooseSide_CreateAsFolder(&hdsa, pftl);
if (SUCCEEDED(hres))
{
TCHAR sz[MAX_PATH];
UINT ichRealPath;
// Name
if (!GSetString(&pitem->pszName, PathFindFileName(pszPath)))
goto Error;
PathMakePresentable(pitem->pszName);
// Get the inside folder
if (ChooseSide_GetBest(hdsa, pszBrfPath, pszPath, &pchside))
{
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
// Are there any intersecting files in this folder twin?
if (0 == prl->ulcItems)
rnsInside = DeriveFolderState(pchside); // No
pitem->siInside.uState = SiFromRns(rnsInside);
PathForSideItem(sz, ARRAYSIZE(sz), pchside->hvid, pchside->pszFolder, rnsInside, &ichRealPath);
if (!GSetString(&pitem->siInside.pszDir, sz))
goto Error;
// (Hack to avoid printing bogus time/date)
pitem->siInside.fs.fscond = FS_COND_UNAVAILABLE;
pitem->siInside.ichRealPath = ichRealPath;
}
else
{
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
ASSERT(0);
}
// Get the outside folder
if (ChooseSide_GetBest(hdsa, pszBrfPath, NULL, &pchside))
{
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
// Are there any intersecting files in this folder twin?
if (0 == prl->ulcItems)
rnsOutside = DeriveFolderState(pchside); // No
pitem->siOutside.uState = SiFromRns(rnsOutside);
PathForSideItem(sz, ARRAYSIZE(sz), pchside->hvid, pchside->pszFolder, rnsOutside, &ichRealPath);
if (!GSetString(&pitem->siOutside.pszDir, sz))
goto Error;
// (Hack to avoid printing bogus time/date)
pitem->siOutside.fs.fscond = FS_COND_UNAVAILABLE;
pitem->siOutside.ichRealPath = ichRealPath;
// Save away twin handle. Use the outside handle
// for folders.
pitem->htwin = pchside->htwin;
}
else
{
DEBUG_CODE( ChooseSide_DumpList(hdsa); )
ASSERT(0);
}
DEBUG_CODE( DumpTwinPair(pitem); )
ChooseSide_Free(hdsa);
}
}
if (FAILED(hres))
{
Error:
if (SUCCEEDED(hres))
hres = E_OUTOFMEMORY;
ChooseSide_Free(hdsa);
}
return hres;
}
/*----------------------------------------------------------
Purpose: Create a paired-twin structure given a path name.
Returns: standard result
Cond: --
*/
HRESULT PUBLIC RAI_Create(
LPRA_ITEM * ppitem,
LPCTSTR pszBrfPath,
LPCTSTR pszPath, // Should be inside the briefcase
PRECLIST prl,
PFOLDERTWINLIST pftl) // NULL if pszPath is a file
{
HRESULT hres;
LPRA_ITEM pitem;
ASSERT(ppitem);
ASSERT(pszPath);
ASSERT(pszBrfPath);
ASSERT(prl);
DBG_ENTER_SZ(TEXT("RAI_Create"), pszPath);
if (PathExists(pszPath))
{
pitem = GAlloc(sizeof(*pitem));
if (pitem)
{
if (PathIsDirectory(pszPath))
hres = RAI_InitAsFolder(pitem, pszBrfPath, pszPath, prl, pftl);
else
hres = RAI_InitAsFile(pitem, pszBrfPath, pszPath, prl);
if (FAILED(hres))
{
// Cleanup
RAI_Free(pitem);
pitem = NULL;
}
}
else
hres = E_OUTOFMEMORY;
}
else
{
pitem = NULL;
hres = E_FAIL;
}
*ppitem = pitem;
DBG_EXIT_HRES(TEXT("RAI_Create"), hres);
return hres;
}
/*----------------------------------------------------------
Purpose: Create a paired-twin structure given a recitem.
Returns: standard result
Cond: --
*/
HRESULT PUBLIC RAI_CreateFromRecItem(LPRA_ITEM * ppitem, LPCTSTR pszBrfPath, PRECITEM pri)
{
HRESULT hr = E_INVALIDARG;
LPRA_ITEM pitem;
DBG_ENTER(TEXT("RAI_CreateFromRecItem"));
if (ppitem && pszBrfPath && pri)
{
pitem = GAlloc(sizeof(*pitem));
if (pitem)
{
hr = RAI_InitAsRecItem(pitem, pszBrfPath, NULL, pri, FALSE);
if (FAILED(hr))
{
// Cleanup
RAI_Free(pitem);
pitem = NULL;
}
}
else
{
hr = E_OUTOFMEMORY;
}
*ppitem = pitem;
}
DBG_EXIT_HRES(TEXT("RAI_CreateFromRecItem"), hr);
return hr;
}
/*----------------------------------------------------------
Purpose: Free a paired item structure
Returns: standard result
Cond: --
*/
HRESULT PUBLIC RAI_Free(
LPRA_ITEM pitem)
{
HRESULT hres;
if (pitem)
{
GFree(pitem->pszName);
GFree(pitem->siInside.pszDir);
GFree(pitem->siOutside.pszDir);
GFree(pitem);
hres = NOERROR;
}
else
hres = E_FAIL;
return hres;
}