diff options
author | Caolán McNamara <caolanm@redhat.com> | 2019-08-05 17:26:53 +0100 |
---|---|---|
committer | Caolán McNamara <caolanm@redhat.com> | 2019-08-22 16:37:16 +0200 |
commit | 69057064d8957804c76e623d57c103c3413b7cbc (patch) | |
tree | 8026201f2750300a3b9f831a04dcc34016d54a95 /vcl/source/control | |
parent | e724f245e9652230d4c1f58c353be150006affcd (diff) |
weld ODbTypeWizDialogSetup
split up RoadmapWizard to sit its wizard logic on top of a a native GtkAssistant
a) awkwardly GtkAssistant is not a GtkDialog, but derives directly from
GtkWindow so some shuffling around required due to that
b) hidden/unused pages are shuffled to the end of the list of pages and
their titles turned off in order to hide them from the roadmap
c) some nonstandard hackery required to get the gtk roadmap titles to wrap
Change-Id: I0d2346c489fef744136a2785f33c846d97bd8dc6
Reviewed-on: https://gerrit.libreoffice.org/76876
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
Diffstat (limited to 'vcl/source/control')
-rw-r--r-- | vcl/source/control/button.cxx | 3 | ||||
-rw-r--r-- | vcl/source/control/roadmapwizard.cxx | 356 | ||||
-rw-r--r-- | vcl/source/control/wizardmachine.cxx | 598 |
3 files changed, 945 insertions, 12 deletions
diff --git a/vcl/source/control/button.cxx b/vcl/source/control/button.cxx index 8a076fadceb9..8669e655479f 100644 --- a/vcl/source/control/button.cxx +++ b/vcl/source/control/button.cxx @@ -145,6 +145,9 @@ OUString Button::GetStandardText(StandardButtonType eButton) SV_BUTTONTEXT_IGNORE, SV_BUTTONTEXT_ABORT, SV_BUTTONTEXT_LESS, + STR_WIZDLG_PREVIOUS, + STR_WIZDLG_NEXT, + STR_WIZDLG_FINISH, }; return VclResId(aResIdAry[static_cast<sal_uInt16>(eButton)]); diff --git a/vcl/source/control/roadmapwizard.cxx b/vcl/source/control/roadmapwizard.cxx index ec27bb6dae82..2d29a71aa47a 100644 --- a/vcl/source/control/roadmapwizard.cxx +++ b/vcl/source/control/roadmapwizard.cxx @@ -124,6 +124,13 @@ namespace vcl impl_construct(); } + RoadmapWizardMachine::RoadmapWizardMachine(weld::Window* pParent) + : WizardMachine(pParent, WizardButtonFlags::NEXT | WizardButtonFlags::PREVIOUS | WizardButtonFlags::FINISH | WizardButtonFlags::CANCEL | WizardButtonFlags::HELP) + , m_pImpl( new RoadmapWizardImpl ) + { + m_xAssistant->connect_jump_page(LINK(this, RoadmapWizardMachine, OnRoadmapItemSelected)); + } + void RoadmapWizard::impl_construct() { SetLeftAlignedButtonCount( 1 ); @@ -143,12 +150,15 @@ namespace vcl m_pImpl->pRoadmap->Show(); } - RoadmapWizard::~RoadmapWizard() { disposeOnce(); } + RoadmapWizardMachine::~RoadmapWizardMachine() + { + } + void RoadmapWizard::dispose() { m_pImpl.reset(); @@ -160,12 +170,15 @@ namespace vcl m_pImpl->pRoadmap->SetHelpId( _rId ); } - void RoadmapWizard::SetRoadmapInteractive( bool _bInteractive ) { m_pImpl->pRoadmap->SetRoadmapInteractive( _bInteractive ); } + void RoadmapWizardMachine::SetRoadmapInteractive( bool _bInteractive ) + { + m_pImpl->pRoadmap->SetRoadmapInteractive( _bInteractive ); + } void RoadmapWizard::declarePath( PathId _nPathId, const WizardPath& _lWizardStates) { @@ -179,6 +192,17 @@ namespace vcl implUpdateRoadmap( ); } + void RoadmapWizardMachine::declarePath( PathId _nPathId, const WizardPath& _lWizardStates) + { + + m_pImpl->aPaths.emplace( _nPathId, _lWizardStates ); + + if ( m_pImpl->aPaths.size() == 1 ) + // the very first path -> activate it + activatePath( _nPathId ); + else + implUpdateRoadmap( ); + } void RoadmapWizard::describeState( WizardState _nState, const OUString& _rStateDisplayName, RoadmapPageFactory _pPageFactory ) { @@ -187,7 +211,6 @@ namespace vcl m_pImpl->aStateDescriptors[ _nState ] = StateDescriptions::mapped_type( _rStateDisplayName, _pPageFactory ); } - void RoadmapWizard::activatePath( PathId _nPathId, bool _bDecideForIt ) { @@ -231,6 +254,47 @@ namespace vcl implUpdateRoadmap( ); } + void RoadmapWizardMachine::activatePath( PathId _nPathId, bool _bDecideForIt ) + { + if ( ( _nPathId == m_pImpl->nActivePath ) && ( _bDecideForIt == m_pImpl->bActivePathIsDefinite ) ) + // nothing to do + return; + + // does the given path exist? + Paths::const_iterator aNewPathPos = m_pImpl->aPaths.find( _nPathId ); + DBG_ASSERT( aNewPathPos != m_pImpl->aPaths.end(), "RoadmapWizard::activate: there is no such path!" ); + if ( aNewPathPos == m_pImpl->aPaths.end() ) + return; + + // determine the index of the current state in the current path + sal_Int32 nCurrentStatePathIndex = -1; + if ( m_pImpl->nActivePath != -1 ) + nCurrentStatePathIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath ); + + DBG_ASSERT( static_cast<sal_Int32>(aNewPathPos->second.size()) > nCurrentStatePathIndex, + "RoadmapWizard::activate: you cannot activate a path which has less states than we've already advanced!" ); + // If this asserts, this for instance means that we are already in state number, say, 5 + // of our current path, and the caller tries to activate a path which has less than 5 + // states + if ( static_cast<sal_Int32>(aNewPathPos->second.size()) <= nCurrentStatePathIndex ) + return; + + // assert that the current and the new path are equal, up to nCurrentStatePathIndex + Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath ); + if ( aActivePathPos != m_pImpl->aPaths.end() ) + { + if ( RoadmapWizardImpl::getFirstDifferentIndex( aActivePathPos->second, aNewPathPos->second ) <= nCurrentStatePathIndex ) + { + OSL_FAIL( "RoadmapWizard::activate: you cannot activate a path which conflicts with the current one *before* the current state!" ); + return; + } + } + + m_pImpl->nActivePath = _nPathId; + m_pImpl->bActivePathIsDefinite = _bDecideForIt; + + implUpdateRoadmap( ); + } void RoadmapWizard::implUpdateRoadmap( ) { @@ -337,6 +401,110 @@ namespace vcl m_pImpl->pRoadmap->SetRoadmapComplete( !bIncompletePath ); } + void RoadmapWizardMachine::implUpdateRoadmap( ) + { + + DBG_ASSERT( m_pImpl->aPaths.find( m_pImpl->nActivePath ) != m_pImpl->aPaths.end(), + "RoadmapWizard::implUpdateRoadmap: there is no such path!" ); + const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] ); + + sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath ); + if (nCurrentStatePathIndex < 0) + return; + + // determine up to which index (in the new path) we have to display the items + RoadmapTypes::ItemIndex nUpperStepBoundary = static_cast<RoadmapTypes::ItemIndex>(rActivePath.size()); + if ( !m_pImpl->bActivePathIsDefinite ) + { + for (auto const& path : m_pImpl->aPaths) + { + if ( path.first == m_pImpl->nActivePath ) + // it's the path we are just activating -> no need to check anything + continue; + // the index from which on both paths differ + sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second ); + if ( nDivergenceIndex <= nCurrentStatePathIndex ) + // they differ in an index which we have already left behind us + // -> this is no conflict anymore + continue; + + // the path conflicts with our new path -> don't activate the + // *complete* new path, but only up to the step which is unambiguous + nUpperStepBoundary = nDivergenceIndex; + } + } + + // can we advance from the current page? + bool bCurrentPageCanAdvance = true; + TabPage* pCurrentPage = GetPage( getCurrentState() ); + if ( pCurrentPage ) + { + const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) ); + OSL_ENSURE( pController != nullptr, "RoadmapWizard::implUpdateRoadmap: no controller for the current page!" ); + bCurrentPageCanAdvance = !pController || pController->canAdvance(); + } + + // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active + // path, up to (excluding) nUpperStepBoundary + RoadmapTypes::ItemIndex nRoadmapItems = m_xAssistant->get_n_pages(); + RoadmapTypes::ItemIndex nLoopUntil = ::std::max( nUpperStepBoundary, nRoadmapItems ); + for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex ) + { + bool bExistentItem = ( nItemIndex < nRoadmapItems ); + bool bNeedItem = ( nItemIndex < nUpperStepBoundary ); + + bool bInsertItem = false; + if ( bExistentItem ) + { + if ( !bNeedItem ) + { + int nPages = nRoadmapItems; + for (int i = nPages - 1; i >= nItemIndex; --i) + { + m_xAssistant->set_page_title(m_xAssistant->get_page_ident(i), ""); + --nRoadmapItems; + } + break; + } + else + { + // there is an item with this index in the roadmap - does it match what is requested by + // the respective state in the active path? + RoadmapTypes::ItemId nPresentItemId = m_xAssistant->get_page_ident(nItemIndex).toInt32(); + WizardState nRequiredState = rActivePath[ nItemIndex ]; + if ( nPresentItemId != nRequiredState ) + { + m_xAssistant->set_page_title(OString::number(nPresentItemId), ""); + bInsertItem = true; + } + } + } + else + { + DBG_ASSERT( bNeedItem, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" ); + bInsertItem = bNeedItem; + } + + WizardState nState( rActivePath[ nItemIndex ] ); + + if ( bInsertItem ) + { + GetOrCreatePage(nState); + } + + OString sIdent(OString::number(nState)); + m_xAssistant->set_page_index(sIdent, nItemIndex); + m_xAssistant->set_page_title(sIdent, getStateDisplayName(nState)); + + // if the item is *after* the current state, but the current page does not + // allow advancing, the disable the state. This relieves derived classes + // from disabling all future states just because the current state does not + // (yet) allow advancing. + const bool bUnconditionedDisable = !bCurrentPageCanAdvance && ( nItemIndex > nCurrentStatePathIndex ); + const bool bEnable = !bUnconditionedDisable && ( m_pImpl->aDisabledStates.find( nState ) == m_pImpl->aDisabledStates.end() ); + m_xAssistant->set_page_sensitive(sIdent, bEnable); + } + } WizardTypes::WizardState RoadmapWizard::determineNextState( WizardState _nCurrentState ) const { @@ -367,6 +535,33 @@ namespace vcl return aActivePathPos->second[ nNextStateIndex ]; } + WizardTypes::WizardState RoadmapWizardMachine::determineNextState( WizardState _nCurrentState ) const + { + sal_Int32 nCurrentStatePathIndex = -1; + + Paths::const_iterator aActivePathPos = m_pImpl->aPaths.find( m_pImpl->nActivePath ); + if ( aActivePathPos != m_pImpl->aPaths.end() ) + nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( _nCurrentState, aActivePathPos->second ); + + DBG_ASSERT( nCurrentStatePathIndex != -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" ); + if ( nCurrentStatePathIndex == -1 ) + return WZS_INVALID_STATE; + + sal_Int32 nNextStateIndex = nCurrentStatePathIndex + 1; + + while ( ( nNextStateIndex < static_cast<sal_Int32>(aActivePathPos->second.size()) ) + && ( m_pImpl->aDisabledStates.find( aActivePathPos->second[ nNextStateIndex ] ) != m_pImpl->aDisabledStates.end() ) + ) + { + ++nNextStateIndex; + } + + if ( nNextStateIndex >= static_cast<sal_Int32>(aActivePathPos->second.size()) ) + // there is no next state in the current path (at least none which is enabled) + return WZS_INVALID_STATE; + + return aActivePathPos->second[ nNextStateIndex ]; + } bool RoadmapWizard::canAdvance() const { @@ -398,6 +593,35 @@ namespace vcl return *rPath.rbegin() != getCurrentState(); } + bool RoadmapWizardMachine::canAdvance() const + { + if ( !m_pImpl->bActivePathIsDefinite ) + { + // check how many paths are still allowed + const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] ); + sal_Int32 nCurrentStatePathIndex = RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath ); + + size_t nPossiblePaths(0); + for (auto const& path : m_pImpl->aPaths) + { + // the index from which on both paths differ + sal_Int32 nDivergenceIndex = RoadmapWizardImpl::getFirstDifferentIndex( rActivePath, path.second ); + + if ( nDivergenceIndex > nCurrentStatePathIndex ) + // this path is still a possible path + nPossiblePaths += 1; + } + + // if we have more than one path which is still possible, then we assume + // to always have a next state. Though there might be scenarios where this + // is not true, but this is too sophisticated (means not really needed) right now. + if ( nPossiblePaths > 1 ) + return true; + } + + const WizardPath& rPath = m_pImpl->aPaths[ m_pImpl->nActivePath ]; + return *rPath.rbegin() != getCurrentState(); + } void RoadmapWizard::updateTravelUI() { @@ -421,6 +645,27 @@ namespace vcl implUpdateRoadmap(); } + void RoadmapWizardMachine::updateTravelUI() + { + WizardMachine::updateTravelUI(); + + // disable the "Previous" button if all states in our history are disabled + ::std::vector< WizardState > aHistory; + getStateHistory( aHistory ); + bool bHaveEnabledState = false; + for (auto const& state : aHistory) + { + if ( isStateEnabled(state) ) + { + bHaveEnabledState = true; + break; + } + } + + enableButtons( WizardButtonFlags::PREVIOUS, bHaveEnabledState ); + + implUpdateRoadmap(); + } IMPL_LINK_NOARG(RoadmapWizard, OnRoadmapItemSelected, LinkParamNone*, void) { @@ -463,10 +708,48 @@ namespace vcl m_pImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() ); } + IMPL_LINK(RoadmapWizardMachine, OnRoadmapItemSelected, const OString&, rCurItemId, bool) + { + int nCurItemId = rCurItemId.toInt32(); + + if ( nCurItemId == getCurrentState() ) + // nothing to do + return false; + + if ( isTravelingSuspended() ) + return false; + + WizardTravelSuspension aTravelGuard( *this ); + + sal_Int32 nCurrentIndex = m_pImpl->getStateIndexInPath( getCurrentState(), m_pImpl->nActivePath ); + sal_Int32 nNewIndex = m_pImpl->getStateIndexInPath( nCurItemId, m_pImpl->nActivePath ); + + DBG_ASSERT( ( nCurrentIndex != -1 ) && ( nNewIndex != -1 ), + "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" ); + if ( ( nCurrentIndex == -1 ) || ( nNewIndex == -1 ) ) + { + return false; + } + + bool bResult = true; + if ( nNewIndex > nCurrentIndex ) + { + bResult = skipUntil( static_cast<WizardState>(nCurItemId) ); + WizardState nTemp = static_cast<WizardState>(nCurItemId); + while( nTemp ) + { + if( m_pImpl->aDisabledStates.find( --nTemp ) != m_pImpl->aDisabledStates.end() ) + removePageFromHistory( nTemp ); + } + } + else + bResult = skipBackwardUntil( static_cast<WizardState>(nCurItemId) ); + + return bResult; + } void RoadmapWizard::enterState( WizardState _nState ) { - OWizardMachine::enterState( _nState ); // synchronize the roadmap @@ -474,6 +757,13 @@ namespace vcl m_pImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() ); } + void RoadmapWizardMachine::enterState( WizardState _nState ) + { + WizardMachine::enterState( _nState ); + + // synchronize the roadmap + implUpdateRoadmap(); + } OUString RoadmapWizard::getStateDisplayName( WizardState _nState ) const { @@ -488,6 +778,18 @@ namespace vcl return sDisplayName; } + OUString RoadmapWizardMachine::getStateDisplayName( WizardState _nState ) const + { + OUString sDisplayName; + + StateDescriptions::const_iterator pos = m_pImpl->aStateDescriptors.find( _nState ); + OSL_ENSURE( pos != m_pImpl->aStateDescriptors.end(), + "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" ); + if ( pos != m_pImpl->aStateDescriptors.end() ) + sDisplayName = pos->second.first; + + return sDisplayName; + } VclPtr<TabPage> RoadmapWizard::createPage( WizardState _nState ) { @@ -505,7 +807,6 @@ namespace vcl return pPage; } - void RoadmapWizard::enableState( WizardState _nState, bool _bEnable ) { @@ -522,6 +823,20 @@ namespace vcl m_pImpl->pRoadmap->EnableRoadmapItem( static_cast<RoadmapTypes::ItemId>(_nState), _bEnable ); } + void RoadmapWizardMachine::enableState( WizardState _nState, bool _bEnable ) + { + // remember this (in case the state appears in the roadmap later on) + if ( _bEnable ) + m_pImpl->aDisabledStates.erase( _nState ); + else + { + m_pImpl->aDisabledStates.insert( _nState ); + removePageFromHistory( _nState ); + } + + // if the state is currently in the roadmap, reflect it's new status + m_xAssistant->set_page_sensitive(OString::number(_nState), _bEnable); + } bool RoadmapWizard::knowsState( WizardState i_nState ) const { @@ -541,6 +856,11 @@ namespace vcl return m_pImpl->aDisabledStates.find( _nState ) == m_pImpl->aDisabledStates.end(); } + bool RoadmapWizardMachine::isStateEnabled( WizardState _nState ) const + { + return m_pImpl->aDisabledStates.find( _nState ) == m_pImpl->aDisabledStates.end(); + } + void RoadmapWizard::updateRoadmapItemLabel( WizardState _nState ) { const WizardPath& rActivePath( m_pImpl->aPaths[ m_pImpl->nActivePath ] ); @@ -569,6 +889,32 @@ namespace vcl } } + void RoadmapWizard::InsertRoadmapItem(int nItemIndex, const OUString& rText, int nItemId, bool bEnable) + { + m_pImpl->pRoadmap->InsertRoadmapItem(nItemIndex, rText, nItemId, bEnable); + } + + void RoadmapWizard::SelectRoadmapItemByID(int nItemId) + { + m_pImpl->pRoadmap->SelectRoadmapItemByID(nItemId); + } + + void RoadmapWizard::DeleteRoadmapItems() + { + while (m_pImpl->pRoadmap->GetItemCount()) + m_pImpl->pRoadmap->DeleteRoadmapItem(0); + } + + void RoadmapWizard::SetItemSelectHdl( const Link<LinkParamNone*,void>& _rHdl ) + { + m_pImpl->pRoadmap->SetItemSelectHdl(_rHdl); + } + + int RoadmapWizard::GetCurrentRoadmapItemID() const + { + return m_pImpl->pRoadmap->GetCurrentRoadmapItemID(); + } + } // namespace vcl /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/control/wizardmachine.cxx b/vcl/source/control/wizardmachine.cxx index f42c509bff37..8a1a92a707f2 100644 --- a/vcl/source/control/wizardmachine.cxx +++ b/vcl/source/control/wizardmachine.cxx @@ -18,7 +18,6 @@ */ #include <vcl/wizardmachine.hxx> -#include <svtools/helpids.h> #include <tools/debug.hxx> #include <tools/diagnose_ex.h> #include <vcl/svapp.hxx> @@ -26,6 +25,15 @@ #include <svdata.hxx> #include <stack> +#define HID_WIZARD_NEXT "SVT_HID_WIZARD_NEXT" +#define HID_WIZARD_PREVIOUS "SVT_HID_WIZARD_PREVIOUS" + +struct ImplWizPageData +{ + ImplWizPageData* mpNext; + VclPtr<TabPage> mpPage; +}; + namespace vcl { //= WizardPageImplData @@ -360,14 +368,13 @@ namespace vcl m_pCancel->Enable(_bEnable); } - void OWizardMachine::enterState(WizardState _nState) { // tell the page IWizardPageController* pController = getPageController( GetPage( _nState ) ); - OSL_ENSURE( pController, "OWizardMachine::enterState: no controller for the given page!" ); - if ( pController ) - pController->initializePage(); + if (!pController) + return; + pController->initializePage(); if ( isAutomaticNextButtonStateEnabled() ) enableButtons( WizardButtonFlags::NEXT, canAdvance() ); @@ -378,7 +385,6 @@ namespace vcl implUpdateTitle(); } - bool OWizardMachine::leaveState(WizardState) { // no need to ask the page here. @@ -522,7 +528,6 @@ namespace vcl // all fine } - bool OWizardMachine::travelNext() { // allowed to leave the current page? @@ -679,7 +684,586 @@ namespace vcl m_pImpl->m_bTravelingSuspended = false; } + WizardMachine::WizardMachine(weld::Window* pParent, WizardButtonFlags nButtonFlags) + : AssistantController(pParent, "dbaccess/ui/databasewizard.ui", "DatabaseWizard") + , m_nCurState(0) + , m_pFirstPage(nullptr) + , m_xFinish(m_xAssistant->weld_widget_for_response(static_cast<int>(WizardButtonFlags::FINISH))) + , m_xCancel(m_xAssistant->weld_widget_for_response(static_cast<int>(WizardButtonFlags::CANCEL))) + , m_xNextPage(m_xAssistant->weld_widget_for_response(static_cast<int>(WizardButtonFlags::NEXT))) + , m_xPrevPage(m_xAssistant->weld_widget_for_response(static_cast<int>(WizardButtonFlags::PREVIOUS))) + , m_xHelp(m_xAssistant->weld_widget_for_response(static_cast<int>(WizardButtonFlags::HELP))) + , m_pImpl(new WizardMachineImplData) + { + implConstruct(nButtonFlags); + } + + void WizardMachine::implConstruct(const WizardButtonFlags nButtonFlags) + { + m_pImpl->sTitleBase = m_xAssistant->get_title(); + + // create the buttons according to the wizard button flags + // the help button + if (nButtonFlags & WizardButtonFlags::HELP) + { + m_xHelp->show(); + } + + // the previous button + if (nButtonFlags & WizardButtonFlags::PREVIOUS) + { + m_xPrevPage->set_help_id( HID_WIZARD_PREVIOUS ); + m_xPrevPage->show(); + + m_xPrevPage->connect_clicked( LINK( this, WizardMachine, OnPrevPage ) ); + } + + // the next button + if (nButtonFlags & WizardButtonFlags::NEXT) + { + m_xNextPage->set_help_id( HID_WIZARD_NEXT ); + m_xNextPage->show(); + + m_xNextPage->connect_clicked( LINK( this, WizardMachine, OnNextPage ) ); + } + + // the finish button + if (nButtonFlags & WizardButtonFlags::FINISH) + { + m_xFinish->show(); + + m_xFinish->connect_clicked( LINK( this, WizardMachine, OnFinish ) ); + } + + // the cancel button + if (nButtonFlags & WizardButtonFlags::CANCEL) + { + m_xCancel->show(); + m_xCancel->connect_clicked( LINK( this, WizardMachine, OnCancel ) ); + } + } + + WizardMachine::~WizardMachine() + { + if (m_pImpl) + { + for (WizardState i = 0; i < m_pImpl->nFirstUnknownPage; ++i) + { + TabPage *pPage = GetPage(i); + if (pPage) + pPage->disposeOnce(); + } + m_pImpl.reset(); + } + } + + void WizardMachine::implUpdateTitle() + { + OUString sCompleteTitle(m_pImpl->sTitleBase); + + // append the page title + TabPage* pCurrentPage = GetPage(getCurrentState()); + if ( pCurrentPage && !pCurrentPage->GetText().isEmpty() ) + { + sCompleteTitle += " - " + pCurrentPage->GetText(); + } + + m_xAssistant->set_title(sCompleteTitle); + } + + void WizardMachine::setTitleBase(const OUString& _rTitleBase) + { + m_pImpl->sTitleBase = _rTitleBase; + implUpdateTitle(); + } + + TabPage* WizardMachine::GetOrCreatePage( const WizardState i_nState ) + { + if ( nullptr == GetPage( i_nState ) ) + { + VclPtr<TabPage> pNewPage = createPage( i_nState ); + DBG_ASSERT( pNewPage, "WizardMachine::GetOrCreatePage: invalid new page (NULL)!" ); + + // fill up the page sequence of our base class (with dummies) + while ( m_pImpl->nFirstUnknownPage < i_nState ) + { + AddPage( nullptr ); + ++m_pImpl->nFirstUnknownPage; + } + + if ( m_pImpl->nFirstUnknownPage == i_nState ) + { + // encountered this page number the first time + AddPage( pNewPage ); + ++m_pImpl->nFirstUnknownPage; + } + else + // already had this page - just change it + SetPage( i_nState, pNewPage ); + } + return GetPage( i_nState ); + } + + void WizardMachine::ActivatePage() + { + WizardState nCurrentLevel = m_nCurState; + GetOrCreatePage( nCurrentLevel ); + + enterState( nCurrentLevel ); + } + + bool WizardMachine::DeactivatePage() + { + WizardState nCurrentState = getCurrentState(); + return leaveState(nCurrentState); + } + + void WizardMachine::defaultButton(WizardButtonFlags _nWizardButtonFlags) + { + // the new default button + weld::Button* pNewDefButton = nullptr; + if (_nWizardButtonFlags & WizardButtonFlags::FINISH) + pNewDefButton = m_xFinish.get(); + if (_nWizardButtonFlags & WizardButtonFlags::NEXT) + pNewDefButton = m_xNextPage.get(); + if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS) + pNewDefButton = m_xPrevPage.get(); + if (_nWizardButtonFlags & WizardButtonFlags::HELP) + pNewDefButton = m_xHelp.get(); + if (_nWizardButtonFlags & WizardButtonFlags::CANCEL) + pNewDefButton = m_xCancel.get(); + + if ( pNewDefButton ) + defaultButton( pNewDefButton ); + else + m_xAssistant->recursively_unset_default_buttons(); + } + void WizardMachine::defaultButton(weld::Button* _pNewDefButton) + { + // loop through all (direct and indirect) descendants which participate in our tabbing order, and + // reset the WB_DEFBUTTON for every window which is a button + m_xAssistant->recursively_unset_default_buttons(); + + // set its new style + if (_pNewDefButton) + _pNewDefButton->set_has_default(true); + } + + void WizardMachine::enableButtons(WizardButtonFlags _nWizardButtonFlags, bool _bEnable) + { + if (_nWizardButtonFlags & WizardButtonFlags::FINISH) + m_xFinish->set_sensitive(_bEnable); + if (_nWizardButtonFlags & WizardButtonFlags::NEXT) + m_xNextPage->set_sensitive(_bEnable); + if (_nWizardButtonFlags & WizardButtonFlags::PREVIOUS) + m_xPrevPage->set_sensitive(_bEnable); + if (_nWizardButtonFlags & WizardButtonFlags::HELP) + m_xHelp->set_sensitive(_bEnable); + if (_nWizardButtonFlags & WizardButtonFlags::CANCEL) + m_xCancel->set_sensitive(_bEnable); + } + + void WizardMachine::enterState(WizardState _nState) + { + // tell the page + IWizardPageController* pController = getPageController( GetPage( _nState ) ); + OSL_ENSURE( pController, "WizardMachine::enterState: no controller for the given page!" ); + if ( pController ) + pController->initializePage(); + + if ( isAutomaticNextButtonStateEnabled() ) + enableButtons( WizardButtonFlags::NEXT, canAdvance() ); + + enableButtons( WizardButtonFlags::PREVIOUS, !m_pImpl->aStateHistory.empty() ); + + // set the new title - it depends on the current page (i.e. state) + implUpdateTitle(); + } + + bool WizardMachine::leaveState(WizardState) + { + // no need to ask the page here. + // If we reach this point, we already gave the current page the chance to commit it's data, + // and it was allowed to commit it's data + + return true; + } + + bool WizardMachine::onFinish() + { + return Finish( RET_OK ); + } + + IMPL_LINK_NOARG(WizardMachine, OnFinish, weld::Button&, void) + { + if ( isTravelingSuspended() ) + return; + WizardTravelSuspension aTravelGuard( *this ); + if ( !prepareLeaveCurrentState( eFinish ) ) + { + return; + } + onFinish(); + } + + IMPL_LINK_NOARG(WizardMachine, OnCancel, weld::Button&, void) + { + m_xAssistant->response(static_cast<int>(WizardButtonFlags::CANCEL)); + } + + WizardMachine::WizardState WizardMachine::determineNextState( WizardState _nCurrentState ) const + { + return _nCurrentState + 1; + } + + bool WizardMachine::prepareLeaveCurrentState( CommitPageReason _eReason ) + { + IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) ); + ENSURE_OR_RETURN( pController != nullptr, "WizardMachine::prepareLeaveCurrentState: no controller for the current page!", true ); + return pController->commitPage( _eReason ); + } + + + bool WizardMachine::skipBackwardUntil( WizardState _nTargetState ) + { + // allowed to leave the current page? + if ( !prepareLeaveCurrentState( eTravelBackward ) ) + return false; + + // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong + ::std::stack< WizardState > aTravelVirtually = m_pImpl->aStateHistory; + ::std::stack< WizardState > aOldStateHistory = m_pImpl->aStateHistory; + + WizardState nCurrentRollbackState = getCurrentState(); + while ( nCurrentRollbackState != _nTargetState ) + { + DBG_ASSERT( !aTravelVirtually.empty(), "WizardMachine::skipBackwardUntil: this target state does not exist in the history!" ); + nCurrentRollbackState = aTravelVirtually.top(); + aTravelVirtually.pop(); + } + m_pImpl->aStateHistory = aTravelVirtually; + if ( !ShowPage( _nTargetState ) ) + { + m_pImpl->aStateHistory = aOldStateHistory; + return false; + } + return true; + } + + + bool WizardMachine::skipUntil( WizardState _nTargetState ) + { + WizardState nCurrentState = getCurrentState(); + + // allowed to leave the current page? + if ( !prepareLeaveCurrentState( nCurrentState < _nTargetState ? eTravelForward : eTravelBackward ) ) + return false; + + // don't travel directly on m_pImpl->aStateHistory, in case something goes wrong + ::std::stack< WizardState > aTravelVirtually = m_pImpl->aStateHistory; + ::std::stack< WizardState > aOldStateHistory = m_pImpl->aStateHistory; + while ( nCurrentState != _nTargetState ) + { + WizardState nNextState = determineNextState( nCurrentState ); + if ( WZS_INVALID_STATE == nNextState ) + { + OSL_FAIL( "WizardMachine::skipUntil: the given target state does not exist!" ); + return false; + } + + // remember the skipped state in the history + aTravelVirtually.push( nCurrentState ); + + // get the next state + nCurrentState = nNextState; + } + m_pImpl->aStateHistory = aTravelVirtually; + // show the target page + if ( !ShowPage( nCurrentState ) ) + { + // argh! prepareLeaveCurrentPage succeeded, determineNextState succeeded, + // but ShowPage doesn't? Somebody behaves very strange here .... + OSL_FAIL( "WizardMachine::skipUntil: very unpolite ...." ); + m_pImpl->aStateHistory = aOldStateHistory; + return false; + } + return true; + } + + + void WizardMachine::skip() + { + // allowed to leave the current page? + if ( !prepareLeaveCurrentState( eTravelForward ) ) + return; + + WizardState nCurrentState = getCurrentState(); + WizardState nNextState = determineNextState(nCurrentState); + + if (WZS_INVALID_STATE == nNextState) + return; + + // remember the skipped state in the history + m_pImpl->aStateHistory.push(nCurrentState); + + // get the next state + nCurrentState = nNextState; + + // show the (n+1)th page + if (!ShowPage(nCurrentState)) + { + // TODO: this leaves us in a state where we have no current page and an inconsistent state history. + // Perhaps we should rollback the skipping here .... + OSL_FAIL("WizardMachine::skip: very unpolite ...."); + // if somebody does a skip and then does not allow to leave ... + // (can't be a commit error, as we've already committed the current page. So if ShowPage fails here, + // somebody behaves really strange ...) + return; + } + + // all fine + } + + bool WizardMachine::travelNext() + { + // allowed to leave the current page? + if ( !prepareLeaveCurrentState( eTravelForward ) ) + return false; + + // determine the next state to travel to + WizardState nCurrentState = getCurrentState(); + WizardState nNextState = determineNextState(nCurrentState); + if (WZS_INVALID_STATE == nNextState) + return false; + + // the state history is used by the enterState method + // all fine + m_pImpl->aStateHistory.push(nCurrentState); + if (!ShowPage(nNextState)) + { + m_pImpl->aStateHistory.pop(); + return false; + } + + return true; + } + + bool WizardMachine::ShowPage(WizardState nState) + { + if (DeactivatePage()) + { + TabPage* pOldTabPage = m_xCurTabPage; + + m_nCurState = nState; + ActivatePage(); + + if (pOldTabPage) + pOldTabPage->DeactivatePage(); + + m_xAssistant->set_current_page(OString::number(nState)); + + m_xCurTabPage = GetPage(m_nCurState); + m_xCurTabPage->ActivatePage(); + + return true; + } + return false; + } + + bool WizardMachine::travelPrevious() + { + DBG_ASSERT(!m_pImpl->aStateHistory.empty(), "WizardMachine::travelPrevious: have no previous page!"); + + // allowed to leave the current page? + if ( !prepareLeaveCurrentState( eTravelBackward ) ) + return false; + + // the next state to switch to + WizardState nPreviousState = m_pImpl->aStateHistory.top(); + + // the state history is used by the enterState method + m_pImpl->aStateHistory.pop(); + // show this page + if (!ShowPage(nPreviousState)) + { + m_pImpl->aStateHistory.push(nPreviousState); + return false; + } + + // all fine + return true; + } + + + void WizardMachine::removePageFromHistory( WizardState nToRemove ) + { + + ::std::stack< WizardState > aTemp; + while(!m_pImpl->aStateHistory.empty()) + { + WizardState nPreviousState = m_pImpl->aStateHistory.top(); + m_pImpl->aStateHistory.pop(); + if(nPreviousState != nToRemove) + aTemp.push( nPreviousState ); + else + break; + } + while(!aTemp.empty()) + { + m_pImpl->aStateHistory.push( aTemp.top() ); + aTemp.pop(); + } + } + + + void WizardMachine::enableAutomaticNextButtonState() + { + m_pImpl->m_bAutoNextButtonState = true; + } + + + bool WizardMachine::isAutomaticNextButtonStateEnabled() const + { + return m_pImpl->m_bAutoNextButtonState; + } + + IMPL_LINK_NOARG(WizardMachine, OnPrevPage, weld::Button&, void) + { + if ( isTravelingSuspended() ) + return; + WizardTravelSuspension aTravelGuard( *this ); + travelPrevious(); + } + + IMPL_LINK_NOARG(WizardMachine, OnNextPage, weld::Button&, void) + { + if ( isTravelingSuspended() ) + return; + WizardTravelSuspension aTravelGuard( *this ); + travelNext(); + } + + IWizardPageController* WizardMachine::getPageController( TabPage* _pCurrentPage ) const + { + IWizardPageController* pController = dynamic_cast< IWizardPageController* >( _pCurrentPage ); + return pController; + } + + + void WizardMachine::getStateHistory( ::std::vector< WizardState >& _out_rHistory ) + { + ::std::stack< WizardState > aHistoryCopy( m_pImpl->aStateHistory ); + while ( !aHistoryCopy.empty() ) + { + _out_rHistory.push_back( aHistoryCopy.top() ); + aHistoryCopy.pop(); + } + } + + + bool WizardMachine::canAdvance() const + { + return WZS_INVALID_STATE != determineNextState( getCurrentState() ); + } + + + void WizardMachine::updateTravelUI() + { + const IWizardPageController* pController = getPageController( GetPage( getCurrentState() ) ); + OSL_ENSURE( pController != nullptr, "RoadmapWizard::updateTravelUI: no controller for the current page!" ); + + bool bCanAdvance = + ( !pController || pController->canAdvance() ) // the current page allows to advance + && canAdvance(); // the dialog as a whole allows to advance + enableButtons( WizardButtonFlags::NEXT, bCanAdvance ); + } + + + bool WizardMachine::isTravelingSuspended() const + { + return m_pImpl->m_bTravelingSuspended; + } + + + void WizardMachine::suspendTraveling( AccessGuard ) + { + DBG_ASSERT( !m_pImpl->m_bTravelingSuspended, "WizardMachine::suspendTraveling: already suspended!" ); + m_pImpl->m_bTravelingSuspended = true; + } + + void WizardMachine::resumeTraveling( AccessGuard ) + { + DBG_ASSERT( m_pImpl->m_bTravelingSuspended, "WizardMachine::resumeTraveling: nothing to resume!" ); + m_pImpl->m_bTravelingSuspended = false; + } + + bool WizardMachine::Finish(short nResult) + { + if ( DeactivatePage() ) + { + if (m_xCurTabPage) + m_xCurTabPage->DeactivatePage(); + + m_xAssistant->response(nResult); + return true; + } + else + return false; + } + + void WizardMachine::AddPage( TabPage* pPage ) + { + ImplWizPageData* pNewPageData = new ImplWizPageData; + pNewPageData->mpNext = nullptr; + pNewPageData->mpPage = pPage; + + if ( !m_pFirstPage ) + m_pFirstPage = pNewPageData; + else + { + ImplWizPageData* pPageData = m_pFirstPage; + while ( pPageData->mpNext ) + pPageData = pPageData->mpNext; + pPageData->mpNext = pNewPageData; + } + } + + void WizardMachine::SetPage(WizardState nLevel, TabPage* pPage) + { + sal_uInt16 nTempLevel = 0; + ImplWizPageData* pPageData = m_pFirstPage; + while ( pPageData ) + { + if ( (nTempLevel == nLevel) || !pPageData->mpNext ) + break; + + nTempLevel++; + pPageData = pPageData->mpNext; + } + + if ( pPageData ) + { + if ( pPageData->mpPage == m_xCurTabPage ) + m_xCurTabPage = nullptr; + pPageData->mpPage = pPage; + } + } + + TabPage* WizardMachine::GetPage(WizardState nLevel) const + { + sal_uInt16 nTempLevel = 0; + + for (ImplWizPageData* pPageData = m_pFirstPage; pPageData; + pPageData = pPageData->mpNext) + { + if ( nTempLevel == nLevel ) + return pPageData->mpPage; + nTempLevel++; + } + + return nullptr; + } } // namespace svt |