#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:

            //notify of navigation key usage
            CCNotifyNavigationKeyUsage(&(ptb->ci), UISF_HIDEFOCUS);

            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)
            {
                RECT rcFace = rc;
                rcFace.right = rcFace.left + ptb->iDxBitmap + g_cxEdge;
                DrawFace(hdc, &rcFace, rc.left + 1, rc.top + 1, 0, 0, 0, 0, &tbdraw, 0, 0);
            }
            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)) && 
        !(CCGetUIState(&(ptb->ci)) & UISF_HIDEFOCUS))
    {
        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);
}

// Context Help IDs
const static DWORD aAdjustHelpIDs[] = 
{  
    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
};

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);
}

// FEATURE: 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;
        }

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

                            // 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

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

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);
}

