///////////////////////////////////////////////////////////////////////////////
// Name:        src/os2/menuitem.cpp
// Purpose:     wxMenuItem implementation
// Author:      David Webster
// Modified by:
// Created:     10/10/98
// Copyright:   (c) David Webster
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

// ============================================================================
// headers & declarations
// ============================================================================

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#include "wx/menuitem.h"
#include "wx/stockitem.h"

#ifndef WX_PRECOMP
    #include "wx/font.h"
    #include "wx/bitmap.h"
    #include "wx/settings.h"
    #include "wx/window.h"
    #include "wx/accel.h"
    #include "wx/menu.h"
    #include "wx/string.h"
    #include "wx/log.h"
#endif

#if wxUSE_ACCEL
    #include "wx/accel.h"
#endif // wxUSE_ACCEL

#include "wx/os2/private.h"

// ---------------------------------------------------------------------------
// macro
// ---------------------------------------------------------------------------

// hide the ugly cast
#define GetHMenuOf(menu)    ((HMENU)menu->GetHMenu())

// conditional compilation
#if wxUSE_OWNER_DRAWN
    #define OWNER_DRAWN_ONLY( code ) if ( IsOwnerDrawn() ) code
#else // !wxUSE_OWNER_DRAWN
    #define OWNER_DRAWN_ONLY( code )
#endif // wxUSE_OWNER_DRAWN/!wxUSE_OWNER_DRAWN

// ============================================================================
// implementation
// ============================================================================

// ----------------------------------------------------------------------------
// dynamic classes implementation
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// wxMenuItem
// ----------------------------------------------------------------------------

// ctor & dtor
// -----------

wxMenuItem::wxMenuItem(
  wxMenu*                           pParentMenu
, int                               nId
, const wxString&                   rsText
, const wxString&                   rsHelp
, wxItemKind                        eKind
, wxMenu*                           pSubMenu
)
: wxMenuItemBase( pParentMenu
                 ,nId
                 ,wxPMTextToLabel(rsText)
                 ,rsHelp
                 ,eKind
                 ,pSubMenu
                )
{
    wxASSERT_MSG(pParentMenu != NULL, wxT("a menu item should have a parent"));
    memset(&m_vMenuData, '\0', sizeof(m_vMenuData));
    m_vMenuData.id = (USHORT)nId;

    Init();
} // end of wxMenuItem::wxMenuItem

wxMenuItem::wxMenuItem(
  wxMenu*                           pParentMenu
, int                               nId
, const wxString&                   rsText
, const wxString&                   rsHelp
, bool                              bIsCheckable
, wxMenu*                           pSubMenu
)
: wxMenuItemBase( pParentMenu
                 ,nId
                 ,wxPMTextToLabel(rsText)
                 ,rsHelp
                 ,bIsCheckable ? wxITEM_CHECK : wxITEM_NORMAL
                 ,pSubMenu
                )
{
    wxASSERT_MSG(pParentMenu != NULL, wxT("a menu item should have a parent"));
    memset(&m_vMenuData, '\0', sizeof(m_vMenuData));
    m_vMenuData.id = (USHORT)nId;

    Init();
} // end of wxMenuItem::wxMenuItem

void wxMenuItem::Init()
{
    m_vRadioGroup.m_nStart = -1;
    m_bIsRadioGroupStart = FALSE;

#if  wxUSE_OWNER_DRAWN
    //
    // Set default menu colors
    //
    SetTextColour(wxNullColour);
    SetBackgroundColour(wxNullColour);

    //
    // We don't want normal items be owner-drawn
    //
    SetOwnerDrawn(false);
#endif // wxUSE_OWNER_DRAWN
} // end of wxMenuItem::Init

wxMenuItem::~wxMenuItem()
{
} // end of wxMenuItem::~wxMenuItem

//
// Misc
// ----

//
// Return the id for calling Win32 API functions
//
int wxMenuItem::GetRealId() const
{
    return m_subMenu ? (int)m_subMenu->GetHMenu() : GetId();
} // end of wxMenuItem::GetRealId

//
// Get item state
// --------------
bool wxMenuItem::IsChecked() const
{
    USHORT uFlag = SHORT1FROMMR(::WinSendMsg( GetHMenuOf(m_parentMenu)
                                             ,MM_QUERYITEMATTR
                                             ,MPFROM2SHORT(GetId(), TRUE)
                                             ,MPFROMSHORT(MIA_CHECKED)
                                            ));

    return (uFlag & MIA_CHECKED) == MIA_CHECKED ;
} // end of wxMenuItem::IsChecked

wxString wxMenuItemBase::GetLabelText(
  const wxString&                   rsText
)
{
    wxString                        sLabel;

    for (const wxChar* zPc = rsText.c_str(); *zPc; zPc++)
    {
        if (*zPc == wxT('~') || *zPc == wxT('&'))
        {
            //
            // '~' is the escape character for OS/2PM and '&' is the one for
            // wxWidgets - skip both of them
            //
            continue;
        }
        sLabel += *zPc;
    }
    return sLabel;
} // end of wxMenuItemBase::GetLabelText

//
// Radio group stuff
// -----------------
//
void wxMenuItem::SetAsRadioGroupStart()
{
    m_bIsRadioGroupStart = true;
} // end of wxMenuItem::SetAsRadioGroupStart

void wxMenuItem::SetRadioGroupStart(
  int                               nStart
)
{
    wxASSERT_MSG( !m_bIsRadioGroupStart
                 ,wxT("should only be called for the next radio items")
                );

    m_vRadioGroup.m_nStart = nStart;
} // wxMenuItem::SetRadioGroupStart

void wxMenuItem::SetRadioGroupEnd(
  int                               nEnd
)
{
    wxASSERT_MSG( m_bIsRadioGroupStart
                 ,wxT("should only be called for the first radio item")
                );
    m_vRadioGroup.m_nEnd = nEnd;
} // end of wxMenuItem::SetRadioGroupEnd

// change item state
// -----------------

void wxMenuItem::Enable(
  bool                              bEnable
)
{
    bool                            bOk;

    if (m_isEnabled == bEnable)
        return;
    if (bEnable)
        bOk = (bool)::WinSendMsg( GetHMenuOf(m_parentMenu)
                                 ,MM_SETITEMATTR
                                 ,MPFROM2SHORT(GetRealId(), TRUE)
                                 ,MPFROM2SHORT(MIA_DISABLED, FALSE)
                                );
    else
        bOk = (bool)::WinSendMsg( GetHMenuOf(m_parentMenu)
                                 ,MM_SETITEMATTR
                                 ,MPFROM2SHORT(GetRealId(), TRUE)
                                 ,MPFROM2SHORT(MIA_DISABLED, MIA_DISABLED)
                                );
    if (!bOk)
    {
        wxLogLastError(wxT("EnableMenuItem"));
    }
    wxMenuItemBase::Enable(bEnable);
} // end of wxMenuItem::Enable

void wxMenuItem::Check(
  bool                              bCheck
)
{
    bool                            bOk;

    wxCHECK_RET( IsCheckable(), wxT("only checkable items may be checked") );
    if (m_isChecked == bCheck)
        return;

    HMENU                           hMenu = GetHmenuOf(m_parentMenu);

    if (GetKind() == wxITEM_RADIO)
    {
        //
        // It doesn't make sense to uncheck a radio item - what would this do?
        //
        if (!bCheck)
            return;

        //
        // Get the index of this item in the menu
        //
        const wxMenuItemList&       rItems = m_parentMenu->GetMenuItems();
        int                         nPos = rItems.IndexOf(this);

        wxCHECK_RET( nPos != wxNOT_FOUND
                    ,wxT("menuitem not found in the menu items list?")
                   );

        //
        // Get the radio group range
        //
        int                         nStart;
        int                         nEnd;

        if (m_bIsRadioGroupStart)
        {
            //
            // We already have all information we need
            //
            nStart = nPos;
            nEnd   = m_vRadioGroup.m_nEnd;
        }
        else // next radio group item
        {
            //
            // Get the radio group end from the start item
            //
            nStart = m_vRadioGroup.m_nStart;
            nEnd = rItems.Item(nStart)->GetData()->m_vRadioGroup.m_nEnd;
        }

        //
        // Also uncheck all the other items in this radio group
        //
        wxMenuItemList::compatibility_iterator node = rItems.Item(nStart);

        for (int n = nStart; n <= nEnd && node; n++)
        {
            if (n == nPos)
            {
                ::WinSendMsg( hMenu
                             ,MM_SETITEMATTR
                             ,MPFROM2SHORT(n, TRUE)
                             ,MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED)
                            );
            }
            if (n != nPos)
            {
                node->GetData()->m_isChecked = FALSE;
                ::WinSendMsg( hMenu
                             ,MM_SETITEMATTR
                             ,MPFROM2SHORT(n, TRUE)
                             ,MPFROM2SHORT(MIA_CHECKED, FALSE)
                            );
            }
            node = node->GetNext();
        }
    }
    else // check item
    {
        if (bCheck)
            bOk = (bool)::WinSendMsg( hMenu
                                     ,MM_SETITEMATTR
                                     ,MPFROM2SHORT(GetRealId(), TRUE)
                                     ,MPFROM2SHORT(MIA_CHECKED, MIA_CHECKED)
                                    );
        else
            bOk = (bool)::WinSendMsg( hMenu
                                     ,MM_SETITEMATTR
                                     ,MPFROM2SHORT(GetRealId(), TRUE)
                                     ,MPFROM2SHORT(MIA_CHECKED, FALSE)
                                    );
    }
    if (!bOk)
    {
        wxLogLastError(wxT("CheckMenuItem"));
    }
    wxMenuItemBase::Check(bCheck);
} // end of wxMenuItem::Check

void wxMenuItem::SetItemLabel( const wxString& rText )
{
    //
    // Don't do anything if label didn't change
    //

    wxString                        sText = wxPMTextToLabel(rText);
    if (m_text == sText)
        return;

    // wxMenuItemBase will do stock ID checks
    wxMenuItemBase::SetItemLabel(sText);

    HWND hMenu = GetHmenuOf(m_parentMenu);

    wxCHECK_RET(hMenu, wxT("menuitem without menu"));

#if wxUSE_ACCEL
    m_parentMenu->UpdateAccel(this);
#endif // wxUSE_ACCEL

    USHORT   uId = (USHORT)GetRealId();
    MENUITEM vItem;
    USHORT   uFlagsOld;

    if (!::WinSendMsg( hMenu
                      ,MM_QUERYITEM
                      ,MPFROM2SHORT(uId, TRUE)
                      ,(MPARAM)&vItem
                     ))
    {
        wxLogLastError(wxT("GetMenuState"));
    }
    else
    {
        uFlagsOld = vItem.afStyle;
        if (IsSubMenu())
        {
            uFlagsOld |= MIS_SUBMENU;
        }

        char*                       pData;

#if wxUSE_OWNER_DRAWN
        if (IsOwnerDrawn())
        {
            uFlagsOld |= MIS_OWNERDRAW;
            pData = (char*)this;
        }
        else
#endif  //owner drawn
        {
            uFlagsOld |= MIS_TEXT;
            pData = (char*) m_text.wx_str();
        }

        //
        // Set the style
        //
        if (!::WinSendMsg( hMenu
                          ,MM_SETITEM
                          ,MPFROM2SHORT(uId, TRUE)
                          ,(MPARAM)&vItem
                         ))
        {
            wxLogLastError(wxT("ModifyMenu"));
        }

        //
        // Set the text
        //
        if (::WinSendMsg( hMenu
                         ,MM_SETITEMTEXT
                         ,MPFROMSHORT(uId)
                         ,(MPARAM)pData
                        ))
        {
            wxLogLastError(wxT("ModifyMenu"));
        }
    }
} // end of wxMenuItem::SetText

#if wxUSE_OWNER_DRAWN

wxString wxMenuItem::GetName() const
{
    return GetItemLabelText();
}

bool wxMenuItem::OnMeasureItem( size_t* pWidth, size_t* pHeight )
{
    wxMemoryDC vDC;

    wxString  sStr = GetName();

    //
    // If we have a valid accel string, then pad out
    // the menu string so that the menu and accel string are not
    // placed on top of each other.
    wxString accel = GetItemLabel().AfterFirst(wxT('\t'));
    if (!accel.empty() )
    {
        sStr.Pad(sStr.length()%8);
        sStr += accel;
    }
    vDC.SetFont(GetFont());
    vDC.GetTextExtent( sStr
                      ,(wxCoord *)pWidth
                      ,(wxCoord *)pHeight
                     );
    if (!accel.empty())
    {
        //
        // Measure the accelerator string, and add its width to
        // the total item width, plus 16 (Accelerators are right justified,
        // with the right edge of the text rectangle 16 pixels left of
        // the right edge of the menu)
        //
        int                         nAccelWidth;
        int                         nAccelHeight;

        vDC.GetTextExtent( m_strAccel
                          ,&nAccelWidth
                          ,&nAccelHeight
                         );
        *pWidth += nAccelWidth;
    }

    //
    // Add space at the end of the menu for the submenu expansion arrow.
    // This will also allow offsetting the accel string from the right edge
    //
    *pWidth = (size_t)(*pWidth + GetDefaultMarginWidth() * 1.5);

    //
    // JACS: items still look too tightly packed, so adding 5 pixels.
    //
    (*pHeight) += 5;

    //
    // Ray Gilbert's changes - Corrects the problem of a BMP
    // being placed next to text in a menu item, and the BMP does
    // not match the size expected by the system.  This will
    // resize the space so the BMP will fit.  Without this, BMPs
    // must be no larger or smaller than 16x16.
    //
    if (m_bmpChecked.IsOk())
    {
        //
        // Is BMP height larger than text height?
        //
        size_t                      nAdjustedHeight = m_bmpChecked.GetHeight() +
                                                      wxSystemSettings::GetMetric(wxSYS_EDGE_Y);
        if (*pHeight < nAdjustedHeight)
            *pHeight = nAdjustedHeight;

        //
        // Does BMP encroach on default check menu position?
        //
        size_t                      nAdjustedWidth = m_bmpChecked.GetWidth() +
                                                     (wxSystemSettings::GetMetric(wxSYS_EDGE_X) * 2);

        //
        // Do we need to widen margin to fit BMP?
        //
        if ((size_t)GetMarginWidth() < nAdjustedWidth)
            SetMarginWidth(nAdjustedWidth);

        //
        // Add the size of the bitmap to our total size...
        //
        *pWidth += GetMarginWidth();
    }

    //
    // Add the size of the bitmap to our total size - even if we don't have
    // a bitmap we leave room for one...
    //
    *pWidth += GetMarginWidth();

    //
    // Make sure that this item is at least as
    // tall as the user's system settings specify
    //
    const size_t heightStd = 6; // FIXME: get value from the system
    if ( *pHeight < heightStd )
      *pHeight = heightStd;
    m_nHeight = *pHeight;                // remember height for use in OnDrawItem
    return true;
} // end of wxOwnerDrawn::OnMeasureItem

bool wxMenuItem::OnDrawItem( wxDC& rDC,
                               const wxRect& rRect,
                               wxODAction eAction,
                               wxODStatus eStatus )
{

    //
    // Select the font and draw the text
    // ---------------------------------
    //

    CHARBUNDLE                      vCbnd;
    wxPMDCImpl                      *impl = (wxPMDCImpl*) rDC.GetImpl();
    HPS                             hPS= impl->GetHPS();
    wxFont                          vFont;
    wxColour                        vColBack;
    wxColour                        vColText;
    COLORREF                        vRef;
    RECTL                           vRect = {rRect.x + 4, rRect.y + 1, rRect.x + (rRect.width - 2), rRect.y + rRect.height};

    memset(&vCbnd, 0, sizeof(CHARBUNDLE));

    GetFontToUse(vFont);
    GetColourToUse(eStatus, vColText, vColBack);

    rDC.SetFont(vFont);
    rDC.SetTextBackground(vColBack);
    rDC.SetTextForeground(vColText);
    rDC.SetBackgroundMode(wxTRANSPARENT);

    vCbnd.lColor     = vColText.GetPixel();
    vCbnd.lBackColor = vColBack.GetPixel();
    ::GpiSetAttrs( hPS
                  ,PRIM_CHAR
                  ,CBB_BACK_COLOR | CBB_COLOR
                  ,0
                  ,&vCbnd
                 );
    ::GpiSetBackMix( hPS
                    ,BM_LEAVEALONE
                   );

    //
    // Paint the background
    //
    ::WinFillRect(hPS, &vRect, vColBack.GetPixel());

    //
    // Determine where to draw and leave space for a check-mark.
    //
    int nX = rRect.x + GetMarginWidth();

    //
    // Unfortunately, unlike Win32, PM has no owner drawn specific text
    // drawing methods like ::DrawState that can cleanly handle accel
    // mnemonics and deal, automatically, with various states, so we have
    // to handle them ourselves. Notice Win32 can't handle \t in ownerdrawn
    // strings either.  We cannot handle mnemonics either.  We display
    // them, though, in the hope we can figure them out some day.
    //

    //
    // Display main text and accel text separately to align better
    //
    wxString sTgt = wxT("\t");
    wxString sFullString = GetItemLabel(); // need to save the original text
    wxString sAccel;
    int      nIndex;
    size_t   nWidth;
    size_t   nCharWidth;
    size_t   nHeight;
    bool     bFoundMnemonic = false;
    bool     bFoundAccel = false;

    //
    // Deal with the tab, extracting the Accel text
    //
    nIndex = sFullString.Find(sTgt);
    if (nIndex != -1)
    {
        bFoundAccel = true;
        sAccel = sFullString.Mid(nIndex + 1);
        sFullString.Remove(nIndex);
    }

    //
    // Deal with the mnemonic character
    //
    sTgt = wxT("~");
    nIndex = sFullString.Find(sTgt);
    if (nIndex != -1)
    {
        wxString sTmp = sFullString;

        bFoundMnemonic = true;
        sTmp.Remove(nIndex);
        rDC.GetTextExtent( sTmp
                          ,(wxCoord *)&nWidth
                          ,(wxCoord *)&nHeight
                         );
        sTmp = sFullString[(size_t)(nIndex + 1)];
        rDC.GetTextExtent( sTmp
                          ,(wxCoord *)&nCharWidth
                          ,(wxCoord *)&nHeight
                         );
        sFullString.Replace(sTgt.c_str(), wxEmptyString, true);
    }

    //
    // Draw the main item text sans the accel text
    //
    POINTL                      vPntStart = {nX, rRect.y + 4};
    ::GpiCharStringAt( impl->GetHPS()
                      ,&vPntStart
                      ,sFullString.length()
                      ,sFullString.char_str()
                     );
    if (bFoundMnemonic)
    {
        //
        // Underline the mnemonic -- still won't work, but at least it "looks" right
        //
        wxPen                       vPen;
        POINTL                      vPntEnd = {nX + nWidth + nCharWidth - 3, rRect.y + 2}; //CharWidth is bit wide

        vPntStart.x = nX + nWidth - 1;
        vPntStart.y = rRect.y + 2; // Make it look pretty!
        vPen = wxPen(vColText, 1, wxSOLID); // Assuming we are always black
        rDC.SetPen(vPen);
        ::GpiMove(hPS, &vPntStart);
        ::GpiLine(hPS, &vPntEnd);
    }

    //
    // Now draw the accel text
    //
    if (bFoundAccel)
    {
        size_t                      nWidth;
        size_t                      nHeight;

        rDC.GetTextExtent( sAccel
                          ,(wxCoord *)&nWidth
                          ,(wxCoord *)&nHeight
                         );
        //
        // Back off the starting position from the right edge
        //
        vPntStart.x = rRect.width - (nWidth + 7);
        vPntStart.y = rRect.y + 4;
        ::GpiCharStringAt( impl->GetHPS()
                          ,&vPntStart
                          ,sAccel.length()
                          ,sAccel.char_str()
                         );
    }

    //
    // Draw the bitmap
    // ---------------
    //
    if (IsCheckable() && !m_bmpChecked.IsOk())
    {
        if (eStatus & wxODChecked)
        {
            RECTL                   vRect;
            HBITMAP                 hBmpCheck = ::WinGetSysBitmap(HWND_DESKTOP, SBMP_MENUCHECK);

            vRect.xLeft   = rRect.x;
            vRect.xRight  = rRect.x + GetMarginWidth();
            vRect.yBottom = rRect.y;
            vRect.yTop    = rRect.y + m_nHeight - 3;

            ::WinDrawBitmap( hPS             // PS for this menuitem
                            ,hBmpCheck       // system checkmark
                            ,NULL            // draw the whole bitmap
                            ,(PPOINTL)&vRect // destination -- bottom left corner of the menuitem area
                            ,0L              // ignored
                            ,0L              // draw a bitmap
                            ,DBM_NORMAL      // draw normal size
                           );
        }
    }
    else
    {
        //
        // For uncheckable item we use only the 'checked' bitmap
        //
        wxBitmap vBmp(GetBitmap(IsCheckable() ? ((eStatus & wxODChecked) != 0) : TRUE));

        if (vBmp.IsOk())
        {

            wxMemoryDC              vDCMem(&rDC);
            wxMemoryDC*             pOldDC = (wxMemoryDC*)vBmp.GetSelectedInto();

            if(pOldDC != NULL)
            {
                vBmp.SetSelectedInto(NULL);
            }
            vDCMem.SelectObject(vBmp);

            //
            // Center bitmap
            //
            int                     nBmpWidth = vBmp.GetWidth();
            int                     nBmpHeight = vBmp.GetHeight();

            //
            // There should be enough space!
            //
            wxASSERT((nBmpWidth <= rRect.width) && (nBmpHeight <= rRect.height));

            int                     nHeightDiff = m_nHeight - nBmpHeight;

            rDC.Blit( rRect.x + (GetMarginWidth() - nBmpWidth) / 2
                     ,rRect.y + nHeightDiff / 2
                     ,nBmpWidth
                     ,nBmpHeight
                     ,&vDCMem
                     ,0
                     ,0
                     ,wxCOPY
                     ,true
                    );

            if (eStatus & wxODSelected)
            {
                POINTL              vPnt1 = {rRect.x + 1, rRect.y + 3}; // Leave a little background border
                POINTL              vPnt2 = {rRect.x + GetMarginWidth(), rRect.y + m_nHeight - 3};

                LINEBUNDLE          vLine;

                vLine.lColor = vColBack.GetPixel();
                ::GpiSetAttrs( hPS
                              ,PRIM_LINE
                              ,LBB_COLOR
                              ,0
                              ,&vLine
                             );
                ::GpiMove(hPS, &vPnt1);
                ::GpiBox( hPS
                         ,DRO_OUTLINE
                         ,&vPnt2
                         ,0L
                         ,0L
                        );
            }
            vBmp.SetSelectedInto(NULL);
        }
    }
    return true;
} // end of wxOwnerDrawn::OnDrawItem

#endif // wxUSE_OWNER_DRAWN

// ----------------------------------------------------------------------------
// wxMenuItemBase
// ----------------------------------------------------------------------------

wxMenuItem* wxMenuItemBase::New(
  wxMenu*                           pParentMenu
, int                               nId
, const wxString&                   rName
, const wxString&                   rHelp
, wxItemKind                        kind
, wxMenu*                           pSubMenu
)
{
    return new wxMenuItem( pParentMenu
                          ,nId
                          ,rName
                          ,rHelp
                          ,kind
                          ,pSubMenu
                         );
} // end of wxMenuItemBase::New
