//--------------------------------------------------------------------------- // // 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 #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; }