This resulted in its base class wxNavigationEnabled<wxBookCtrlBase> being instantiated both in the application itself (when using it directly) and inside the wx DLLs (when using another class using the same base class, but defined in the DLL, such as wxAuiNotebook) and gave linking errors. Work around this by explicitly defining wxCompositeBookCtrlBase, corresponding to this base class, ourselves and export it from the DLL so that it's the single instance which is always used. Closes #22805.
566 lines
16 KiB
C++
566 lines
16 KiB
C++
///////////////////////////////////////////////////////////////////////////////
|
|
// Name: src/common/bookctrl.cpp
|
|
// Purpose: wxBookCtrlBase implementation
|
|
// Author: Vadim Zeitlin
|
|
// Modified by:
|
|
// Created: 19.08.03
|
|
// Copyright: (c) 2003 Vadim Zeitlin <vadim@wxwidgets.org>
|
|
// Licence: wxWindows licence
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// ============================================================================
|
|
// declarations
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// headers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// For compilers that support precompilation, includes "wx.h".
|
|
#include "wx/wxprec.h"
|
|
|
|
|
|
#if wxUSE_BOOKCTRL
|
|
|
|
#include "wx/compositebookctrl.h"
|
|
|
|
#include "wx/imaglist.h"
|
|
|
|
// ============================================================================
|
|
// implementation
|
|
// ============================================================================
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// event table
|
|
// ----------------------------------------------------------------------------
|
|
|
|
wxIMPLEMENT_ABSTRACT_CLASS(wxBookCtrlBase, wxControl);
|
|
|
|
wxBEGIN_EVENT_TABLE(wxBookCtrlBase, wxControl)
|
|
EVT_SIZE(wxBookCtrlBase::OnSize)
|
|
#if wxUSE_HELP
|
|
EVT_HELP(wxID_ANY, wxBookCtrlBase::OnHelp)
|
|
#endif // wxUSE_HELP
|
|
wxEND_EVENT_TABLE()
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// constructors and destructors
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxBookCtrlBase::Init()
|
|
{
|
|
m_selection = wxNOT_FOUND;
|
|
m_bookctrl = nullptr;
|
|
m_fitToCurrentPage = false;
|
|
|
|
m_internalBorder = 5;
|
|
|
|
m_controlMargin = 0;
|
|
m_controlSizer = nullptr;
|
|
|
|
Bind(wxEVT_DPI_CHANGED, &wxBookCtrlBase::WXHandleDPIChanged, this);
|
|
}
|
|
|
|
bool
|
|
wxBookCtrlBase::Create(wxWindow *parent,
|
|
wxWindowID id,
|
|
const wxPoint& pos,
|
|
const wxSize& size,
|
|
long style,
|
|
const wxString& name)
|
|
{
|
|
return wxControl::Create
|
|
(
|
|
parent,
|
|
id,
|
|
pos,
|
|
size,
|
|
style,
|
|
wxDefaultValidator,
|
|
name
|
|
);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// geometry
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void wxBookCtrlBase::DoInvalidateBestSize()
|
|
{
|
|
// notice that it is not necessary to invalidate our own best size
|
|
// explicitly if we have m_bookctrl as it will already invalidate the best
|
|
// size of its parent when its own size is invalidated and its parent is
|
|
// this control
|
|
if ( m_bookctrl )
|
|
m_bookctrl->InvalidateBestSize();
|
|
else
|
|
wxControl::InvalidateBestSize();
|
|
}
|
|
|
|
wxSize wxBookCtrlBase::CalcSizeFromPage(const wxSize& sizePage) const
|
|
{
|
|
// Add the size of the controller and the border between if it's shown.
|
|
if ( !m_bookctrl || !m_bookctrl->IsShown() )
|
|
return sizePage;
|
|
|
|
// Notice that the controller size is its current size while we really want
|
|
// to have its best size. So we only take into account its size in the
|
|
// direction in which we should add it but not in the other one, where the
|
|
// controller size is determined by the size of wxBookCtrl itself.
|
|
const wxSize sizeController = GetControllerSize();
|
|
|
|
wxSize size = sizePage;
|
|
if ( IsVertical() )
|
|
size.y += sizeController.y + GetInternalBorder();
|
|
else // left/right aligned
|
|
size.x += sizeController.x + GetInternalBorder();
|
|
|
|
return size;
|
|
}
|
|
|
|
void wxBookCtrlBase::SetPageSize(const wxSize& size)
|
|
{
|
|
SetClientSize(CalcSizeFromPage(size));
|
|
}
|
|
|
|
wxSize wxBookCtrlBase::DoGetBestSize() const
|
|
{
|
|
wxSize bestSize;
|
|
|
|
if (m_fitToCurrentPage && GetCurrentPage())
|
|
{
|
|
bestSize = GetCurrentPage()->GetBestSize();
|
|
}
|
|
else
|
|
{
|
|
// iterate over all pages, get the largest width and height
|
|
const size_t nCount = m_pages.size();
|
|
for ( size_t nPage = 0; nPage < nCount; nPage++ )
|
|
{
|
|
const wxWindow * const pPage = m_pages[nPage];
|
|
if ( pPage )
|
|
bestSize.IncTo(pPage->GetBestSize());
|
|
}
|
|
}
|
|
|
|
// convert display area to window area, adding the size necessary for the
|
|
// tabs
|
|
return CalcSizeFromPage(bestSize);
|
|
}
|
|
|
|
wxRect wxBookCtrlBase::GetPageRect() const
|
|
{
|
|
const wxSize size = GetControllerSize();
|
|
|
|
wxPoint pt;
|
|
wxRect rectPage(pt, GetClientSize());
|
|
|
|
switch ( GetWindowStyle() & wxBK_ALIGN_MASK )
|
|
{
|
|
default:
|
|
wxFAIL_MSG( wxT("unexpected alignment") );
|
|
wxFALLTHROUGH;
|
|
|
|
case wxBK_TOP:
|
|
rectPage.y = size.y + GetInternalBorder();
|
|
wxFALLTHROUGH;
|
|
|
|
case wxBK_BOTTOM:
|
|
rectPage.height -= size.y + GetInternalBorder();
|
|
if (rectPage.height < 0)
|
|
rectPage.height = 0;
|
|
break;
|
|
|
|
case wxBK_LEFT:
|
|
rectPage.x = size.x + GetInternalBorder();
|
|
wxFALLTHROUGH;
|
|
|
|
case wxBK_RIGHT:
|
|
rectPage.width -= size.x + GetInternalBorder();
|
|
if (rectPage.width < 0)
|
|
rectPage.width = 0;
|
|
break;
|
|
}
|
|
|
|
return rectPage;
|
|
}
|
|
|
|
// Lay out controls
|
|
void wxBookCtrlBase::DoSize()
|
|
{
|
|
if ( !m_bookctrl )
|
|
{
|
|
// we're not fully created yet or OnSize() should be hidden by derived class
|
|
return;
|
|
}
|
|
|
|
if (GetSizer())
|
|
Layout();
|
|
else
|
|
{
|
|
// resize controller and the page area to fit inside our new size
|
|
const wxSize sizeClient( GetClientSize() ),
|
|
sizeBorder( m_bookctrl->GetSize() - m_bookctrl->GetClientSize() ),
|
|
sizeCtrl( GetControllerSize() );
|
|
|
|
m_bookctrl->SetClientSize( sizeCtrl.x - sizeBorder.x, sizeCtrl.y - sizeBorder.y );
|
|
// if this changes the visibility of the scrollbars the best size changes, relayout in this case
|
|
wxSize sizeCtrl2 = GetControllerSize();
|
|
if ( sizeCtrl != sizeCtrl2 )
|
|
{
|
|
wxSize sizeBorder2 = m_bookctrl->GetSize() - m_bookctrl->GetClientSize();
|
|
m_bookctrl->SetClientSize( sizeCtrl2.x - sizeBorder2.x, sizeCtrl2.y - sizeBorder2.y );
|
|
}
|
|
|
|
const wxSize sizeNew = m_bookctrl->GetSize();
|
|
wxPoint posCtrl;
|
|
switch ( GetWindowStyle() & wxBK_ALIGN_MASK )
|
|
{
|
|
default:
|
|
wxFAIL_MSG( wxT("unexpected alignment") );
|
|
wxFALLTHROUGH;
|
|
|
|
case wxBK_TOP:
|
|
case wxBK_LEFT:
|
|
// posCtrl is already ok
|
|
break;
|
|
|
|
case wxBK_BOTTOM:
|
|
posCtrl.y = sizeClient.y - sizeNew.y;
|
|
break;
|
|
|
|
case wxBK_RIGHT:
|
|
posCtrl.x = sizeClient.x - sizeNew.x;
|
|
break;
|
|
}
|
|
|
|
if ( m_bookctrl->GetPosition() != posCtrl )
|
|
m_bookctrl->Move(posCtrl);
|
|
}
|
|
|
|
// resize all pages to fit the new control size
|
|
const wxRect pageRect = GetPageRect();
|
|
const size_t pagesCount = m_pages.size();
|
|
for ( size_t i = 0; i < pagesCount; ++i )
|
|
{
|
|
wxWindow * const page = m_pages[i];
|
|
if ( !page )
|
|
{
|
|
wxASSERT_MSG( AllowNullPage(),
|
|
wxT("Null page in a control that does not allow null pages?") );
|
|
continue;
|
|
}
|
|
|
|
page->SetSize(pageRect);
|
|
}
|
|
}
|
|
|
|
void wxBookCtrlBase::OnSize(wxSizeEvent& event)
|
|
{
|
|
event.Skip();
|
|
|
|
DoSize();
|
|
}
|
|
|
|
wxSize wxBookCtrlBase::GetControllerSize() const
|
|
{
|
|
// For at least some book controls (e.g. wxChoicebook) it may make sense to
|
|
// (temporarily?) hide the controller and we shouldn't leave extra space
|
|
// for the hidden control in this case.
|
|
if ( !m_bookctrl || !m_bookctrl->IsShown() )
|
|
return wxSize(0, 0);
|
|
|
|
const wxSize sizeClient = GetClientSize();
|
|
|
|
wxSize size;
|
|
|
|
// Ask for the best width/height considering the other direction.
|
|
if ( IsVertical() )
|
|
{
|
|
size.x = sizeClient.x;
|
|
size.y = m_bookctrl->GetBestHeight(sizeClient.x);
|
|
}
|
|
else // left/right aligned
|
|
{
|
|
size.x = m_bookctrl->GetBestWidth(sizeClient.y);
|
|
size.y = sizeClient.y;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// miscellaneous stuff
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#if wxUSE_HELP
|
|
|
|
void wxBookCtrlBase::OnHelp(wxHelpEvent& event)
|
|
{
|
|
// determine where does this even originate from to avoid redirecting it
|
|
// back to the page which generated it (resulting in an infinite loop)
|
|
|
|
wxWindow *source = wxStaticCast(event.GetEventObject(), wxWindow);
|
|
|
|
// In all ports but wxUniv it's sufficient to compare the event object with
|
|
// the book control itself to check if the event came to it directly, but
|
|
// in wxUniv we may have other controls inside it (e.g. wxSpinButton inside
|
|
// wxNotebook), so we need more involved checks there.
|
|
#ifdef __WXUNIVERSAL__
|
|
while ( source )
|
|
{
|
|
wxWindow* const parent = source->GetParent();
|
|
if ( parent == this )
|
|
{
|
|
if ( FindPage(source) != wxNOT_FOUND )
|
|
{
|
|
// The event comes from our own page, don't send it back to it.
|
|
source = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// Must be one of internal sub-controls such as the
|
|
// wxSpinButton mentioned above, consider the book control
|
|
// itself as the source of this event.
|
|
source = this;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
source = parent;
|
|
}
|
|
#endif // __WXUNIVERSAL__
|
|
|
|
if ( source == this )
|
|
{
|
|
// this event is for the book control itself, redirect it to the
|
|
// corresponding page
|
|
wxWindow *page = nullptr;
|
|
|
|
if ( event.GetOrigin() == wxHelpEvent::Origin_HelpButton )
|
|
{
|
|
// show help for the page under the mouse
|
|
const int pagePos = HitTest(ScreenToClient(event.GetPosition()));
|
|
|
|
if ( pagePos != wxNOT_FOUND)
|
|
{
|
|
page = GetPage((size_t)pagePos);
|
|
}
|
|
}
|
|
else // event from keyboard or unknown source
|
|
{
|
|
// otherwise show the current page help
|
|
page = GetCurrentPage();
|
|
}
|
|
|
|
if ( page )
|
|
{
|
|
// change event object to the page to avoid infinite recursion if
|
|
// we get this event ourselves if the page doesn't handle it
|
|
event.SetEventObject(page);
|
|
|
|
if ( page->GetEventHandler()->ProcessEvent(event) )
|
|
{
|
|
// don't call event.Skip()
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//else: event coming from one of our pages already
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
#endif // wxUSE_HELP
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// pages management
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool
|
|
wxBookCtrlBase::InsertPage(size_t nPage,
|
|
wxWindow *page,
|
|
const wxString& WXUNUSED(text),
|
|
bool WXUNUSED(bSelect),
|
|
int WXUNUSED(imageId))
|
|
{
|
|
wxCHECK_MSG( page || AllowNullPage(), false,
|
|
wxT("null page in wxBookCtrlBase::InsertPage()") );
|
|
wxCHECK_MSG( nPage <= m_pages.size(), false,
|
|
wxT("invalid page index in wxBookCtrlBase::InsertPage()") );
|
|
|
|
m_pages.insert(m_pages.begin() + nPage, page);
|
|
if ( page )
|
|
page->SetSize(GetPageRect());
|
|
|
|
DoInvalidateBestSize();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool wxBookCtrlBase::DeletePage(size_t nPage)
|
|
{
|
|
wxWindow *page = DoRemovePage(nPage);
|
|
if ( !(page || AllowNullPage()) )
|
|
return false;
|
|
|
|
// deleting null pointer is harmless
|
|
delete page;
|
|
|
|
return true;
|
|
}
|
|
|
|
wxWindow *wxBookCtrlBase::DoRemovePage(size_t nPage)
|
|
{
|
|
wxCHECK_MSG( nPage < m_pages.size(), nullptr,
|
|
wxT("invalid page index in wxBookCtrlBase::DoRemovePage()") );
|
|
|
|
wxWindow *pageRemoved = m_pages[nPage];
|
|
m_pages.erase(m_pages.begin() + nPage);
|
|
DoInvalidateBestSize();
|
|
|
|
return pageRemoved;
|
|
}
|
|
|
|
int wxBookCtrlBase::GetNextPage(bool forward) const
|
|
{
|
|
int nPage;
|
|
|
|
int nMax = GetPageCount();
|
|
if ( nMax-- ) // decrement it to get the last valid index
|
|
{
|
|
int nSel = GetSelection();
|
|
|
|
// change selection wrapping if it becomes invalid
|
|
nPage = forward ? nSel == nMax ? 0
|
|
: nSel + 1
|
|
: nSel == 0 ? nMax
|
|
: nSel - 1;
|
|
}
|
|
else // notebook is empty, no next page
|
|
{
|
|
nPage = wxNOT_FOUND;
|
|
}
|
|
|
|
return nPage;
|
|
}
|
|
|
|
int wxBookCtrlBase::FindPage(const wxWindow* page) const
|
|
{
|
|
const size_t nCount = m_pages.size();
|
|
for ( size_t nPage = 0; nPage < nCount; nPage++ )
|
|
{
|
|
if ( m_pages[nPage] == page )
|
|
return (int)nPage;
|
|
}
|
|
|
|
return wxNOT_FOUND;
|
|
}
|
|
|
|
bool wxBookCtrlBase::DoSetSelectionAfterInsertion(size_t n, bool bSelect)
|
|
{
|
|
if ( bSelect )
|
|
SetSelection(n);
|
|
else if ( m_selection == wxNOT_FOUND )
|
|
ChangeSelection(0);
|
|
else // We're not going to select this page.
|
|
return false;
|
|
|
|
// Return true to indicate that we selected this page.
|
|
return true;
|
|
}
|
|
|
|
void wxBookCtrlBase::DoSetSelectionAfterRemoval(size_t n)
|
|
{
|
|
if ( m_selection >= (int)n )
|
|
{
|
|
// ensure that the selection is valid
|
|
int sel;
|
|
if ( GetPageCount() == 0 )
|
|
sel = wxNOT_FOUND;
|
|
else
|
|
sel = m_selection ? m_selection - 1 : 0;
|
|
|
|
// if deleting current page we shouldn't try to hide it
|
|
m_selection = m_selection == (int)n ? wxNOT_FOUND
|
|
: m_selection - 1;
|
|
|
|
if ( sel != wxNOT_FOUND && sel != m_selection )
|
|
SetSelection(sel);
|
|
}
|
|
}
|
|
|
|
int wxBookCtrlBase::DoSetSelection(size_t n, int flags)
|
|
{
|
|
wxCHECK_MSG( n < GetPageCount(), wxNOT_FOUND,
|
|
wxT("invalid page index in wxBookCtrlBase::DoSetSelection()") );
|
|
|
|
const int oldSel = GetSelection();
|
|
|
|
if ( n != (size_t)oldSel )
|
|
{
|
|
wxBookCtrlEvent *event = CreatePageChangingEvent();
|
|
bool allowed = true;
|
|
|
|
if ( flags & SetSelection_SendEvent )
|
|
{
|
|
event->SetSelection(n);
|
|
event->SetOldSelection(oldSel);
|
|
event->SetEventObject(this);
|
|
|
|
allowed = !GetEventHandler()->ProcessEvent(*event) || event->IsAllowed();
|
|
}
|
|
|
|
if ( allowed )
|
|
{
|
|
if ( oldSel != wxNOT_FOUND )
|
|
{
|
|
if ( wxWindow* const oldPage = TryGetNonNullPage(oldSel) )
|
|
{
|
|
DoShowPage(oldPage, false);
|
|
}
|
|
}
|
|
|
|
if ( wxWindow* const page = TryGetNonNullPage(n) )
|
|
{
|
|
page->SetSize(GetPageRect());
|
|
DoShowPage(page, true);
|
|
}
|
|
|
|
// change selection now to ignore the selection change event
|
|
m_selection = n;
|
|
UpdateSelectedPage(n);
|
|
|
|
if ( flags & SetSelection_SendEvent )
|
|
{
|
|
// program allows the page change
|
|
MakeChangedEvent(*event);
|
|
(void)GetEventHandler()->ProcessEvent(*event);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Selection in the control might have already had changed.
|
|
if ( oldSel != wxNOT_FOUND )
|
|
{
|
|
m_selection = oldSel;
|
|
UpdateSelectedPage(oldSel);
|
|
}
|
|
}
|
|
|
|
delete event;
|
|
}
|
|
|
|
return oldSel;
|
|
}
|
|
|
|
wxIMPLEMENT_DYNAMIC_CLASS(wxBookCtrlEvent, wxNotifyEvent);
|
|
|
|
// Implement the trivial ctor here to ensure it's emitted here and exported
|
|
// from the DLL instead of having an inline version of it which may result in
|
|
// link errors if it happens to be instantiated both inside and outside of the
|
|
// DLL, see #22805.
|
|
wxCompositeBookCtrlBase::wxCompositeBookCtrlBase() = default;
|
|
|
|
#endif // wxUSE_BOOKCTRL
|