///////////////////////////////////////////////////////////////////////////////
// Name:        src/os2/notebook.cpp
// Purpose:     implementation of wxNotebook
// Author:      David Webster
// Modified by:
// Created:     10/12/99
// RCS-ID:      $Id: notebook.cpp 50855 2007-12-20 10:51:33Z JS $
// Copyright:   (c) David Webster
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

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

#if wxUSE_NOTEBOOK

#include  "wx/notebook.h"

// wxWidgets
#ifndef WX_PRECOMP
    #include "wx/app.h"
    #include "wx/dcclient.h"
    #include "wx/string.h"
    #include "wx/settings.h"
    #include "wx/log.h"
    #include "wx/event.h"
    #include  "wx/control.h"
#endif  // WX_PRECOMP

#include  "wx/imaglist.h"

#include  "wx/os2/private.h"

// ----------------------------------------------------------------------------
// macros
// ----------------------------------------------------------------------------

// check that the page index is valid
#define IS_VALID_PAGE(nPage) (                                \
                               /* size_t is _always_ >= 0 */  \
                               /* ((nPage) >= 0) && */        \
                               ((nPage) < GetPageCount())     \
                             )

// hide the ugly cast
#define m_hWnd    (HWND)GetHWND()

// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// event table
// ----------------------------------------------------------------------------

DEFINE_EVENT_TYPE(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED)
DEFINE_EVENT_TYPE(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING)

BEGIN_EVENT_TABLE(wxNotebook, wxBookCtrlBase)
    EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, wxNotebook::OnSelChange)
    EVT_SIZE(wxNotebook::OnSize)
    EVT_SET_FOCUS(wxNotebook::OnSetFocus)
    EVT_NAVIGATION_KEY(wxNotebook::OnNavigationKey)
END_EVENT_TABLE()

IMPLEMENT_DYNAMIC_CLASS(wxNotebook, wxBookCtrlBase)
IMPLEMENT_DYNAMIC_CLASS(wxNotebookEvent, wxNotifyEvent)

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

// ----------------------------------------------------------------------------
// wxNotebook construction
// ----------------------------------------------------------------------------

//
// Common part of all ctors
//
void wxNotebook::Init()
{
    m_imageList  = NULL;
    m_nSelection = -1;
    m_nTabSize   = 0;
} // end of wxNotebook::Init

//
// Default for dynamic class
//
wxNotebook::wxNotebook()
{
    Init();
} // end of wxNotebook::wxNotebook

//
// The same arguments as for wxControl
//
wxNotebook::wxNotebook(
  wxWindow*                         pParent
, wxWindowID                        vId
, const wxPoint&                    rPos
, const wxSize&                     rSize
, long                              lStyle
, const wxString&                   rsName
)
{
    Init();
    Create( pParent
           ,vId
           ,rPos
           ,rSize
           ,lStyle
           ,rsName
          );
} // end of wxNotebook::wxNotebook

//
// Create() function
//
bool wxNotebook::Create( wxWindow*       pParent,
                         wxWindowID      vId,
                         const wxPoint&  rPos,
                         const wxSize&   rSize,
                         long            lStyle,
                         const wxString& rsName )
{
    if ( (lStyle & wxBK_ALIGN_MASK) == wxBK_DEFAULT )
        lStyle |= wxBK_TOP;
    //
    // Base init
    //
    if (!CreateControl( pParent
                       ,vId
                       ,rPos
                       ,rSize
                       ,lStyle
                       ,wxDefaultValidator
                       ,rsName
                      ))
        return false;

    //
    // Notebook, so explicitly specify 0 as last parameter
    //
    if (!OS2CreateControl( wxT("NOTEBOOK")
                          ,wxEmptyString
                          ,rPos
                          ,rSize
                          ,lStyle | wxTAB_TRAVERSAL
                         ))
        return false;

    SetBackgroundColour(wxColour(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
    return true;
} // end of wxNotebook::Create

WXDWORD wxNotebook::OS2GetStyle (
  long                              lStyle
, WXDWORD*                          pdwExstyle
) const
{
    WXDWORD                         dwTabStyle = wxControl::OS2GetStyle( lStyle
                                                                        ,pdwExstyle
                                                                       );

    dwTabStyle |= WS_TABSTOP | BKS_SOLIDBIND | BKS_ROUNDEDTABS | BKS_TABTEXTCENTER | BKS_TABBEDDIALOG;

    if (lStyle & wxBK_BOTTOM)
        dwTabStyle |= BKS_MAJORTABBOTTOM | BKS_BACKPAGESBL;
    else if (lStyle & wxBK_RIGHT)
        dwTabStyle |= BKS_MAJORTABRIGHT | BKS_BACKPAGESBR;
    else if (lStyle & wxBK_LEFT)
        dwTabStyle |= BKS_MAJORTABLEFT | BKS_BACKPAGESTL;
    else // default to top
        dwTabStyle |= BKS_MAJORTABTOP | BKS_BACKPAGESTR;

    //
    // Ex style
    //
    if (pdwExstyle )
    {
        //
        // Note that we never want to have the default WS_EX_CLIENTEDGE style
        // as it looks too ugly for the notebooks
        //
        *pdwExstyle = 0;
    }
    return dwTabStyle;
} // end of wxNotebook::OS2GetStyle

// ----------------------------------------------------------------------------
// wxNotebook accessors
// ----------------------------------------------------------------------------

size_t wxNotebook::GetPageCount() const
{
    //
    // Consistency check
    //
    wxASSERT((int)m_pages.Count() == (int)::WinSendMsg(GetHWND(), BKM_QUERYPAGECOUNT, (MPARAM)0, (MPARAM)BKA_END));
    return m_pages.Count();
} // end of wxNotebook::GetPageCount

int wxNotebook::GetRowCount() const
{
    return (int)::WinSendMsg( GetHWND()
                             ,BKM_QUERYPAGECOUNT
                             ,(MPARAM)0
                             ,(MPARAM)BKA_MAJOR
                            );
} // end of wxNotebook::GetRowCount

int wxNotebook::SetSelection( size_t nPage )
{
    wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );

    if (nPage != (size_t)m_nSelection)
    {
        wxNotebookEvent             vEvent( wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING
                                           ,m_windowId
                                          );

        vEvent.SetSelection(nPage);
        vEvent.SetOldSelection(m_nSelection);
        vEvent.SetEventObject(this);
        if (!GetEventHandler()->ProcessEvent(vEvent) || vEvent.IsAllowed())
        {

            //
            // Program allows the page change
            //
            vEvent.SetEventType(wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED);
            GetEventHandler()->ProcessEvent(vEvent);

            ::WinSendMsg( GetHWND()
                         ,BKM_TURNTOPAGE
                         ,MPFROMLONG((ULONG)m_alPageId[nPage])
                         ,(MPARAM)0
                        );
        }
    }
    m_nSelection = nPage;
    return nPage;
} // end of wxNotebook::SetSelection

int wxNotebook::ChangeSelection( size_t nPage )
{
    wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );

    if (nPage != (size_t)m_nSelection)
    {
        ::WinSendMsg( GetHWND()
                ,BKM_TURNTOPAGE
                ,MPFROMLONG((ULONG)m_alPageId[nPage])
                        ,(MPARAM)0
                    );
    }
    m_nSelection = nPage;
    return nPage;
}

bool wxNotebook::SetPageText( size_t nPage,
                              const wxString& rsStrText )
{
    wxCHECK_MSG( IS_VALID_PAGE(nPage), false, wxT("notebook page out of range") );
    return (bool)::WinSendMsg( m_hWnd
                              ,BKM_SETTABTEXT
                              ,MPFROMLONG((ULONG)m_alPageId[nPage])
                              ,MPFROMP((PSZ)rsStrText.c_str())
                             );
} // end of wxNotebook::SetPageText

wxString wxNotebook::GetPageText ( size_t nPage ) const
{
    BOOKTEXT                        vBookText;
    wxChar                          zBuf[256];
    wxString                        sStr;
    ULONG                           ulRc;

    wxCHECK_MSG( IS_VALID_PAGE(nPage), wxEmptyString, wxT("notebook page out of range") );

    memset(&vBookText, '\0', sizeof(BOOKTEXT));
    vBookText.textLen = 0; // This will get the length
    ulRc = LONGFROMMR(::WinSendMsg( m_hWnd
                                   ,BKM_QUERYTABTEXT
                                   ,MPFROMLONG((ULONG)m_alPageId[nPage])
                                   ,MPFROMP(&vBookText)
                                  ));
    if (ulRc == (ULONG)BOOKERR_INVALID_PARAMETERS || ulRc == 0L)
    {
        if (ulRc == (ULONG)BOOKERR_INVALID_PARAMETERS)
        {
            wxLogError(wxT("Invalid Page Id for page text querry."));
        }
        return wxEmptyString;
    }
    vBookText.textLen = ulRc + 1; // To get the null terminator
    vBookText.pString = (char*)zBuf;

    //
    // Now get the actual text
    //
    ulRc = LONGFROMMR(::WinSendMsg( m_hWnd
                                   ,BKM_QUERYTABTEXT
                                   ,MPFROMLONG((ULONG)m_alPageId[nPage])
                                   ,MPFROMP(&vBookText)
                                  ));
    if (ulRc == (ULONG)BOOKERR_INVALID_PARAMETERS || ulRc == 0L)
    {
        return wxEmptyString;
    }
    if (ulRc > 255L)
        ulRc = 255L;

    vBookText.pString[ulRc] = '\0';
    sStr = (wxChar*)vBookText.pString;
    return sStr;
} // end of wxNotebook::GetPageText

int wxNotebook::GetPageImage ( size_t nPage ) const
{
    wxCHECK_MSG( IS_VALID_PAGE(nPage), wxNOT_FOUND, wxT("notebook page out of range") );

    //
    // For OS/2 just return the page
    //
    return nPage;
} // end of wxNotebook::GetPageImage

bool wxNotebook::SetPageImage (
  size_t                            nPage
, int                               nImage
)
{
    wxBitmap                        vBitmap = (wxBitmap)m_imageList->GetBitmap(nImage);

    return (bool)::WinSendMsg( GetHWND()
                              ,BKM_SETTABBITMAP
                              ,MPFROMLONG((ULONG)m_alPageId[nPage])
                              ,(MPARAM)wxCopyBmp(vBitmap.GetHBITMAP(), true)
                             );
} // end of wxNotebook::SetPageImage

void wxNotebook::SetImageList (
  wxImageList*                      pImageList
)
{
    //
    // Does not really do anything yet, but at least we need to
    // update the base class.
    //
    wxNotebookBase::SetImageList(pImageList);
} // end of wxNotebook::SetImageList

// ----------------------------------------------------------------------------
// wxNotebook size settings
// ----------------------------------------------------------------------------
void wxNotebook::SetPageSize (
  const wxSize&                     rSize
)
{
    SetSize(rSize);
} // end of wxNotebook::SetPageSize

void wxNotebook::SetPadding (
  const wxSize&                     WXUNUSED(rPadding)
)
{
    //
    // No padding in OS/2
    //
} // end of wxNotebook::SetPadding

void wxNotebook::SetTabSize (
  const wxSize&                     rSize
)
{
    ::WinSendMsg( GetHWND()
                 ,BKM_SETDIMENSIONS
                 ,MPFROM2SHORT( (USHORT)rSize.x
                               ,(USHORT)rSize.y
                              )
                 ,(MPARAM)BKA_MAJORTAB
                );
} // end of wxNotebook::SetTabSize

// ----------------------------------------------------------------------------
// wxNotebook operations
// ----------------------------------------------------------------------------

//
// Remove one page from the notebook, without deleting
//
wxNotebookPage* wxNotebook::DoRemovePage ( size_t nPage )
{
    wxNotebookPage* pPageRemoved = wxNotebookBase::DoRemovePage(nPage);

    if (!pPageRemoved)
        return NULL;

    ::WinSendMsg( GetHWND()
                 ,BKM_DELETEPAGE
                 ,MPFROMLONG((ULONG)m_alPageId[nPage])
                 ,(MPARAM)BKA_TAB
                );
    if (m_pages.IsEmpty())
    {
        //
        // No selection any more, the notebook becamse empty
        //
        m_nSelection = -1;
    }
    else // notebook still not empty
    {
        //
        // Change the selected page if it was deleted or became invalid
        //
        int                         nSelNew;

        if (m_nSelection == (int)GetPageCount())
        {
            //
            // Last page deleted, make the new last page the new selection
            //
            nSelNew = m_nSelection - 1;
        }
        else if (nPage <= (size_t)m_nSelection)
        {
            //
            // We must show another page, even if it has the same index
            //
            nSelNew = m_nSelection;
        }
        else // nothing changes for the currently selected page
        {
            nSelNew = -1;

            //
            // We still must refresh the current page: this needs to be done
            // for some unknown reason if the tab control shows the up-down
            // control (i.e. when there are too many pages) -- otherwise after
            // deleting a page nothing at all is shown
            //
            m_pages[m_nSelection]->Refresh();
        }

        if (nSelNew != -1)
        {
            //
            // m_nSelection must be always valid so reset it before calling
            // SetSelection()
            //
            m_nSelection = -1;
            SetSelection(nSelNew);
        }
    }
    return pPageRemoved;
} // end of wxNotebook::DoRemovePage

//
// Remove all pages
//
bool wxNotebook::DeleteAllPages()
{
    int                             nPageCount = GetPageCount();
    int                             nPage;

    for (nPage = 0; nPage < nPageCount; nPage++)
        delete m_pages[nPage];
    m_pages.Clear();
    ::WinSendMsg( GetHWND()
                 ,BKM_DELETEPAGE
                 ,(MPARAM)0
                 ,(MPARAM)BKA_ALL
                );
    m_nSelection = -1;

    return true;
} // end of wxNotebook::DeleteAllPages

//
// Add a page to the notebook
//
bool wxNotebook::AddPage (
  wxNotebookPage*                   pPage
, const wxString&                   rStrText
, bool                              bSelect
, int                               nImageId
)
{
    return InsertPage( GetPageCount()
                      ,pPage
                      ,rStrText
                      ,bSelect
                      ,nImageId
                     );
} // end of wxNotebook::AddPage

//
// Same as AddPage() but does it at given position
//
bool wxNotebook::InsertPage ( size_t          nPage,
                              wxNotebookPage* pPage,
                              const wxString& rsStrText,
                              bool            bSelect,
                              int             nImageId )
{
    ULONG                           ulApiPage;

    wxASSERT( pPage != NULL );
    wxCHECK( IS_VALID_PAGE(nPage) || nPage == GetPageCount(), false );

    //
    // Under OS/2 we can only insert FIRST, LAST, NEXT or PREV.  Requires
    // two different calls to the API.  Page 1 uses the BKA_FIRST.  Subsequent
    // pages use the previous page ID coupled with a BKA_NEXT call.  Unlike
    // Windows, OS/2 uses an internal Page ID to ID the pages.
    //
    // OS/2 also has a nice auto-size feature that automatically sizes the
    // the attached window so we don't have to worry about the size of the
    // window on the page.
    //
    if (nPage == 0)
    {
        ulApiPage = LONGFROMMR(::WinSendMsg( GetHWND()
                                            ,BKM_INSERTPAGE
                                            ,(MPARAM)0
                                            ,MPFROM2SHORT(BKA_AUTOPAGESIZE | BKA_MAJOR, BKA_FIRST)
                                           ));
        if (ulApiPage == 0L)
        {
            ERRORID                 vError;
            wxString                sError;

            vError = ::WinGetLastError(vHabmain);
            sError = wxPMErrorToStr(vError);
            return false;
        }
        m_alPageId.Insert((long)ulApiPage, nPage);
    }
    else
    {
        ulApiPage = LONGFROMMR(::WinSendMsg( GetHWND()
                                            ,BKM_INSERTPAGE
                                            ,MPFROMLONG((ULONG)m_alPageId[nPage - 1])
                                            ,MPFROM2SHORT(BKA_AUTOPAGESIZE | BKA_MAJOR, BKA_NEXT)
                                           ));
        if (ulApiPage == 0L)
        {
            ERRORID                     vError;
            wxString                    sError;

            vError = ::WinGetLastError(vHabmain);
            sError = wxPMErrorToStr(vError);
            return false;
        }
        m_alPageId.Insert((long)ulApiPage, nPage);
    }

    //
    // Associate a window handle with the page
    //
    if (pPage)
    {
        if (!::WinSendMsg( GetHWND()
                          ,BKM_SETPAGEWINDOWHWND
                          ,MPFROMLONG((ULONG)m_alPageId[nPage])
                          ,MPFROMHWND(pPage->GetHWND())
                         ))
            return false;
    }
    //
    // If the inserted page is before the selected one, we must update the
    // index of the selected page
    //
    if (nPage <= (size_t)m_nSelection)
    {
        //
        // One extra page added
        //
        m_nSelection++;
    }

    if (pPage)
    {
        //
        // Save the pointer to the page
        //
        m_pages.Insert( pPage
                       ,nPage
                      );
    }

    //
    // Now set TAB dimenstions
    //

    wxWindowDC vDC(this);
    wxCoord    nTextX;
    wxCoord    nTextY;

    vDC.GetTextExtent(rsStrText, &nTextX, &nTextY);
    nTextY *= 2;
    nTextX  = (wxCoord)(nTextX * 1.3);
    if (nTextX > m_nTabSize)
    {
        m_nTabSize = nTextX;
        ::WinSendMsg( GetHWND()
                     ,BKM_SETDIMENSIONS
                     ,MPFROM2SHORT((USHORT)m_nTabSize, (USHORT)nTextY)
                     ,(MPARAM)BKA_MAJORTAB
                    );
    }
    //
    // Now set any TAB text
    //
    if (!rsStrText.empty())
    {
        if (!SetPageText( nPage
                         ,rsStrText
                        ))
            return false;
    }

    //
    // Now set any TAB bitmap image
    //
    if (nImageId != -1)
    {
        if (!SetPageImage( nPage
                          ,nImageId
                         ))
            return false;
    }

    if (pPage)
    {
        //
        // Don't show pages by default (we'll need to adjust their size first)
        //
        HWND hWnd = GetWinHwnd(pPage);

        WinSetWindowULong( hWnd
                          ,QWL_STYLE
                          ,WinQueryWindowULong( hWnd
                                               ,QWL_STYLE
                                              ) & ~WS_VISIBLE
                         );

        //
        // This updates internal flag too - otherwise it will get out of sync
        //
        pPage->Show(false);
    }

    //
    // Some page should be selected: either this one or the first one if there is
    // still no selection
    //
    int nSelNew = -1;

    if (bSelect)
        nSelNew = nPage;
    else if ( m_nSelection == -1 )
        nSelNew = 0;

    if (nSelNew != -1)
        SetSelection(nSelNew);

    InvalidateBestSize();

    return true;
} // end of wxNotebook::InsertPage

// ----------------------------------------------------------------------------
// wxNotebook callbacks
// ----------------------------------------------------------------------------
void wxNotebook::OnSize(
  wxSizeEvent&                      rEvent
)
{
    rEvent.Skip();
} // end of wxNotebook::OnSize

void wxNotebook::OnSelChange (
  wxNotebookEvent&                  rEvent
)
{
    //
    // Is it our tab control?
    //
    if (rEvent.GetEventObject() == this)
    {
        int   nPageCount = GetPageCount();
        int   nSel;
        ULONG ulOS2Sel = (ULONG)rEvent.GetOldSelection();
        bool  bFound = false;

        for (nSel = 0; nSel < nPageCount; nSel++)
        {
            if (ulOS2Sel == (ULONG)m_alPageId[nSel])
            {
                bFound = true;
                break;
            }
        }

        if (!bFound)
            return;

        m_pages[nSel]->Show(false);

        ulOS2Sel = (ULONG)rEvent.GetSelection();

        bFound = false;

        for (nSel = 0; nSel < nPageCount; nSel++)
        {
            if (ulOS2Sel == (ULONG)m_alPageId[nSel])
            {
                bFound = true;
                break;
            }
        }

        if (!bFound)
            return;

        wxNotebookPage*         pPage = m_pages[nSel];

        pPage->Show(true);
        m_nSelection = nSel;
    }

    //
    // We want to give others a chance to process this message as well
    //
    rEvent.Skip();
} // end of wxNotebook::OnSelChange

void wxNotebook::OnSetFocus (
  wxFocusEvent&                     rEvent
)
{
    //
    // This function is only called when the focus is explicitly set (i.e. from
    // the program) to the notebook - in this case we don't need the
    // complicated OnNavigationKey() logic because the programmer knows better
    // what [s]he wants
    //
    // set focus to the currently selected page if any
    //
    if (m_nSelection != -1)
        m_pages[m_nSelection]->SetFocus();
    rEvent.Skip();
} // end of wxNotebook::OnSetFocus

void wxNotebook::OnNavigationKey (
  wxNavigationKeyEvent&             rEvent
)
{
    if (rEvent.IsWindowChange())
    {
        //
        // Change pages
        //
        AdvanceSelection(rEvent.GetDirection());
    }
    else
    {
        //
        // We get this event in 2 cases
        //
        // a) one of our pages might have generated it because the user TABbed
        // out from it in which case we should propagate the event upwards and
        // our parent will take care of setting the focus to prev/next sibling
        //
        // or
        //
        // b) the parent panel wants to give the focus to us so that we
        // forward it to our selected page. We can't deal with this in
        // OnSetFocus() because we don't know which direction the focus came
        // from in this case and so can't choose between setting the focus to
        // first or last panel child
        //
        wxWindow*                   pParent = GetParent();

        if (rEvent.GetEventObject() == pParent)
        {
            //
            // No, it doesn't come from child, case (b): forward to a page
            //
            if (m_nSelection != -1)
            {
                //
                // So that the page knows that the event comes from it's parent
                // and is being propagated downwards
                //
                rEvent.SetEventObject(this);

                wxWindow*           pPage = m_pages[m_nSelection];

                if (!pPage->GetEventHandler()->ProcessEvent(rEvent))
                {
                    pPage->SetFocus();
                }
                //else: page manages focus inside it itself
            }
            else
            {
                //
                // We have no pages - still have to give focus to _something_
                //
                SetFocus();
            }
        }
        else
        {
            //
            // It comes from our child, case (a), pass to the parent
            //
            if (pParent)
            {
                rEvent.SetCurrentFocus(this);
                pParent->GetEventHandler()->ProcessEvent(rEvent);
            }
        }
    }
} // end of wxNotebook::OnNavigationKey

// ----------------------------------------------------------------------------
// wxNotebook base class virtuals
// ----------------------------------------------------------------------------

//
// Override these 2 functions to do nothing: everything is done in OnSize
//
void wxNotebook::SetConstraintSizes( bool WXUNUSED(bRecurse) )
{
    //
    // Don't set the sizes of the pages - their correct size is not yet known
    //
    wxControl::SetConstraintSizes(false);
} // end of wxNotebook::SetConstraintSizes

bool wxNotebook::DoPhase ( int WXUNUSED(nPhase) )
{
    return true;
} // end of wxNotebook::DoPhase

// ----------------------------------------------------------------------------
// wxNotebook Windows message handlers
// ----------------------------------------------------------------------------
bool wxNotebook::OS2OnScroll ( int    nOrientation,
                               WXWORD wSBCode,
                               WXWORD wPos,
                               WXHWND wControl )
{
    //
    // Don't generate EVT_SCROLLWIN events for the WM_SCROLLs coming from the
    // up-down control
    //
    if (wControl)
        return false;
    return wxNotebookBase::OS2OnScroll( nOrientation
                                       ,wSBCode
                                       ,wPos
                                       ,wControl
                                      );
} // end of wxNotebook::OS2OnScroll

#endif // wxUSE_NOTEBOOK
