/**************************** Module Header ********************************\
* Module Name: mncomput.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* Menu Layout Calculation Routines
*
* History:
* 10-10-90 JimA       Cleanup.
* 03-18-91 IanJa      Window revalidation added
\***************************************************************************/

#include "precomp.h"
#pragma hdrstop


DWORD MNRecalcTabStrings(HDC hdc, PMENU pMenu, UINT iBeg, UINT iEnd,
                         DWORD xTab, DWORD hCount);

/***************************************************************************\
* xxxMNGetBitmapSize
*
* Returns TRUE if measureitem was sent and FALSE if not
*
* History:
*  07-23-96 GerardoB - Added header & fixed up for 5.0
\***************************************************************************/
BOOL xxxMNGetBitmapSize(
    LPITEM pItem,
    PWND pwndNotify)
{
    MEASUREITEMSTRUCT mis;

    if (pItem->cxBmp != MNIS_MEASUREBMP) {
        return FALSE;
    }

    // Send a measure item message to the owner
    mis.CtlType = ODT_MENU;
    mis.CtlID = 0;
    mis.itemID  = pItem->wID;
    mis.itemWidth = 0;
// After scrollable menus
//    mis32.itemHeight= gcyMenuFontChar;
    mis.itemHeight= (UINT)gpsi->cySysFontChar;
    mis.itemData = pItem->dwItemData;

    xxxSendMessage(pwndNotify, WM_MEASUREITEM, 0, (LPARAM)&mis);

    pItem->cxBmp = mis.itemWidth;
    pItem->cyBmp = mis.itemHeight;

    return TRUE;
}

/***************************************************************************\
* xxxItemSize
*
* Calc the dimensions of bitmaps and strings. Loword of returned
* value contains width, high word contains height of item.
*
* History:
*  07-23-96 GerardoB - fixed up for 5.0
\***************************************************************************/
BOOL xxxMNItemSize(
    PMENU pMenu,
    PWND pwndNotify,
    HDC hdc,
    PITEM pItem,
    BOOL fPopup,
    LPPOINT lppt)
{
    BITMAP bmp;
    int width = 0;
    int height = 0;
    DWORD xRightJustify;
    LPWSTR lpMenuString;
    HFONT               hfnOld;
    int                 tcExtra;

    UNREFERENCED_PARAMETER(pMenu);

    CheckLock(pMenu);
    CheckLock(pwndNotify);

    if (!fPopup) {

        /*
         * Save off the height of the top menu bar since we will used this often
         * if the pItem is not in a popup. (ie. it is in the top level menu bar)
         */
        height = SYSMET(CYMENUSIZE);
    }

    hfnOld = NULL;
    if (TestMFS(pItem, MFS_DEFAULT)) {
        if (ghMenuFontDef)
            hfnOld = GreSelectFont(hdc, ghMenuFontDef);
        else {
            tcExtra = GreGetTextCharacterExtra(hdc);
            GreSetTextCharacterExtra(hdc, tcExtra + 1 + (gcxMenuFontChar / gpsi->cxSysFontChar));
        }
    }

    /*
     * Compute bitmap dimensions if needed
     */
    if (pItem->hbmp != NULL)  {
        if (pItem->hbmp == HBMMENU_CALLBACK) {
            xxxMNGetBitmapSize(pItem, pwndNotify);
        } else if (pItem->cxBmp == MNIS_MEASUREBMP) {
            if (TestMFS(pItem, MFS_CACHEDBMP)) {
                pItem->cxBmp = SYSMET(CXMENUSIZE);
                pItem->cyBmp = SYSMET(CYMENUSIZE);
                if (pItem->hbmp == HBMMENU_SYSTEM) {
                    pItem->cxBmp += SYSMET(CXEDGE);
                    /*
                     * Chicago/Memphis only stretch the width,
                     * not the height. NT Bug 124779. FritzS
                     */
                 //  pItem->cyBmp += SYSMET(CXEDGE);
                }
            } else {
                if (GreExtGetObjectW(pItem->hbmp, sizeof(BITMAP), (LPSTR)&bmp)) {
                    pItem->cxBmp = bmp.bmWidth;
                    pItem->cyBmp = bmp.bmHeight;
                } else {
                    /*
                     * If the bitmap is not useable, this is as good a default
                     * as any.
                     */
                    pItem->cxBmp = SYSMET(CXMENUSIZE);
                    pItem->cyBmp = SYSMET(CYMENUSIZE);
                }
            }
        }
        width = pItem->cxBmp;
        /*
         * Remember the max bitmap width to align the text in all items.
         */
        pMenu->cxTextAlign = max(pMenu->cxTextAlign, (DWORD)width);
        /*
         * In menu bars, we force the item to be at least CYMNSIZE.
         * Fixes many, many problems w/ apps that fake own MDI.
         */
        if (fPopup) {
            height = pItem->cyBmp;
        } else {
            height = max((int)pItem->cyBmp, height);
        }
    } else if (TestMFT(pItem, MFT_OWNERDRAW)) {
        // This is an ownerdraw item -- the width and height are stored in
        // cxBmp and cyBmp
        xxxMNGetBitmapSize(pItem, pwndNotify);
        width = pItem->cxBmp;
        //
        // Ignore height with menu bar now--that's set by user.
        //
        if (fPopup) {
            height = pItem->cyBmp;
            // If this item has a popup (hierarchical) menu associated with it, then
            // reserve room for the bitmap that tells the user that a hierarchical
            // menu exists here.
            // B#2966, t-arthb

            UserAssert(fPopup == (TestMF(pMenu, MFISPOPUP) != 0));

            width = width + (gcxMenuFontChar << 1);
        }
    }

    if ((pItem->lpstr != NULL) && (!TestMFT(pItem, MFT_OWNERDRAW)) ) {
        SIZE size;

        /*
         * This menu item contains a string
         */

        /*
         * We want to keep the menu bar height if this isn't a popup.
         */
        if (fPopup) {
            /* The thickness of mnemonic underscore is CYBORDER and the gap
             * between the characters and the underscore is another CYBORDER
             */
            height = max(height, gcyMenuFontChar + gcyMenuFontExternLeading + SYSMET(CYEDGE));
        }

        lpMenuString = TextPointer(pItem->lpstr);
        xRightJustify = FindCharPosition(lpMenuString, TEXT('\t'));

        xxxPSMGetTextExtent(hdc, lpMenuString, xRightJustify, &size);

        if (width) {
            width += MNXSPACE + size.cx;
        } else {
            width =  size.cx;
        }
    }

    if (fPopup && !TestMFT(pItem, MFT_OWNERDRAW)) {
        /*
         *  Add on space for checkmark, then horz spacing for default & disabled
         *   plus some left margin.
         */
        if (TestMF(pMenu, MNS_CHECKORBMP) || !TestMF(pMenu, MNS_NOCHECK)) {
            width += gpsi->oembmi[OBI_MENUCHECK].cx;
        }
        width += MNXSPACE + MNLEFTMARGIN + 2;
        height += 2;
    }

    if (TestMFS(pItem, MFS_DEFAULT)) {
        if (hfnOld)
            GreSelectFont(hdc, hfnOld);
        else
            GreSetTextCharacterExtra(hdc, tcExtra);
    }

    /*
     * Loword contains width, high word contains height of item.
     */
    lppt->x = width;
    lppt->y = height;

    return(TestMFT(pItem, MFT_OWNERDRAW));
}

/***************************************************************************\
* xxxMNCompute
*
* !
*
* History:
\***************************************************************************/
int xxxMNCompute(
    PMENU pMenu,
    PWND pwndNotify,
    DWORD yMenuTop,
    DWORD xMenuLeft,
    DWORD cxMax,
    LPDWORD lpdwMenuHeight)
{
    UINT         cItem;
    DWORD        cxItem;
    DWORD        cyItem;
    DWORD        cyItemKeep;
    DWORD        yPopupTop;
    INT          cMaxWidth;
    DWORD        cMaxHeight;
    UINT         cItemBegCol;
    DWORD        temp;
    PITEM        pCurItem;
    POINT        ptMNItemSize;
    BOOL         fOwnerDrawItems;
    BOOL         fMenuBreak;
    LPWSTR       lpsz;
    BOOL         fPopupMenu;
    DWORD        menuHeight = 0;
    HDC          hdc;
    HFONT        hOldFont;
    PTHREADINFO  ptiCurrent = PtiCurrent();
    BOOL         fStringAndBitmapItems;

    CheckLock(pMenu);
    CheckLock(pwndNotify);

    /*
     * Whoever computes the menu, becomes the owner.
     */
    if (pwndNotify != pMenu->spwndNotify) {
        Lock(&pMenu->spwndNotify, pwndNotify);
    }


    if (lpdwMenuHeight != NULL) {
        menuHeight = *lpdwMenuHeight;
    }

    /*
     * Empty menus have a height of zero.
     */
    if (pMenu->cItems == 0) {
        return 0;
    }

    hdc = _GetDCEx(NULL, NULL, DCX_WINDOW | DCX_CACHE);
    hOldFont = GreSelectFont(hdc, ghMenuFont);

    /*
     * Try to make a non-multirow menu first.
     */
    pMenu->fFlags &= (~MFMULTIROW);

    fPopupMenu = TestMF(pMenu, MFISPOPUP);

    if (fPopupMenu) {
        /*
         * Reset the menu bar height to 0 if this is a popup since we are
         * being called from menu.c MN_SIZEWINDOW.
         */
        menuHeight = 0;
    } else if (pwndNotify != NULL) {
        pMenu->cxMenu = cxMax;
    }

    /*
     * Initialize the computing variables.
     */
    cMaxWidth = cyItemKeep = 0L;
    cItemBegCol = 0;

    cyItem = yPopupTop = yMenuTop;
    cxItem = xMenuLeft;

    pCurItem = (PITEM)&pMenu->rgItems[0];
    /*
     * cxTextAlign is used to align the text in all items; this is useful
     *  in popup menus that mix text only items with bitmap-text items. It's
     *  set to the max bitmap width plus some spacing.
     * Do this for new menus wich use string AND bitmaps on the same item
     */
    fStringAndBitmapItems = FALSE;
    pMenu->cxTextAlign = 0;

    /*
     * Process each item in the menu.
     */
    fOwnerDrawItems = FALSE;
    for (cItem = 0; cItem < pMenu->cItems; cItem++) {

        /*
         * If it's not a separator, find the dimensions of the object.
         */
        if (TestMFT(pCurItem, MFT_SEPARATOR) &&
                ( !TestMFT(pCurItem, MFT_OWNERDRAW) ||
                  (LOWORD(ptiCurrent->dwExpWinVer) < VER40)) ) {
            /*
            * If version is less than 4.0  don't test the MFT_OWNERDRAW
            * flag. Bug 21922; App MaxEda has both separator and Ownerdraw
            * flags on. In 3.51 we didn't test the OwnerDraw flag
            */

            //
            // This is a separator. It's drawn as wide as the menu area,
            // leaving some space above and below. Since the menu area is
            // the max of the items' widths, we set our width to 0 so as not
            // to affect the result.
            //
            pCurItem->cxItem = 0;
            pCurItem->cyItem = SYSMET(CYMENUSIZE) / 2;


        } else {
            /*
             * Are we using NT5 strings and bitmaps?
             */
            fStringAndBitmapItems |= ((pCurItem->hbmp != NULL) && (pCurItem->lpstr != NULL));
            /*
             * Get the item's X and Y dimensions.
             */
            if (xxxMNItemSize(pMenu, pwndNotify, hdc, pCurItem, fPopupMenu, &ptMNItemSize))
                fOwnerDrawItems = TRUE;

            pCurItem->cxItem = ptMNItemSize.x;
            pCurItem->cyItem = ptMNItemSize.y;
            if (!fPopupMenu && ((pCurItem->hbmp == NULL) || (pCurItem->lpstr != NULL))) {
                pCurItem->cxItem += gcxMenuFontChar * 2;
            }
        }

        if (menuHeight != 0)
            pCurItem->cyItem = menuHeight;

        /*
         * If this is the first item, initialize cMaxHeight.
         */
        if (cItem == 0)
            cMaxHeight = pCurItem->cyItem;

        /*
         * Is this a Pull-Down menu?
         */
        if (fPopupMenu) {

            /*
             * If this item has a break or is the last item...
             */
            if ((fMenuBreak = TestMFT(pCurItem, MFT_BREAK)) ||
                pMenu->cItems == cItem + (UINT)1) {

                /*
                 * Keep cMaxWidth around if this is not the last item.
                 */
                temp = cMaxWidth;
                if (pMenu->cItems == cItem + (UINT)1) {
                    if ((int)(pCurItem->cxItem) > cMaxWidth)
                        temp = pCurItem->cxItem;
                }

                /*
                 * Get new width of string from RecalcTabStrings.
                 */
                temp = MNRecalcTabStrings(hdc, pMenu, cItemBegCol,
                        (UINT)(cItem + (fMenuBreak ? 0 : 1)), temp, cxItem);

                /*
                 * If this item has a break, account for it.
                 */
                if (fMenuBreak) {
                    //
                    // Add on space for the etch and a border on either side.
                    // NOTE:  For old apps that do weird stuff with owner
                    // draw, keep 'em happy by adding on the same amount
                    // of space we did in 3.1.
                    //
                    if (fOwnerDrawItems && !TestWF(pwndNotify, WFWIN40COMPAT))
                        cxItem = temp + SYSMET(CXBORDER);
                    else
                        cxItem = temp + 2 * SYSMET(CXEDGE);

                    /*
                     * Reset the cMaxWidth to the current item.
                     */
                    cMaxWidth = pCurItem->cxItem;

                    /*
                     * Start at the top.
                     */
                    cyItem = yPopupTop;

                    /*
                     * Save the item where this column begins.
                     */
                    cItemBegCol = cItem;

                    /*
                     * If this item is also the last item, recalc for this
                     * column.
                     */
                    if (pMenu->cItems == (UINT)(cItem + 1)) {
                        temp = MNRecalcTabStrings(hdc, pMenu, cItem,
                                (UINT)(cItem + 1), cMaxWidth, cxItem);
                    }
                }

                /*
                 * If this is the last entry, supply the width.
                 */
                if (pMenu->cItems == cItem + (UINT)1)
                    pMenu->cxMenu = temp;
            }

            pCurItem->xItem = cxItem;
            pCurItem->yItem = cyItem;

            cyItem += pCurItem->cyItem;

            if (cyItemKeep < cyItem) {
                cyItemKeep = cyItem;
            }
        } else {
            /*
             * This a Top Level menu, not a Pull-Down.
             */

            /*
             * Adjust right aligned items before testing for multirow
             */
            if (pCurItem->lpstr != NULL) {
                lpsz = TextPointer(pCurItem->lpstr);
                if ((lpsz != NULL) && (*lpsz == CH_HELPPREFIX)) {
                    pCurItem->cxItem -= gcxMenuFontChar;
                }
            }


            /*
             * If this is a new line or a menu break.
             */
            if ((TestMFT(pCurItem, MFT_BREAK)) ||
                    (((cxItem + pCurItem->cxItem + gcxMenuFontChar) >
                    (xMenuLeft + pMenu->cxMenu)) && (cItem != 0))) {
                cyItem += cMaxHeight;

                cxItem = xMenuLeft;
                cMaxHeight = pCurItem->cyItem;
                pMenu->fFlags |= MFMULTIROW;
            }

            pCurItem->yItem = cyItem;

            pCurItem->xItem = cxItem;
            cxItem += pCurItem->cxItem;
        }

        if (cMaxWidth < (int)(pCurItem->cxItem)) {
            cMaxWidth = pCurItem->cxItem;
        }

        if (cMaxHeight != pCurItem->cyItem) {
            if (cMaxHeight < pCurItem->cyItem)
                cMaxHeight = pCurItem->cyItem;

            if (!fPopupMenu) {
                menuHeight = cMaxHeight;
            }
        }

        if (!fPopupMenu) {
            cyItemKeep = cyItem + cMaxHeight;
        }

        pCurItem++;
    }

    /*
     * Determine where the strings should be drawn so they are aligned.
     * The alignment is only for popup (vertical) menus (see
     * xxxRealDrawMenuItem). The actual space depends on the MNS_NOCHECK
     * and MNS_CHECKORBMP styles Multicolumn menus don't get aligment (now 
     * that we have scrollbars, multicolumn is out).
     */
    if (!fStringAndBitmapItems || (cItemBegCol != 0)) {
        pMenu->cxTextAlign = 0;
    } else if (TestMF(pMenu, MNS_NOCHECK)) {
        pMenu->cxTextAlign += MNXSPACE;
    } else if (TestMF(pMenu, MNS_CHECKORBMP)) {
        pMenu->cxTextAlign = max(pMenu->cxTextAlign, (UINT)gpsi->oembmi[OBI_MENUCHECK].cx);
        pMenu->cxTextAlign += MNXSPACE;
    } else {
        pMenu->cxTextAlign += gpsi->oembmi[OBI_MENUCHECK].cx + MNXSPACE;
    }

    /*
     * Add the left margin
     */
    if (pMenu->cxTextAlign != 0) {
        pMenu->cxTextAlign += MNLEFTMARGIN;
    }


    if (cItemBegCol && pMenu->cItems &&
        TestMFT(((PITEM)&pMenu->rgItems[0]), MFT_RIGHTJUSTIFY)) {
        //
        // multi-column, if we are in RtoL mode, reverse the columns
        //
        pCurItem = &pMenu->rgItems[0];

        for (cItem = 0; cItem < pMenu->cItems; cItem++, pCurItem++) {
            pCurItem->xItem = pMenu->cxMenu -
                              (pCurItem->xItem + pCurItem->cxItem);
        }
    }

    GreSelectFont(hdc, hOldFont);
    _ReleaseDC(hdc);

    pMenu->cyMenu = cyItemKeep - yMenuTop;

    if (lpdwMenuHeight != NULL) {
        *lpdwMenuHeight = menuHeight;
    }

    return pMenu->cyMenu;
}

/***************************************************************************\
* MBC_RightJustifyMenu
*
* !
*
* History:
\***************************************************************************/
VOID MBC_RightJustifyMenu(
    PMENU pMenu)
{
    PITEM pItem;
    int cItem;
    int iFirstRJItem = MFMWFP_NOITEM;
    DWORD xMenuPos;
    DWORD  yPos;
    DWORD  xPosStart;
    DWORD  xPosEnd;
    int    cItemEnd;
    int    cItemStart;
    BOOL   bIsWin95;

    //
    // Need to compensate for MDI menus. Need to do all here as Win31/Hebrew did
    // this. Also messed up computation, anything non-text was not moved.
    //
    if (pMenu->cItems == 0) {
        return;
    }

    pItem = (PITEM)&pMenu->rgItems[0];
    cItemStart = 0;

    if (TestMF(pMenu,MFRTL)) {
        bIsWin95 = TestWF(pMenu->spwndNotify, WFWIN40COMPAT);

        while (cItemStart < (int)pMenu->cItems) {
            if (bIsWin95) {
                //
                // deal with fake MDI dude.
                //
                if (!cItemStart && IsMDIItem(pItem)) {
                    goto StillFindStart;
                } else {
                    break;
                }
            }

            if (TestMFT(pItem, MFT_BITMAP)) {
                if (pItem->hbmp > HBMMENU_MAX) {
                    break;
                } else {
                    goto StillFindStart;
                }
            }

            if (!TestMFT(pItem, MFT_OWNERDRAW)) {
                break;
            }

StillFindStart:
            cItemStart++;
            pItem = pMenu->rgItems + cItemStart;
        }

        //
        // Anything before cItems should be left where it is. Now need to find
        // the last item to fool with.
        //
        cItemEnd = pMenu->cItems - 1;
        pItem = pMenu->rgItems + cItemEnd;

        while (cItemEnd > cItemStart) {
            if (bIsWin95) {
                //
                // fake mdi dudes
                //
                if (IsMDIItem(pItem)) {
                    goto StillFindEnd;
                } else {
                    break;
                }
            }

            if (!TestMFT(pItem, MFT_BITMAP) && !TestMFT(pItem, MFT_OWNERDRAW)) {
                break;
            }
StillFindEnd:
            cItemEnd--;
            pItem = pMenu->rgItems + cItemEnd;
        }

        yPos      = pMenu->rgItems[0].yItem;
        xMenuPos  = pMenu->cxMenu ;
        xPosStart = xMenuPos;              // for 2nd row onward
        xPosEnd   = pMenu->rgItems[cItemStart].xItem ;

        for (cItem = pMenu->cItems-1; cItem > cItemEnd; cItem--) {
            //
            // Force any MDI dudes back to the top line again.
            //
            pItem        = pMenu->rgItems + cItem;
            xMenuPos     = pItem->xItem = xMenuPos - pItem->cxItem;
            pItem->yItem = yPos;
        }

        for (cItem = cItemStart; cItem <= cItemEnd; cItem++) {
            pItem = pMenu->rgItems + cItem;
            if (xMenuPos - pItem->cxItem > xPosEnd) {
                xMenuPos -= pItem->cxItem;
            } else {
                xMenuPos = xPosStart - pItem->cxItem;
                yPos     += pItem->cyItem;
                xPosEnd  = 0;
            }
            pItem->xItem = xMenuPos;
            pItem->yItem = yPos;
        }
    } else {
        // B#4055
        // Use signed arithmetic so comparison cItem >= iFirstRJItem won't
        // cause underflow.
        for (cItem = 0; cItem < (int)pMenu->cItems; cItem++) {
            // Find the first item which is right justified.
            if (TestMFT((pMenu->rgItems + cItem), MFT_RIGHTJUSTIFY)) {
                iFirstRJItem = cItem;
                xMenuPos = pMenu->cxMenu + pMenu->rgItems[0].xItem;
                for (cItem = (int)pMenu->cItems - 1; cItem >= iFirstRJItem; cItem--) {
                    pItem = pMenu->rgItems + cItem;
                    xMenuPos -= pItem->cxItem;
                    if (pItem->xItem < xMenuPos)
                        pItem->xItem = xMenuPos;
                }
                return;
            }
        }
    }
}

/***************************************************************************\
* xxxMenuBarCompute
*
* Returns the height of the menubar menu. yMenuTop, xMenuLeft, and
* cxMax are used when computing the height/width of top level menu bars in
* windows.
*
*
* History:
\***************************************************************************/
int xxxMenuBarCompute(
    PMENU pMenu,
    PWND pwndNotify,
    DWORD yMenuTop,
    DWORD xMenuLeft,
    int cxMax)
{
    int size;
    /* menuHeight is set by MNCompute when dealing with a top level menu and
     * not all items in the menu bar have the same height. Thus, by setting
     * menuHeight, MNCompute is called a second time to set every item to the
     * same height. The actual value stored in menuHeight is the maximum
     * height of all the menu bar items
     */
    DWORD menuHeight = 0;

    CheckLock(pwndNotify);
    CheckLock(pMenu);

    size = xxxMNCompute(pMenu, pwndNotify, yMenuTop, xMenuLeft, cxMax, &menuHeight);

    if (!TestMF(pMenu, MFISPOPUP)) {
        if (menuHeight != 0) {

            /*
             * Add a border for the multi-row case.
             */
            size = xxxMNCompute(pMenu, pwndNotify, yMenuTop, xMenuLeft,
                    cxMax, &menuHeight);
        }

        /*
         * Right justification of HELP items is only needed on top level
         * menus.
         */
        MBC_RightJustifyMenu(pMenu);
    }

    /*
     * There's an extra border underneath the menu bar, if it's not empty!
     */
    return(size ? size + SYSMET(CYBORDER) : size);
}

/***************************************************************************\
* xxxMNRecomputeBarIfNeeded
*
* !
*
* History:
\***************************************************************************/
VOID xxxMNRecomputeBarIfNeeded(
    PWND pwndNotify,
    PMENU pMenu)
{
    int cxFrame;
    int cyFrame;

    UserAssert(!TestMF(pMenu, MFISPOPUP));

    CheckLock(pwndNotify);
    CheckLock(pMenu);

    if (!TestMF(pMenu, MFSYSMENU)
        && ((pMenu->spwndNotify != pwndNotify) || !pMenu->cxMenu || !pMenu->cyMenu)) {
        int cBorders;

        cBorders = GetWindowBorders(pwndNotify->style, pwndNotify->ExStyle, TRUE, FALSE);
        cxFrame = cBorders * SYSMET(CXBORDER);
        cyFrame = cBorders * SYSMET(CYBORDER);

        cyFrame += GetCaptionHeight(pwndNotify);

        // The width passed in this call was larger by cxFrame;
        // Fix for Bug #11466 - Fixed by SANKAR - 01/06/92 --
        xxxMenuBarCompute(pMenu, pwndNotify, cyFrame, cxFrame,
                (pwndNotify->rcWindow.right - pwndNotify->rcWindow.left) - cxFrame * 2);
    }
}

/***************************************************************************\
* RecalcTabStrings
*
* !
*
* History:
*   10-11-90 JimA       Translated from ASM
\***************************************************************************/
DWORD MNRecalcTabStrings(
    HDC hdc,
    PMENU pMenu,
    UINT iBeg,
    UINT iEnd,
    DWORD xTab,
    DWORD hCount)
{
    UINT i;
    UINT    cOwnerDraw;
    int adx;
    int     maxWidth = 0;
    int     cx;
    PITEM pItem;
    CheckLock(pMenu);

    xTab += hCount;

    if ((iBeg >= pMenu->cItems) || (iBeg > iEnd))
        goto SeeYa;

    cOwnerDraw = 0;

    for (i = iBeg, pItem = pMenu->rgItems + iBeg; i < iEnd; pItem++, i++) {
        adx = 0;

        /*
         * Subtract hCount to make dxTab relative to start of column for
         * multi-column menus.
         */

        pItem->dxTab = xTab - hCount;

        // Skip non-string or empty string items
        if ((pItem->lpstr != NULL) && !TestMFT(pItem, MFT_OWNERDRAW)) {
            LPWSTR   lpString = TextPointer(pItem->lpstr);
            int     tp;
            SIZE size;

            // Are there any tabs?
            tp = FindCharPosition(lpString, TEXT('\t'));
            if (tp < (int) pItem->cch) {
                PTHREADINFO ptiCurrent = PtiCurrentShared();

                if (CALL_LPK(ptiCurrent)) {
                    xxxClientGetTextExtentPointW(hdc, lpString + tp + 1,
                          pItem->cch - tp - 1, &size);
                } else {
                    GreGetTextExtentW(hdc, lpString + tp + 1,
                          pItem->cch - tp - 1, &size, GGTE_WIN3_EXTENT);
                }
                adx = gcxMenuFontChar + size.cx;
            }
        } else if (TestMFT(pItem, MFT_OWNERDRAW))
            cOwnerDraw++;

        adx += xTab;

        if (adx > maxWidth)
            maxWidth = adx;

    }

    /*
     * Add on space for hierarchical arrow. So basically, popup menu items
     * can have 4 columns:
     *      (1) Checkmark
     *      (2) Text
     *      (3) Tabbed text for accel
     *      (4) Hierarchical arrow
     *
     * But, we only do this if at least one item isn't ownerdraw
     *  and if there's at least one submenu in the popup.
     */
    if (cOwnerDraw != (iEnd - iBeg)) {
        maxWidth += gcxMenuFontChar + gpsi->oembmi[OBI_MENUCHECK].cx;
    }

    cx = maxWidth - hCount;

    for (i = iBeg, pItem = pMenu->rgItems + iBeg; i < iEnd; pItem++, i++)
        pItem->cxItem = cx;

SeeYa:
    return(maxWidth);
}

/***************************************************************************\
* GetMenuPwnd
*
* This function is used by xxxGetMenuItemRect and xxxMenuItemFromPoint
* which expect a pointer to the menu window for popup menus.
*
* In 4.0, apps had to go the extra mile to find the menu window; but this
* is bogus since menu windows are an internally thing not directly exposed
* to applications.
*
* 08/19/97  GerardoB    Created
\***************************************************************************/
PWND GetMenuPwnd(
    PWND pwnd,
    PMENU pmenu)
{
    if (TestMF(pmenu, MFISPOPUP)) {
        if ((pwnd == NULL) || (GETFNID(pwnd) != FNID_MENU)) {
            PPOPUPMENU ppopup = MNGetPopupFromMenu(pmenu, NULL);
            if (ppopup != NULL) {
                UserAssert(ppopup->spmenu == pmenu);
                pwnd = ppopup->spwndPopupMenu;
            }
        }
    }

    return pwnd;
}

/***************************************************************************\
* GetMenuItemRect
\***************************************************************************/
BOOL xxxGetMenuItemRect(
    PWND pwnd,
    PMENU pMenu,
    UINT uIndex,
    LPRECT lprcScreen)
{
    PITEM  pItem;
    int    dx, dy;
    BOOL fRTL;

    CheckLock(pwnd);
    CheckLock(pMenu);

    SetRectEmpty(lprcScreen);

    if (uIndex >= pMenu->cItems) {
        return FALSE;
    }

    /*
     * Raid #315084: Compatiblity with NT4/Win95/98
     *
     * WordPerfect does a long complex way to calc the menu rect
     * by calling this API. It calls GetMenuItemRect() with the app's
     * window.
     */
    if (pwnd == NULL || TestWF(pwnd, WFWIN50COMPAT)) {
        pwnd = GetMenuPwnd(pwnd, pMenu);
    }

    /*
     * If no pwnd, no go.
     * IMPORTANT: For MFISPOPUP we might get a different pwnd but we don't lock
     * it because we won't call back.
     */
    if (pwnd == NULL) {
        return FALSE;
    }

    fRTL = TestWF(pwnd, WEFLAYOUTRTL);
    
    if (TestMF(pMenu, MFISPOPUP)) {
        if (fRTL) {
            dx = pwnd->rcClient.right;            
        } else {
            dx = pwnd->rcClient.left;
        }
        dy = pwnd->rcClient.top;
    } else {
        xxxMNRecomputeBarIfNeeded(pwnd, pMenu);
        if (fRTL) {
            dx = pwnd->rcWindow.right;
        } else {
            dx = pwnd->rcWindow.left;
        }
        dy = pwnd->rcWindow.top;
    }

    if (uIndex >= pMenu->cItems) {
        return FALSE;
    }

    pItem = pMenu->rgItems + uIndex;

    lprcScreen->right   = pItem->cxItem;
    lprcScreen->bottom  = pItem->cyItem;
 
    if (fRTL) {
        dx -= (pItem->cxItem + pItem->xItem);
    } else {
        dx += pItem->xItem;
    }
    dy += pItem->yItem;
    OffsetRect(lprcScreen, dx, dy);
  
    return TRUE;
}

/***************************************************************************\
* xxxMenuItemFromPoint
\***************************************************************************/
int xxxMenuItemFromPoint(
    PWND pwnd,
    PMENU pMenu,
    POINT ptScreen)
{
    CheckLock(pwnd);
    CheckLock(pMenu);

    /*
     * If no pwnd, no go.
     *
     * IMPORTANT: For MFISPOPUP we might get a different pwnd but we don't lock
     * it because we won't call back.
     */
    pwnd = GetMenuPwnd(pwnd, pMenu);
    if (pwnd == NULL) {
        return MFMWFP_NOITEM;
    }

    if (!TestMF(pMenu, MFISPOPUP)) {
        xxxMNRecomputeBarIfNeeded(pwnd, pMenu);
    }

    return MNItemHitTest(pMenu, pwnd, ptScreen);
}


PMENU MakeMenuRtoL(
    PMENU pMenu,
    BOOL bRtoL)
{
    PITEM  pItem;
    int    cItem;

    if (bRtoL) {
        SetMF(pMenu,MFRTL);
    } else {
        ClearMF(pMenu,MFRTL);
    }

    for (cItem = 0; cItem < (int)pMenu->cItems; cItem++) {
        pItem = pMenu->rgItems + cItem;
        if (bRtoL) {
            SetMFT(pItem, MFT_RIGHTJUSTIFY);
            SetMFT(pItem, MFT_RIGHTORDER);
        } else {
            ClearMFT(pItem, MFT_RIGHTJUSTIFY);
            ClearMFT(pItem, MFT_RIGHTORDER);
        }

        if (pItem->spSubMenu) {
            MakeMenuRtoL(pItem->spSubMenu, bRtoL);
        }
    }

    return pMenu;
}

/***************************************************************************\
* xxxCalcMenuBar
*
* 3/8/2000  vadimg      created
\***************************************************************************/
UINT xxxCalcMenuBar(
    PWND pwnd,
    int iLeftOffset,
    int iRightOffset,
    int iTopOffset,
    LPCRECT prcWnd)
{
    PMENU pMenu;
    UINT cyMenu, cxMenuMax;
    TL tlpMenu;

    CheckLock(pwnd);

    pMenu = pwnd->spmenu;

    if (TestwndChild(pwnd) || pMenu == NULL) {
        return 0;
    }

    ThreadLockMenuNoModify(pMenu, &tlpMenu);

    cxMenuMax = (prcWnd->right - iRightOffset) - (prcWnd->left + iLeftOffset);

    xxxMenuBarCompute(pMenu, pwnd, iTopOffset, iLeftOffset, cxMenuMax);

    cyMenu = pMenu->cyMenu;
    ThreadUnlockMenuNoModify(&tlpMenu);

    return cyMenu;
}