#include "ctlspriv.h" #include "toolbar.h" #include "help.h" // Help IDs #define SEND_WM_COMMAND(hwnd, id, hwndCtl, codeNotify) \ (void)SendMessage((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl)) #define SPACESTRLEN 20 #define FLAG_NODEL 0x8000 #define FLAG_HIDDEN 0x4000 #define FLAG_SEP 0x2000 #define FLAG_ALLFLAGS (FLAG_NODEL|FLAG_HIDDEN|FLAG_SEP) typedef struct { /* instance data for toolbar edit dialog */ HWND hDlg; /* dialog hwnd */ PTBSTATE ptb; // current toolbar state int iPos; /* position to insert into */ } ADJUSTDLGDATA, *LPADJUSTDLGDATA; int g_dyButtonHack = 0; // to pass on before WM_INITDIALOG LPTSTR TB_StrForButton(PTBSTATE ptb, LPTBBUTTONDATA pTBButton); int GetPrevButton(PTBSTATE ptb, int iPos) { /* This means to delete the preceding space */ for (--iPos; ; --iPos) { if (iPos < 0) break; if (!(ptb->Buttons[iPos].fsState & TBSTATE_HIDDEN)) break;; } return(iPos); } BOOL GetAdjustInfo(PTBSTATE ptb, int iItem, LPTBBUTTONDATA ptbButton, LPTSTR lpString, int cbString) { TBNOTIFY tbn; tbn.pszText = lpString; tbn.cchText = cbString; tbn.iItem = iItem; if (lpString) *lpString = 0; if ((BOOL)CCSendNotify(&ptb->ci, TBN_GETBUTTONINFO, &tbn.hdr)) { TBInputStruct(ptb, ptbButton, &tbn.tbButton); return TRUE; } return FALSE; } LRESULT SendItemNotify(PTBSTATE ptb, int iItem, int code) { TBNOTIFY tbn = {0}; tbn.iItem = iItem; switch (code) { case TBN_QUERYDELETE: case TBN_QUERYINSERT: // The following is to provide the parent app with information // about the button that information is being requested for... // Otherwise it's really awful trying to have control over // certain aspects of toolbar customization... [t-mkim] // IE4.0's toolbar wants this information. // Should ONLY be done for TBN_QUERY* notifications BECAUSE // this can be either a zero-based index _or_ Command ID depending // on the particular notification code. if (iItem < ptb->iNumButtons) CopyMemory (&tbn.tbButton, &ptb->Buttons[iItem], sizeof (TBBUTTON)); break; case TBN_DROPDOWN: TB_GetItemRect(ptb, PositionFromID(ptb, iItem), &tbn.rcButton); break; } // default return from SendNotify is false // this actually shouldnt return a bool, TBN_DROPDOWN needs to return 0, 1, or 2. return CCSendNotify(&ptb->ci, code, &tbn.hdr); } #define SendCmdNotify(ptb, code) CCSendNotify(&ptb->ci, code, NULL) // this is used to deal with the case where the ptb structure is re-alloced // after a TBInsertButtons() PTBSTATE FixPTB(HWND hwnd) { PTBSTATE ptb = (PTBSTATE)GetWindowInt(hwnd, 0); if (ptb->hdlgCust) { LPADJUSTDLGDATA lpad = (LPADJUSTDLGDATA)GetWindowPtr(ptb->hdlgCust, DWLP_USER); #ifdef DEBUG if (lpad->ptb != ptb) DebugMsg(DM_TRACE, TEXT("Fixing busted ptb pointer")); #endif lpad->ptb = ptb; } return ptb; } void MoveButton(PTBSTATE ptb, int nSource) { int nDest; RECT rc; HCURSOR hCursor; MSG32 msg32; /* You can't move separators like this */ if (nSource < 0) return; // Make sure it is all right to "delete" the selected button if (!SendItemNotify(ptb, nSource, TBN_QUERYDELETE)) return; hCursor = SetCursor(LoadCursor(HINST_THISDLL, MAKEINTRESOURCE(IDC_MOVEBUTTON))); SetCapture(ptb->ci.hwnd); // Get the dimension of the window. GetClientRect(ptb->ci.hwnd, &rc); for ( ; ; ) { while (!PeekMessage32(&msg32, NULL, 0, 0, PM_REMOVE, TRUE)) ; if (GetCapture() != ptb->ci.hwnd) goto AbortMove; // See if the application wants to process the message... if (CallMsgFilter32(&msg32, MSGF_COMMCTRL_TOOLBARCUST, TRUE) != 0) continue; switch (msg32.message) { case WM_KEYDOWN: case WM_KEYUP: case WM_CHAR: #ifdef KEYBOARDCUES //notify of navigation key usage CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS); #endif break; case WM_LBUTTONUP: RelayToToolTips(ptb->hwndToolTips, ptb->ci.hwnd, msg32.message, msg32.wParam, msg32.lParam); if ((GET_Y_LPARAM(msg32.lParam) > (short)(rc.bottom+ptb->iButWidth)) || (GET_X_LPARAM(msg32.lParam) > (short)(rc.right+ptb->iButWidth)) || (GET_Y_LPARAM(msg32.lParam) < -ptb->iButWidth) || (GET_X_LPARAM(msg32.lParam) < -ptb->iButWidth)) { /* If the button was dragged off the toolbar, delete it. */ DeleteSrcButton: DeleteButton(ptb, nSource); SendCmdNotify(ptb, TBN_TOOLBARCHANGE); TBInvalidateItemRects(ptb); } else { TBBUTTONDATA tbbAdd; /* Add half a button to X so that it looks like it is centered * over the target button, iff we have a horizontal layout. * Add half a button to Y otherwise. */ if (rc.right!=ptb->iButWidth) nDest = TBHitTest(ptb, GET_X_LPARAM(msg32.lParam) + ptb->iButWidth / 2, GET_Y_LPARAM(msg32.lParam)); else nDest = TBHitTest(ptb, GET_X_LPARAM(msg32.lParam), GET_Y_LPARAM(msg32.lParam) + ptb->iButHeight / 2); if (nDest < 0) nDest = -1 - nDest; if (nDest>0 && (ptb->Buttons[nDest-1].fsState & TBSTATE_WRAP) && GET_X_LPARAM(msg32.lParam)>ptb->iButWidth && SendItemNotify(ptb, --nDest, TBN_QUERYINSERT)) { tbbAdd = ptb->Buttons[nSource]; DeleteButton(ptb, nSource); if (nDest>nSource) --nDest; /* Insert before spaces, but after buttons. */ if (!(ptb->Buttons[nDest].fsStyle & TBSTYLE_SEP)) nDest++; goto InsertSrcButton; } else if (nDest == nSource) { /* This means to delete the preceding space, or to move a button to the previous row. */ nSource = GetPrevButton(ptb, nSource); if (nSource < 0) goto AbortMove; // If the preceding item is a space with no ID, and // the app says it's OK, then delete it. if ((ptb->Buttons[nSource].fsStyle & TBSTYLE_SEP) && !ptb->Buttons[nSource].idCommand && SendItemNotify(ptb, nSource, TBN_QUERYDELETE)) goto DeleteSrcButton; } else if (nDest == nSource+1) { // This means to add a preceding space --nDest; if (SendItemNotify(ptb, nDest, TBN_QUERYINSERT)) { tbbAdd.DUMMYUNION_MEMBER(iBitmap) = 0; tbbAdd.idCommand = 0; tbbAdd.iString = -1; tbbAdd.fsState = 0; tbbAdd.fsStyle = TBSTYLE_SEP; goto InsertSrcButton; } } else if (SendItemNotify(ptb, nDest, TBN_QUERYINSERT)) { HWND hwndT; TBBUTTON tbbAddExt; /* This is a normal move operation */ tbbAdd = ptb->Buttons[nSource]; ptb->Buttons[nSource].iString = -1; DeleteButton(ptb, nSource); if (nDest > nSource) --nDest; InsertSrcButton: hwndT = ptb->ci.hwnd; TBOutputStruct(ptb, &tbbAdd, &tbbAddExt); TBInsertButtons(ptb, nDest, 1, &tbbAddExt, TRUE); ptb = FixPTB(hwndT); SendCmdNotify(ptb, TBN_TOOLBARCHANGE); TBInvalidateItemRects(ptb); } else { AbortMove: ; } } goto AllDone; case WM_RBUTTONDOWN: goto AbortMove; default: TranslateMessage32(&msg32, TRUE); DispatchMessage32(&msg32, TRUE); break; } } AllDone: SetCursor(hCursor); CCReleaseCapture(&ptb->ci); } #define GNI_HIGH 0x0001 #define GNI_LOW 0x0002 int GetNearestInsert(PTBSTATE ptb, int iPos, int iNumButtons, UINT uFlags) { int i; BOOL bKeepTrying; // Find the nearest index where we can actually insert items for (i = iPos; ; ++i, --iPos) { bKeepTrying = FALSE; // Notice we favor going high if both flags are set if ((uFlags & GNI_HIGH) && i <= iNumButtons) { bKeepTrying = TRUE; if (SendItemNotify(ptb, i, TBN_QUERYINSERT)) return i; } if ((uFlags & GNI_LOW) && iPos >= 0) { bKeepTrying = TRUE; if (SendItemNotify(ptb, iPos, TBN_QUERYINSERT)) return iPos; } if (!bKeepTrying) return -1; // There was no place to add buttons } } BOOL InitAdjustDlg(HWND hDlg, LPADJUSTDLGDATA lpad) { HDC hDC; HFONT hFont; HWND hwndCurrent, hwndNew; LPTBBUTTONDATA ptbButton; int i, iPos, nItem, nWid, nMaxWid; TBBUTTONDATA tbAdjust; TCHAR szDesc[128]; NMTBCUSTOMIZEDLG nm; TCHAR szSeparator[MAX_PATH]; szSeparator[0] = 0; LocalizedLoadString(IDS_SPACE, szSeparator, ARRAYSIZE(szSeparator)); lpad->hDlg = hDlg; lpad->ptb->hdlgCust = hDlg; /* Determine the item nearest the desired item that will allow * insertion. */ iPos = GetNearestInsert(lpad->ptb, lpad->iPos, lpad->ptb->iNumButtons, GNI_HIGH | GNI_LOW); if (iPos < 0) /* No item allowed insertion, so leave the dialog */ { return(FALSE); } /* Reset the lists of used and available items. */ hwndCurrent = GetDlgItem(hDlg, IDC_CURRENT); SendMessage(hwndCurrent, LB_RESETCONTENT, 0, 0L); hwndNew = GetDlgItem(hDlg, IDC_BUTTONLIST); SendMessage(hwndNew, LB_RESETCONTENT, 0, 0L); nm.hDlg = hDlg; if (CCSendNotify(&lpad->ptb->ci, TBN_INITCUSTOMIZE, &nm.hdr) == TBNRF_HIDEHELP) { ShowWindow(GetDlgItem(hDlg, IDC_APPHELP), SW_HIDE); } for (i=0, ptbButton = lpad->ptb->Buttons; i < lpad->ptb->iNumButtons; ++i, ++ptbButton) { UINT uFlags; int iBitmap; LPTSTR pszStr = NULL; uFlags = 0; // Non-deletable and hidden items show up grayed. if (!SendItemNotify(lpad->ptb, i, TBN_QUERYDELETE)) { uFlags |= FLAG_NODEL; } if (ptbButton->fsState & TBSTATE_HIDDEN) { uFlags |= FLAG_HIDDEN; } /* Separators have no bitmaps (even ones with IDs). Only set * the separator flag if there is no ID (it is a "real" * separator rather than an owner item). */ if (ptbButton->fsStyle&TBSTYLE_SEP) { if (!(ptbButton->idCommand)) { uFlags |= FLAG_SEP; } iBitmap = -1; pszStr = szSeparator; } else { iBitmap = ptbButton->DUMMYUNION_MEMBER(iBitmap); // this specifies an imagelist. // pack this into the loword of the ibitmap. // this causes a restriction of max 16 imagelists, and 4096 images in any imagelist iBitmap = LOWORD(iBitmap) | (HIWORD(iBitmap) << 12); /* Add the item and the data * Note: A negative number in the LOWORD indicates no bitmap; * otherwise it is the bitmap index. */ pszStr = TB_StrForButton(lpad->ptb, ptbButton); } if ((int)SendMessage(hwndCurrent, LB_ADDSTRING, 0, (LPARAM)(LPTSTR)(pszStr ? pszStr : (LPTSTR)c_szNULL)) != i) { return(FALSE); } SendMessage(hwndCurrent, LB_SETITEMDATA, i, MAKELPARAM(iBitmap, uFlags)); } /* Add a dummy "nodel" space at the end so things can be inserted at the end. */ if ((int)SendMessage(hwndCurrent, LB_ADDSTRING, 0,(LPARAM)(LPTSTR)szSeparator) == i) { SendMessage(hwndCurrent, LB_SETITEMDATA, i, MAKELPARAM(-1, FLAG_NODEL|FLAG_SEP)); } /* Now add a space at the beginning of the "new" list. */ if (SendMessage(hwndNew, LB_ADDSTRING, 0, (LPARAM)(LPTSTR)szSeparator) == LB_ERR) return(FALSE); SendMessage(hwndNew, LB_SETITEMDATA, 0, MAKELPARAM(-1, FLAG_SEP)); /* We need this to determine the widest (in pixels) item string. */ hDC = GetDC(hwndCurrent); hFont = (HFONT)(INT_PTR)SendMessage(hwndCurrent, WM_GETFONT, 0, 0L); if (hFont) { hFont = SelectObject(hDC, hFont); } nMaxWid = 0; for (i=0; ; ++i) { // Get the info about the i'th item from the app. if (!GetAdjustInfo(lpad->ptb, i, &tbAdjust, szDesc, ARRAYSIZE(szDesc))) break; if (!szDesc[0]) { LPTSTR psz = TB_StrForButton(lpad->ptb, &tbAdjust); if (psz) { lstrcpyn(szDesc, psz, ARRAYSIZE(szDesc)); } } /* Don't show separators that don't have commands */ if (!(tbAdjust.fsStyle & TBSTYLE_SEP) || tbAdjust.idCommand) { /* Get the maximum width of a string. */ MGetTextExtent(hDC, szDesc, lstrlen(szDesc), &nWid, NULL); if (nMaxWid < nWid) { nMaxWid = nWid; } nItem = PositionFromID(lpad->ptb, tbAdjust.idCommand); if (nItem < 0) /* If the item is not on the toolbar already */ { #ifdef UNIX if (!lstrcmp(szDesc, TEXT("Folders")) || !lstrcmp(szDesc, TEXT("Edit"))) continue; #endif /* Don't show hidden buttons */ if (!(tbAdjust.fsState & TBSTATE_HIDDEN)) { nItem = (int)SendMessage(hwndNew, LB_ADDSTRING, 0, (LPARAM)(LPTSTR)szDesc); if (nItem != LB_ERR) { if (tbAdjust.fsStyle & TBSTYLE_SEP) SendMessage(hwndNew, LB_SETITEMDATA, nItem, MAKELPARAM(-1, i)); else { int iBitmap = tbAdjust.DUMMYUNION_MEMBER(iBitmap); iBitmap = LOWORD(iBitmap) | (HIWORD(iBitmap) << 12); SendMessage(hwndNew, LB_SETITEMDATA, nItem, MAKELPARAM(iBitmap, i)); } } } } else /* The item is on the toolbar already */ { /* Preserve the flags and bitmap. */ DWORD dwTemp = (DWORD)SendMessage(hwndCurrent, LB_GETITEMDATA, nItem, 0L); if (szDesc[0]) { SendMessage(hwndCurrent, LB_DELETESTRING, nItem, 0L); if ((int)SendMessage(hwndCurrent, LB_INSERTSTRING, nItem, (LPARAM)(LPTSTR)szDesc) != nItem) { ReleaseDC(hwndCurrent, hDC); return(FALSE); } } SendMessage(hwndCurrent, LB_SETITEMDATA, nItem, MAKELPARAM(LOWORD(dwTemp), HIWORD(dwTemp)|i)); } } } if (hFont) { SelectObject(hDC, hFont); } ReleaseDC(hwndCurrent, hDC); /* Add on some extra and set the extents for both lists. */ nMaxWid += lpad->ptb->iButWidth + 2 + 1; SendMessage(hwndNew, LB_SETHORIZONTALEXTENT, nMaxWid, 0L); SendMessage(hwndCurrent, LB_SETHORIZONTALEXTENT, nMaxWid, 0L); /* Set the sels and return. */ SendMessage(hwndNew, LB_SETCURSEL, 0, 0L); SendMessage(hwndCurrent, LB_SETCURSEL, iPos, 0L); SEND_WM_COMMAND(hDlg, IDC_CURRENT, hwndCurrent, LBN_SELCHANGE); return(TRUE); } #define IsSeparator(x) (HIWORD(x) & FLAG_SEP) void PaintAdjustLine(PTBSTATE ptb, DRAWITEMSTRUCT *lpdis) { HDC hdc = lpdis->hDC; HWND hwndList = lpdis->hwndItem; PTSTR pszText; RECT rc = lpdis->rcItem; int nBitmap, nLen, nItem = lpdis->itemID; COLORREF oldBkColor, oldTextColor; BOOL bSelected, bHasFocus; int wHeight; int x; if (lpdis->CtlID != IDC_BUTTONLIST && lpdis->CtlID != IDC_CURRENT) return; nBitmap = LOWORD(lpdis->itemData); // unpack the nBitmap. we stored the imagelist spec in the hi char of loword if (nBitmap != 0xFFFF) nBitmap = (nBitmap & 0x0FFF) | ((nBitmap & 0xF000) << 4); nLen = (int)SendMessage(hwndList, LB_GETTEXTLEN, nItem, 0L); if (nLen < 0) return; pszText = (PTSTR)LocalAlloc(LPTR, (nLen+1)*sizeof(TCHAR)); if (!pszText) return; // This needs to work for separators also or ActiveAccessibility // won't work. SendMessage(hwndList, LB_GETTEXT, nItem, (LPARAM)(LPTSTR)pszText); if (lpdis->itemAction != ODA_FOCUS) { COLORREF clr; TCHAR szSample[2]; /* We don't care about focus if the item is not selected. */ bSelected = lpdis->itemState & ODS_SELECTED; bHasFocus = bSelected && (GetFocus() == hwndList); if (HIWORD(lpdis->itemData) & (FLAG_NODEL | FLAG_HIDDEN)) clr = g_clrGrayText; else if (bHasFocus) clr = g_clrHighlightText; else clr = g_clrWindowText; oldTextColor = SetTextColor(hdc, clr); oldBkColor = SetBkColor(hdc, bHasFocus ? g_clrHighlight : g_clrWindow); szSample[0] = TEXT('W'); szSample[1] = TEXT('\0'); MGetTextExtent(hdc, szSample, 1, NULL, &wHeight); x = rc.left + 2; x += (ptb->ci.style & TBSTYLE_FLAT) ? (ptb->iDxBitmap + g_cxEdge) : ptb->iButWidth; ExtTextOut(hdc, x, (rc.top + rc.bottom-wHeight) / 2, ETO_CLIPPED | ETO_OPAQUE, &rc, pszText, nLen, NULL); /* We really care about the bitmap value here; this is not just an * indicator for the separator. */ if (nBitmap >= 0) { TBBUTTONDATA tbbAdd = {0}; TBDRAWITEM tbdraw = {0}; tbbAdd.DUMMYUNION_MEMBER(iBitmap) = nBitmap; tbbAdd.iString = -1; tbbAdd.fsStyle = TBSTYLE_BUTTON; tbbAdd.fsState = (BYTE)((HIWORD(lpdis->itemData) & FLAG_HIDDEN) ? 0 : TBSTATE_ENABLED); InitTBDrawItem(&tbdraw, ptb, &tbbAdd, tbbAdd.fsState, 0, 0, 0); if (ptb->ci.style & TBSTYLE_FLAT) DrawFace(hdc, rc.left + 1, rc.top + 1, 0, 0, 0, 0, &tbdraw); else DrawButton(hdc, rc.left + 1, rc.top + 1, ptb, &tbbAdd, TRUE); ReleaseMonoDC(ptb); } SetBkColor(hdc, oldBkColor); SetTextColor(hdc, oldTextColor); /* Frame the item if it is selected but does not have the focus. */ if (bSelected && !bHasFocus) { nLen = rc.left + (int)SendMessage(hwndList, LB_GETHORIZONTALEXTENT, 0, 0L); if (rc.right < nLen) rc.right = nLen; FrameRect(hdc, &rc, g_hbrHighlight); } } if ((lpdis->itemAction == ODA_FOCUS || (lpdis->itemState & ODS_FOCUS)) #ifdef KEYBOARDCUES && !(CCGetUIState(&(ptb->ci)) & UISF_HIDEFOCUS) #endif ) DrawFocusRect(hdc, &rc); LocalFree((HLOCAL)pszText); } void LBMoveButton(LPADJUSTDLGDATA lpad, UINT wIDSrc, int iPosSrc, UINT wIDDst, int iPosDst, int iSelOffset) { HWND hwndSrc, hwndDst; DWORD dwDataSrc; PTSTR pStr; TBBUTTONDATA tbAdjust = {0}; TBBUTTON tbbAddExt; int iTopDst; TCHAR szDesc[128]; hwndSrc = GetDlgItem(lpad->hDlg, wIDSrc); hwndDst = GetDlgItem(lpad->hDlg, wIDDst); // Make sure we can delete the source and insert at the dest // dwDataSrc = (DWORD)SendMessage(hwndSrc, LB_GETITEMDATA, iPosSrc, 0L); if (iPosSrc < 0 || (HIWORD(dwDataSrc) & FLAG_NODEL)) return; if (wIDDst == IDC_CURRENT && !SendItemNotify(lpad->ptb, iPosDst, TBN_QUERYINSERT)) return; // Get the string for the source // pStr = (PTSTR)LocalAlloc(LPTR, ((int)(SendMessage(hwndSrc, LB_GETTEXTLEN, iPosSrc, 0L))+1)*sizeof(TCHAR)); if (!pStr) return; SendMessage(hwndSrc, LB_GETTEXT, iPosSrc, (LPARAM)(LPTSTR)pStr); SendMessage(hwndSrc, WM_SETREDRAW, 0, 0L); SendMessage(hwndDst, WM_SETREDRAW, 0, 0L); iTopDst = (int)SendMessage(hwndDst, LB_GETTOPINDEX, 0, 0L); // If we are inserting into the available button list, we need to determine // the insertion point // if (wIDDst == IDC_BUTTONLIST) { // Insert this back in the available list if this is not a space or a // hidden button. // if (HIWORD(dwDataSrc)&(FLAG_SEP|FLAG_HIDDEN)) { iPosDst = 0; goto DelTheSrc; } else { UINT uCmdSrc = HIWORD(dwDataSrc) & ~(FLAG_ALLFLAGS); // This just does a linear search for where to put the // item. Slow, but this only happens when the user clicks // the "Remove" button. // iPosDst = 1; for ( ; ; ++iPosDst) { // Notice that this will break out when iPosDst is // past the number of items, since -1 will be returned // if ((UINT)HIWORD(SendMessage(hwndDst, LB_GETITEMDATA, iPosDst, 0L)) >= uCmdSrc) break; } } } else if (iPosDst < 0) goto CleanUp; // Attempt to insert the new string // if ((int)SendMessage(hwndDst, LB_INSERTSTRING, iPosDst, (LPARAM)(LPTSTR)pStr) == iPosDst) { // Attempt to sync up the actual toolbar. // if (wIDDst == IDC_CURRENT) { HWND hwndT; if (IsSeparator(dwDataSrc)) { // Make up a dummy lpInfo if this is a space // tbAdjust.DUMMYUNION_MEMBER(iBitmap) = 0; tbAdjust.idCommand = 0; tbAdjust.fsState = 0; tbAdjust.fsStyle = TBSTYLE_SEP; } else { // Call back to client to get the source button info // int iCmdSrc = HIWORD(dwDataSrc) & ~FLAG_ALLFLAGS; if (!GetAdjustInfo(lpad->ptb, iCmdSrc, &tbAdjust, szDesc, ARRAYSIZE(szDesc))) goto DelTheDst; } hwndT = lpad->ptb->ci.hwnd; TBOutputStruct(lpad->ptb, &tbAdjust, &tbbAddExt); if (!TBInsertButtons(lpad->ptb, iPosDst, 1, &tbbAddExt, TRUE)) { DelTheDst: SendMessage(hwndDst, LB_DELETESTRING, iPosDst, 0L); goto CleanUp; } else { lpad->ptb = FixPTB(hwndT); } if (wIDSrc == IDC_CURRENT && iPosSrc >= iPosDst) ++iPosSrc; } SendMessage(hwndDst, LB_SETITEMDATA, iPosDst, dwDataSrc); DelTheSrc: // Don't delete the "Separator" in the new list // if ((wIDSrc != IDC_BUTTONLIST) || (iPosSrc != 0)) { SendMessage(hwndSrc, LB_DELETESTRING, iPosSrc, 0L); if (wIDSrc == wIDDst) { if (iPosSrc < iPosDst) --iPosDst; if (iPosSrc < iTopDst) --iTopDst; } } // Delete the corresponding button // if (wIDSrc == IDC_CURRENT) DeleteButton(lpad->ptb, iPosSrc); // Only set the src index if the two windows are different // if (wIDSrc != wIDDst) { if (iPosSrc >= SendMessage(hwndSrc, LB_GETCOUNT, 0, 0L)) { // HACKHACK: workaround for funkdified listbox scrolling behavior. // Select the first item (to force scroll back to top of list), // then select the item we really want selected. SendMessage(hwndSrc, LB_SETCURSEL, 0, 0L); } if (SendMessage(hwndSrc, LB_SETCURSEL, iPosSrc, 0L) == LB_ERR) SendMessage(hwndSrc, LB_SETCURSEL, iPosSrc-1, 0L); SEND_WM_COMMAND(lpad->hDlg, wIDSrc, hwndSrc, LBN_SELCHANGE); } // Send the final SELCHANGE message after everything else is done // SendMessage(hwndDst, LB_SETCURSEL, iPosDst+iSelOffset, 0L); SEND_WM_COMMAND(lpad->hDlg, wIDDst, hwndDst, LBN_SELCHANGE); } CleanUp: LocalFree((HLOCAL)pStr); if (wIDSrc == wIDDst) { SendMessage(hwndDst, LB_SETTOPINDEX, iTopDst, 0L); //make sure that the selected item is still visible SendMessage(hwndDst, LB_SETCURSEL, (int)SendMessage(hwndDst, LB_GETCURSEL, 0, 0L), 0); } SendMessage(hwndSrc, WM_SETREDRAW, 1, 0L); SendMessage(hwndDst, WM_SETREDRAW, 1, 0L); InvalidateRect(hwndDst, NULL, TRUE); SendCmdNotify(lpad->ptb, TBN_TOOLBARCHANGE); } void SafeEnableWindow(HWND hDlg, UINT wID, HWND hwndDef, BOOL bEnable) { HWND hwndEnable; hwndEnable = GetDlgItem(hDlg, wID); if (!bEnable && GetFocus()==hwndEnable) SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)hwndDef, 1L); EnableWindow(hwndEnable, bEnable); } int InsertIndex(LPADJUSTDLGDATA lpad, POINT pt, BOOL bDragging) { HWND hwndCurrent = GetDlgItem(lpad->hDlg, IDC_CURRENT); int nItem = LBItemFromPt(hwndCurrent, pt, bDragging); if (nItem >= 0) { if (!SendItemNotify(lpad->ptb, nItem, TBN_QUERYINSERT)) nItem = -1; } DrawInsert(lpad->hDlg, hwndCurrent, bDragging ? nItem : -1); return(nItem); } BOOL IsInButtonList(HWND hDlg, POINT pt) { ScreenToClient(hDlg, &pt); return(ChildWindowFromPoint(hDlg, pt) == GetDlgItem(hDlg, IDC_BUTTONLIST)); } BOOL HandleDragMsg(LPADJUSTDLGDATA lpad, HWND hDlg, WPARAM wID, LPDRAGLISTINFO lpns) { switch (wID) { case IDC_CURRENT: switch (lpns->uNotification) { case DL_BEGINDRAG: { int nItem = (int)SendMessage(lpns->hWnd, LB_GETCURSEL, 0, 0L); if (HIWORD(SendMessage(lpns->hWnd, LB_GETITEMDATA, nItem, 0L)) & FLAG_NODEL) return SetDlgMsgResult(hDlg, WM_COMMAND, FALSE); return SetDlgMsgResult(hDlg, WM_COMMAND, TRUE); } case DL_DRAGGING: { int nDropIndex; DraggingSomething: nDropIndex = InsertIndex(lpad, lpns->ptCursor, TRUE); if (nDropIndex>=0 || IsInButtonList(hDlg, lpns->ptCursor)) { SetCursor(LoadCursor(HINST_THISDLL, MAKEINTRESOURCE(IDC_MOVEBUTTON))); return SetDlgMsgResult(hDlg, WM_COMMAND, 0); } return SetDlgMsgResult(hDlg, WM_COMMAND, DL_STOPCURSOR); } case DL_DROPPED: { int nDropIndex, nSrcIndex; nDropIndex = InsertIndex(lpad, lpns->ptCursor, FALSE); nSrcIndex = (int)SendMessage(lpns->hWnd, LB_GETCURSEL, 0, 0L); if (nDropIndex >= 0) { if ((UINT)(nDropIndex-nSrcIndex) > 1) LBMoveButton(lpad, IDC_CURRENT, nSrcIndex, IDC_CURRENT, nDropIndex, 0); } else if (IsInButtonList(hDlg, lpns->ptCursor)) { LBMoveButton(lpad, IDC_CURRENT, nSrcIndex, IDC_BUTTONLIST, 0, 0); } break; } case DL_CANCELDRAG: CancelDrag: /* This erases the insert icon if it exists. */ InsertIndex(lpad, lpns->ptCursor, FALSE); break; default: break; } break; case IDC_BUTTONLIST: switch (lpns->uNotification) { case DL_BEGINDRAG: return SetDlgMsgResult(hDlg, WM_COMMAND, TRUE); case DL_DRAGGING: goto DraggingSomething; case DL_DROPPED: { int nDropIndex; nDropIndex = InsertIndex(lpad, lpns->ptCursor, FALSE); if (nDropIndex >= 0) LBMoveButton(lpad, IDC_BUTTONLIST, (int)SendMessage(lpns->hWnd,LB_GETCURSEL,0,0L), IDC_CURRENT, nDropIndex, 0); break; } case DL_CANCELDRAG: goto CancelDrag; default: break; } break; default: break; } return(0); } #ifndef WINNT #pragma data_seg(DATASEG_READONLY) #endif const static DWORD aAdjustHelpIDs[] = { // Context Help IDs IDC_RESET, IDH_COMCTL_RESET, IDC_APPHELP, IDH_HELP, IDC_MOVEUP, IDH_COMCTL_MOVEUP, IDC_MOVEDOWN, IDH_COMCTL_MOVEDOWN, IDC_BUTTONLIST, IDH_COMCTL_AVAIL_BUTTONS, IDOK, IDH_COMCTL_ADD, IDC_REMOVE, IDH_COMCTL_REMOVE, IDC_CURRENT, IDH_COMCTL_BUTTON_LIST, IDCANCEL, IDH_COMCTL_CLOSE, 0, 0 }; #ifndef WINNT #pragma data_seg() #endif BOOL_PTR CALLBACK AdjustDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { LPADJUSTDLGDATA lpad = (LPADJUSTDLGDATA)GetWindowPtr(hDlg, DWLP_USER); switch (uMsg) { case WM_INITDIALOG: SetWindowLongPtr(hDlg, DWLP_USER, lParam); /* LPADJUSTDLGDATA pointer */ if (!InitAdjustDlg(hDlg, (LPADJUSTDLGDATA)lParam)) EndDialog(hDlg, FALSE); ShowWindow(hDlg, SW_SHOW); UpdateWindow(hDlg); SetFocus(GetDlgItem(hDlg, IDC_CURRENT)); MakeDragList(GetDlgItem(hDlg, IDC_CURRENT)); MakeDragList(GetDlgItem(hDlg, IDC_BUTTONLIST)); return FALSE; case WM_MEASUREITEM: #define lpmis ((MEASUREITEMSTRUCT *)lParam) if (lpmis->CtlID == IDC_BUTTONLIST || lpmis->CtlID == IDC_CURRENT) { int nHeight; HWND hwndList = GetDlgItem(hDlg, lpmis->CtlID); HDC hDC = GetDC(hwndList); TCHAR szSample[2]; szSample[0] = TEXT('W'); szSample[1] = TEXT('\0'); MGetTextExtent(hDC, szSample, 1, NULL, &nHeight); // note, we use this hack because we get WM_MEASUREITEMS // before our WM_INITDIALOG where we get the lpad setup if (nHeight < g_dyButtonHack + 2) nHeight = g_dyButtonHack + 2; lpmis->itemHeight = nHeight; ReleaseDC(hwndList, hDC); } break; case WM_DRAWITEM: PaintAdjustLine(lpad->ptb, (DRAWITEMSTRUCT *)lParam); break; case WM_HELP: WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, NULL, HELP_WM_HELP, (ULONG_PTR)(LPTSTR) aAdjustHelpIDs); break; case WM_CONTEXTMENU: WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU, (ULONG_PTR)(LPVOID) aAdjustHelpIDs); break; case WM_COMMAND: switch (GET_WM_COMMAND_ID(wParam, lParam)) { case IDC_APPHELP: SendCmdNotify(lpad->ptb, TBN_CUSTHELP); break; case IDOK: { int iPos, nItem; nItem = (int)SendDlgItemMessage(hDlg, IDC_BUTTONLIST, LB_GETCURSEL, 0, 0L); iPos = (int)SendDlgItemMessage(hDlg, IDC_CURRENT, LB_GETCURSEL, 0, 0L); if (iPos == -1) iPos = 0; LBMoveButton(lpad, IDC_BUTTONLIST, nItem, IDC_CURRENT, iPos, 1); break; } case IDC_BUTTONLIST: switch (GET_WM_COMMAND_CMD(wParam, lParam)) { case LBN_DBLCLK: SendMessage(hDlg, WM_COMMAND, IDOK, 0L); break; case LBN_SETFOCUS: case LBN_KILLFOCUS: { RECT rc; if (SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETITEMRECT, (int)SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, 0, 0L), (LPARAM)(LPRECT)&rc) != LB_ERR) InvalidateRect(GET_WM_COMMAND_HWND(wParam, lParam), &rc, FALSE); } default: break; } break; case IDC_CURRENT: switch (GET_WM_COMMAND_CMD(wParam, lParam)) { case LBN_SELCHANGE: { BOOL bDelOK; HWND hwndList = GET_WM_COMMAND_HWND(wParam, lParam); int iPos = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0L); SafeEnableWindow(hDlg, IDOK, hwndList, BOOLFROMPTR(SendItemNotify(lpad->ptb, iPos, TBN_QUERYINSERT))); bDelOK = !(HIWORD(SendMessage(hwndList, LB_GETITEMDATA, iPos, 0L)) & FLAG_NODEL); SafeEnableWindow(hDlg, IDC_REMOVE, hwndList, bDelOK); SafeEnableWindow(hDlg, IDC_MOVEUP, hwndList, bDelOK && GetNearestInsert(lpad->ptb, iPos - 1, 0, GNI_LOW) >= 0); SafeEnableWindow(hDlg, IDC_MOVEDOWN, hwndList, bDelOK && GetNearestInsert(lpad->ptb, iPos + 2, lpad->ptb->iNumButtons, GNI_HIGH) >=0 ); break; } case LBN_DBLCLK: SendMessage(hDlg, WM_COMMAND, IDC_REMOVE, 0L); break; case LBN_SETFOCUS: case LBN_KILLFOCUS: { RECT rc; if (SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETITEMRECT, (int)SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL, 0, 0L), (LPARAM)(LPRECT)&rc) != LB_ERR) InvalidateRect(GET_WM_COMMAND_HWND(wParam, lParam), &rc, FALSE); } default: break; } break; case IDC_REMOVE: { int iPos = (int)SendDlgItemMessage(hDlg, IDC_CURRENT, LB_GETCURSEL, 0, 0); LBMoveButton(lpad, IDC_CURRENT, iPos, IDC_BUTTONLIST, 0, 0); break; } case IDC_MOVEUP: case IDC_MOVEDOWN: { int iPosSrc, iPosDst; iPosSrc = (int)SendDlgItemMessage(hDlg, IDC_CURRENT, LB_GETCURSEL, 0, 0L); if (wParam == IDC_MOVEUP) iPosDst = GetNearestInsert(lpad->ptb, iPosSrc - 1, 0, GNI_LOW); else iPosDst = GetNearestInsert(lpad->ptb, iPosSrc + 2, lpad->ptb->iNumButtons, GNI_HIGH); LBMoveButton(lpad, IDC_CURRENT, iPosSrc, IDC_CURRENT,iPosDst,0); break; } case IDC_RESET: { // ptb will change across call below HWND hwndT = lpad->ptb->ci.hwnd; BOOL fClose = FALSE; NMTBCUSTOMIZEDLG nm; nm.hDlg = hDlg; if (CCSendNotify(&lpad->ptb->ci, TBN_RESET, &nm.hdr) == TBNRF_ENDCUSTOMIZE) fClose = TRUE; // ptb probably changed across above call lpad->ptb = FixPTB(hwndT); /* Reset the dialog, but exit if something goes wrong. */ lpad->iPos = 0; if (!fClose && InitAdjustDlg(hDlg, lpad)) break; } /* We have to fall through because we won't know where to insert * buttons after resetting. */ case IDCANCEL: EndDialog(hDlg, TRUE); break; default: return(FALSE); } break; default: if (uMsg == uDragListMsg) return HandleDragMsg(lpad, hDlg, wParam, (LPDRAGLISTINFO)lParam); return(FALSE); } return(TRUE); } // BUGBUG: this should support saving to an IStream /* This saves the state of the toolbar. Spaces are saved as -1 (-2 if hidden) * and other buttons are just saved as the command ID. When restoring, all * ID's are filled in, and the app is queried for all buttons so that the * bitmap and state information may be filled in. Button ID's that are not * returned from the app are removed. */ BOOL SaveRestoreFromReg(PTBSTATE ptb, BOOL bWrite, HKEY hkr, LPCTSTR pszSubKey, LPCTSTR pszValueName) { BOOL bRet = FALSE; TCHAR szDesc[128]; if (bWrite) { UINT uSize = ptb->iNumButtons * sizeof(DWORD); NMTBSAVE nmtbs; BOOL fAlloced = FALSE; nmtbs.pData = NULL; nmtbs.cbData = uSize; nmtbs.pCurrent = NULL; nmtbs.iItem = -1; // signal pre saving nmtbs.cButtons = ptb->iNumButtons; CCSendNotify(&ptb->ci, TBN_SAVE, &nmtbs.hdr); if (!nmtbs.pData) { nmtbs.pData = (DWORD *)LocalAlloc(LPTR, nmtbs.cbData); fAlloced = TRUE; } // BUGBUG -- Somebody could've changed ptb->iNumButtons // during the CCSendNotify if (!nmtbs.pCurrent) nmtbs.pCurrent = nmtbs.pData; if (nmtbs.pData) { HKEY hkeySave; if (RegCreateKey(hkr, pszSubKey, &hkeySave) == ERROR_SUCCESS) { int i; for (i = 0; i < ptb->iNumButtons; i++) { if (ptb->Buttons[i].idCommand) *nmtbs.pCurrent = ptb->Buttons[i].idCommand; else { // If the separator has an ID, then it is an "owner" item. if (ptb->Buttons[i].fsState & TBSTATE_HIDDEN) *nmtbs.pCurrent = (DWORD)-2; // hidden else *nmtbs.pCurrent = (DWORD)-1; // normal seperator } nmtbs.pCurrent++; nmtbs.iItem = i; TBOutputStruct(ptb, &ptb->Buttons[i], &nmtbs.tbButton); CCSendNotify(&ptb->ci, TBN_SAVE, &nmtbs.hdr); } if (RegSetValueEx(hkeySave, (LPTSTR)pszValueName, 0, REG_BINARY, (LPVOID)nmtbs.pData, nmtbs.cbData) == ERROR_SUCCESS) bRet = TRUE; RegCloseKey(hkeySave); } if (fAlloced) LocalFree((HLOCAL)nmtbs.pData); } } else { HKEY hkey; if (RegOpenKey(hkr, pszSubKey, &hkey) == ERROR_SUCCESS) { DWORD cbSize = 0; if ((RegQueryValueEx(hkey, (LPTSTR)pszValueName, 0, NULL, NULL, &cbSize) == ERROR_SUCCESS) && (cbSize > sizeof(DWORD))) { UINT uSize = (UINT)cbSize; DWORD *pData = (DWORD *)LocalAlloc(LPTR, uSize); if (pData) { DWORD dwType; DWORD cbSize = (DWORD)uSize; if ((RegQueryValueEx(hkey, (LPTSTR)pszValueName, 0, &dwType, (LPVOID)pData, &cbSize) == ERROR_SUCCESS) && (dwType == REG_BINARY) && (cbSize == (DWORD)uSize)) { int iButtonIndex; NMTBRESTORE nmtbs; BOOL fAlloced = FALSE; nmtbs.pData = pData; nmtbs.pCurrent = pData; nmtbs.iItem = -1; // signal pre saving nmtbs.cButtons = (int)uSize / SIZEOF(DWORD); nmtbs.cbBytesPerRecord = SIZEOF(DWORD); nmtbs.cbData = uSize; // since we don't know the cButtons if they've added on extra data to pData, // we'll use whatever they fill for cButtons if (!CCSendNotify(&ptb->ci, TBN_RESTORE, &nmtbs.hdr)) { // // Before reloading the buttons, delete the tooltips // of the previous buttons (if they exist). // if (ptb && ptb->hwndToolTips) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.hwnd = ptb->ci.hwnd; for (iButtonIndex = 0; iButtonIndex < ptb->iNumButtons; iButtonIndex++) { if (!(ptb->Buttons[iButtonIndex].fsStyle & TBSTYLE_SEP)) { ti.uId = ptb->Buttons[iButtonIndex].idCommand; SendMessage(ptb->hwndToolTips, TTM_DELTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } } } // BUGBUG -- can ptb be NULL here? - raymondc // BUGBUG -- what if pCaptureButton != NULL? // grow (or maybe shrink) pbt to hold new buttons if (TBReallocButtons(ptb, nmtbs.cButtons)) { int i; if (ptb->iNumButtons < nmtbs.cButtons) ZeroMemory(&ptb->Buttons[ptb->iNumButtons], (nmtbs.cButtons - ptb->iNumButtons) * sizeof(TBBUTTON)); ptb->iNumButtons = nmtbs.cButtons; for (i = 0; i < ptb->iNumButtons; i++) { nmtbs.iItem = i; if ((long)*nmtbs.pCurrent < 0) { ptb->Buttons[i].fsStyle = TBSTYLE_SEP; ptb->Buttons[i].DUMMYUNION_MEMBER(iBitmap) = g_dxButtonSep; ptb->Buttons[i].idCommand = 0; if (*nmtbs.pCurrent == (DWORD)-1) ptb->Buttons[i].fsState = 0; else { ASSERT(*nmtbs.pCurrent == (DWORD)-2); ptb->Buttons[i].fsState = TBSTATE_HIDDEN; } } else { ptb->Buttons[i].fsStyle = 0; ptb->Buttons[i].idCommand = *nmtbs.pCurrent; ptb->Buttons[i].DUMMYUNION_MEMBER(iBitmap) = -1; } nmtbs.pCurrent++; TBOutputStruct(ptb, &ptb->Buttons[i], &nmtbs.tbButton); CCSendNotify(&ptb->ci, TBN_RESTORE, &nmtbs.hdr); ASSERT(nmtbs.tbButton.iString == -1 || !HIWORD(nmtbs.tbButton.iString)); // we don't thunk. only allow string index in string pool here if (HIWORD(nmtbs.tbButton.iString)) nmtbs.tbButton.iString = 0; TBInputStruct(ptb, &ptb->Buttons[i], &nmtbs.tbButton); } // Now query for all buttons, and fill in the rest of the info // For backward compatibility, ignore return value of TBN_BEGINADJUST // if client is older than version 5 (NT5 #185499). if (!SendCmdNotify(ptb, TBN_BEGINADJUST) || (ptb->ci.iVersion < 5)) { for (i = 0; ; i++) { TBBUTTONDATA tbAdjust; tbAdjust.idCommand = 0; if (!GetAdjustInfo(ptb, i, &tbAdjust, szDesc, ARRAYSIZE(szDesc))) break; if (!(tbAdjust.fsStyle & TBSTYLE_SEP) || tbAdjust.idCommand) { int iPos = PositionFromID(ptb, tbAdjust.idCommand); if (iPos >= 0) { ptb->Buttons[iPos] = tbAdjust; } } } SendCmdNotify(ptb, TBN_ENDADJUST); } // cleanup all the buttons that were not recognized // do this backwards to minimize data movement (and nmtbs.cButtons changes) for (i = ptb->iNumButtons - 1; i >= 0; i--) { // DeleteButton does no realloc, so ptb will not move if (ptb->Buttons[i].DUMMYUNION_MEMBER(iBitmap) < 0) DeleteButton(ptb, (UINT)i); else { // the rest, add to tooltips if(ptb->hwndToolTips && (!(ptb->Buttons[i].fsStyle & TBSTYLE_SEP || !ptb->Buttons[i].idCommand))) { TOOLINFO ti; // don't bother setting the rect because we'll do it below // in TBInvalidateItemRects; ti.cbSize = sizeof(ti); ti.uFlags = 0; ti.hwnd = ptb->ci.hwnd; ti.uId = ptb->Buttons[i].idCommand; ti.lpszText = LPSTR_TEXTCALLBACK; SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti); } } } bRet = (ptb->iNumButtons != 0); // success // bugbug: break autosize to a function and call it SendMessage(ptb->ci.hwnd, TB_AUTOSIZE, 0, 0); InvalidateRect(ptb->ci.hwnd, NULL, TRUE); TBInvalidateItemRects(ptb); } } } LocalFree((HLOCAL)pData); } } RegCloseKey(hkey); } } return bRet; } #ifndef WIN32 #ifndef WINNT #pragma data_seg(DATASEG_READONLY) #endif #ifdef WINNT const TCHAR c_szToolbarStates[] = TEXT("Software\\Microsoft\\Windows NT\\CurrentVersion\\ToolbarState"); #else const TCHAR c_szToolbarStates[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\ToolbarState"); #endif #ifndef WINNT #pragma data_seg() #endif BOOL SaveRestore(PTBSTATE ptb, BOOL bWrite, LPTSTR *lpNames) { // note, we ignore lpNames[1] (the ini file name) // ... we hope we don't get conflicts with lpNames[0] entires return SaveRestoreFromReg(ptb, bWrite, HKEY_CURRENT_USER, c_szToolbarStates, lpNames[0]); } #endif void CustomizeTB(PTBSTATE ptb, int iPos) { ADJUSTDLGDATA ad; HWND hwndT = ptb->ci.hwnd; // ptb will change across call below HRSRC hrsrc; LANGID wLang; LPVOID pTemplate; if (ptb->hdlgCust) // We are already customizing this toolbar return; ad.ptb = ptb; ad.iPos = iPos; // REVIEW: really should be per thread data, but not likely to cause a problem // see note in WM_MEASUREITEM code g_dyButtonHack = (ptb->ci.style & TBSTYLE_FLAT) ? ptb->iDyBitmap : ptb->iButHeight; SendCmdNotify(ptb, TBN_BEGINADJUST); // // Do locale-specific futzing. // wLang = LANGIDFROMLCID(CCGetProperThreadLocale(NULL)); hrsrc = FindResourceExRetry(HINST_THISDLL, RT_DIALOG, MAKEINTRESOURCE(ADJUSTDLG), wLang); if (hrsrc && (pTemplate = (LPVOID)LoadResource(HINST_THISDLL, hrsrc))) { DialogBoxIndirectParam(HINST_THISDLL, pTemplate, ptb->ci.hwndParent, AdjustDlgProc, (LPARAM)(LPADJUSTDLGDATA)&ad); } // ptb probably changed across above call ptb = (PTBSTATE)GetWindowInt(hwndT, 0); ptb->hdlgCust = NULL; SendCmdNotify(ptb, TBN_ENDADJUST); }