1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <svtools/roadmapwizard.hxx>
22 #include <svtools/svtools.hrc>
23 #include <svtools/svtresid.hxx>
24 #include <roadmap.hxx>
25 #include <tools/debug.hxx>
38 typedef ::std::set
< WizardTypes::WizardState
> StateSet
;
41 RoadmapWizardTypes::PathId
,
42 RoadmapWizardTypes::WizardPath
46 WizardTypes::WizardState
,
49 RoadmapWizardTypes::RoadmapPageFactory
54 struct RoadmapWizardImpl
: public RoadmapWizardTypes
56 ScopedVclPtr
<ORoadmap
> pRoadmap
;
59 StateDescriptions aStateDescriptors
;
60 StateSet aDisabledStates
;
61 bool bActivePathIsDefinite
;
66 ,bActivePathIsDefinite( false )
70 /// returns the index of the current state in given path, or -1
71 static sal_Int32
getStateIndexInPath( WizardTypes::WizardState _nState
, const WizardPath
& _rPath
);
72 /// returns the index of the current state in the path with the given id, or -1
73 sal_Int32
getStateIndexInPath( WizardTypes::WizardState _nState
, PathId _nPathId
);
74 /// returns the index of the first state in which the two given paths differ
75 static sal_Int32
getFirstDifferentIndex( const WizardPath
& _rLHS
, const WizardPath
& _rRHS
);
79 sal_Int32
RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState
, const WizardPath
& _rPath
)
81 sal_Int32 nStateIndexInPath
= 0;
82 WizardPath::const_iterator aPathLoop
= _rPath
.begin();
83 for ( ; aPathLoop
!= _rPath
.end(); ++aPathLoop
, ++nStateIndexInPath
)
84 if ( *aPathLoop
== _nState
)
86 if ( aPathLoop
== _rPath
.end() )
87 nStateIndexInPath
= -1;
88 return nStateIndexInPath
;
92 sal_Int32
RoadmapWizardImpl::getStateIndexInPath( WizardTypes::WizardState _nState
, PathId _nPathId
)
94 sal_Int32 nStateIndexInPath
= -1;
95 Paths::const_iterator aPathPos
= aPaths
.find( _nPathId
);
96 if ( aPathPos
!= aPaths
.end( ) )
97 nStateIndexInPath
= getStateIndexInPath( _nState
, aPathPos
->second
);
98 return nStateIndexInPath
;
102 sal_Int32
RoadmapWizardImpl::getFirstDifferentIndex( const WizardPath
& _rLHS
, const WizardPath
& _rRHS
)
104 sal_Int32 nMinLength
= ::std::min( _rLHS
.size(), _rRHS
.size() );
105 for ( sal_Int32 nCheck
= 0; nCheck
< nMinLength
; ++nCheck
)
107 if ( _rLHS
[ nCheck
] != _rRHS
[ nCheck
] )
114 RoadmapWizard::RoadmapWizard( vcl::Window
* _pParent
, const WinBits i_nStyle
, WizardButtonFlags _nButtonFlags
)
115 :OWizardMachine( _pParent
, i_nStyle
, _nButtonFlags
)
116 ,m_pImpl( new RoadmapWizardImpl
)
121 RoadmapWizard::RoadmapWizard( vcl::Window
* _pParent
, WizardButtonFlags _nButtonFlags
)
122 :OWizardMachine( _pParent
, _nButtonFlags
)
123 ,m_pImpl( new RoadmapWizardImpl
)
128 void RoadmapWizard::impl_construct()
130 SetLeftAlignedButtonCount( 1 );
131 SetEmptyViewMargin();
133 m_pImpl
->pRoadmap
.reset( VclPtr
<ORoadmap
>::Create( this, WB_TABSTOP
) );
134 m_pImpl
->pRoadmap
->SetText( SVT_RESSTR( STR_WIZDLG_ROADMAP_TITLE
) );
135 m_pImpl
->pRoadmap
->SetPosPixel( Point( 0, 0 ) );
136 m_pImpl
->pRoadmap
->SetItemSelectHdl( LINK( this, RoadmapWizard
, OnRoadmapItemSelected
) );
138 Size aRoadmapSize
=( LogicToPixel( Size( 85, 0 ), MAP_APPFONT
) );
139 aRoadmapSize
.Height() = GetSizePixel().Height();
140 m_pImpl
->pRoadmap
->SetSizePixel( aRoadmapSize
);
142 SetViewWindow( m_pImpl
->pRoadmap
);
143 SetViewAlign( WINDOWALIGN_LEFT
);
144 m_pImpl
->pRoadmap
->Show();
148 RoadmapWizard::~RoadmapWizard()
153 void RoadmapWizard::dispose()
157 OWizardMachine::dispose();
160 void RoadmapWizard::SetRoadmapHelpId( const OString
& _rId
)
162 m_pImpl
->pRoadmap
->SetHelpId( _rId
);
166 void RoadmapWizard::SetRoadmapInteractive( bool _bInteractive
)
168 m_pImpl
->pRoadmap
->SetRoadmapInteractive( _bInteractive
);
172 void RoadmapWizard::declarePath( PathId _nPathId
, const WizardPath
& _lWizardStates
)
175 m_pImpl
->aPaths
.insert( Paths::value_type( _nPathId
, _lWizardStates
) );
177 if ( m_pImpl
->aPaths
.size() == 1 )
178 // the very first path -> activate it
179 activatePath( _nPathId
, false );
181 implUpdateRoadmap( );
185 void RoadmapWizard::declarePath( PathId _nPathId
, WizardState _nFirstState
, ... )
188 DBG_ASSERT( _nFirstState
!= WZS_INVALID_STATE
, "RoadmapWizard::declarePath: there should be at least one state in the path!" );
189 if ( _nFirstState
== WZS_INVALID_STATE
)
194 // collect the elements of the path
196 va_start( aStateList
, _nFirstState
);
198 WizardState nState
= _nFirstState
;
199 while ( nState
!= WZS_INVALID_STATE
)
201 aNewPath
.push_back( nState
);
202 nState
= sal::static_int_cast
< WizardState
>(
203 va_arg( aStateList
, int ));
205 va_end( aStateList
);
207 DBG_ASSERT( _nFirstState
== 0, "RoadmapWizard::declarePath: first state must be NULL." );
208 // The WizardDialog (our very base class) always starts with a mnCurLevel == 0
210 declarePath( _nPathId
, aNewPath
);
214 void RoadmapWizard::describeState( WizardState _nState
, const OUString
& _rStateDisplayName
, RoadmapPageFactory _pPageFactory
)
216 OSL_ENSURE( m_pImpl
->aStateDescriptors
.find( _nState
) == m_pImpl
->aStateDescriptors
.end(),
217 "RoadmapWizard::describeState: there already is a descriptor for this state!" );
218 m_pImpl
->aStateDescriptors
[ _nState
] = StateDescriptions::mapped_type( _rStateDisplayName
, _pPageFactory
);
222 void RoadmapWizard::activatePath( PathId _nPathId
, bool _bDecideForIt
)
225 if ( ( _nPathId
== m_pImpl
->nActivePath
) && ( _bDecideForIt
== m_pImpl
->bActivePathIsDefinite
) )
229 // does the given path exist?
230 Paths::const_iterator aNewPathPos
= m_pImpl
->aPaths
.find( _nPathId
);
231 DBG_ASSERT( aNewPathPos
!= m_pImpl
->aPaths
.end(), "RoadmapWizard::activate: there is no such path!" );
232 if ( aNewPathPos
== m_pImpl
->aPaths
.end() )
235 // determine the index of the current state in the current path
236 sal_Int32 nCurrentStatePathIndex
= -1;
237 if ( m_pImpl
->nActivePath
!= -1 )
238 nCurrentStatePathIndex
= m_pImpl
->getStateIndexInPath( getCurrentState(), m_pImpl
->nActivePath
);
240 DBG_ASSERT( (sal_Int32
)aNewPathPos
->second
.size() > nCurrentStatePathIndex
,
241 "RoadmapWizard::activate: you cannot activate a path which has less states than we've already advanced!" );
242 // If this asserts, this for instance means that we are already in state number, say, 5
243 // of our current path, and the caller tries to activate a path which has less than 5
245 if ( (sal_Int32
)aNewPathPos
->second
.size() <= nCurrentStatePathIndex
)
248 // assert that the current and the new path are equal, up to nCurrentStatePathIndex
249 Paths::const_iterator aActivePathPos
= m_pImpl
->aPaths
.find( m_pImpl
->nActivePath
);
250 if ( aActivePathPos
!= m_pImpl
->aPaths
.end() )
252 if ( RoadmapWizardImpl::getFirstDifferentIndex( aActivePathPos
->second
, aNewPathPos
->second
) <= nCurrentStatePathIndex
)
254 OSL_FAIL( "RoadmapWizard::activate: you cannot activate a path which conflicts with the current one *before* the current state!" );
259 m_pImpl
->nActivePath
= _nPathId
;
260 m_pImpl
->bActivePathIsDefinite
= _bDecideForIt
;
262 implUpdateRoadmap( );
266 void RoadmapWizard::implUpdateRoadmap( )
269 DBG_ASSERT( m_pImpl
->aPaths
.find( m_pImpl
->nActivePath
) != m_pImpl
->aPaths
.end(),
270 "RoadmapWizard::implUpdateRoadmap: there is no such path!" );
271 const WizardPath
& rActivePath( m_pImpl
->aPaths
[ m_pImpl
->nActivePath
] );
273 sal_Int32 nCurrentStatePathIndex
= RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath
);
274 if (nCurrentStatePathIndex
< 0)
277 // determine up to which index (in the new path) we have to display the items
278 RoadmapTypes::ItemIndex nUpperStepBoundary
= (RoadmapTypes::ItemIndex
)rActivePath
.size();
279 bool bIncompletePath
= false;
280 if ( !m_pImpl
->bActivePathIsDefinite
)
282 for ( Paths::const_iterator aPathPos
= m_pImpl
->aPaths
.begin();
283 aPathPos
!= m_pImpl
->aPaths
.end();
287 if ( aPathPos
->first
== m_pImpl
->nActivePath
)
288 // it's the path we are just activating -> no need to check anything
290 // the index from which on both paths differ
291 sal_Int32 nDivergenceIndex
= RoadmapWizardImpl::getFirstDifferentIndex( rActivePath
, aPathPos
->second
);
292 if ( nDivergenceIndex
<= nCurrentStatePathIndex
)
293 // they differ in an index which we have already left behind us
294 // -> this is no conflict anymore
297 // the path conflicts with our new path -> don't activate the
298 // *complete* new path, but only up to the step which is unambiguous
299 nUpperStepBoundary
= nDivergenceIndex
;
300 bIncompletePath
= true;
304 // can we advance from the current page?
305 bool bCurrentPageCanAdvance
= true;
306 TabPage
* pCurrentPage
= GetPage( getCurrentState() );
309 const IWizardPageController
* pController
= getPageController( GetPage( getCurrentState() ) );
310 OSL_ENSURE( pController
!= NULL
, "RoadmapWizard::implUpdateRoadmap: no controller for the current page!" );
311 bCurrentPageCanAdvance
= !pController
|| pController
->canAdvance();
314 // now, we have to remove all items after nCurrentStatePathIndex, and insert the items from the active
315 // path, up to (excluding) nUpperStepBoundary
316 RoadmapTypes::ItemIndex nLoopUntil
= ::std::max( (RoadmapTypes::ItemIndex
)nUpperStepBoundary
, m_pImpl
->pRoadmap
->GetItemCount() );
317 for ( RoadmapTypes::ItemIndex nItemIndex
= nCurrentStatePathIndex
; nItemIndex
< nLoopUntil
; ++nItemIndex
)
319 bool bExistentItem
= ( nItemIndex
< m_pImpl
->pRoadmap
->GetItemCount() );
320 bool bNeedItem
= ( nItemIndex
< nUpperStepBoundary
);
322 bool bInsertItem
= false;
327 while ( nItemIndex
< m_pImpl
->pRoadmap
->GetItemCount() )
328 m_pImpl
->pRoadmap
->DeleteRoadmapItem( nItemIndex
);
333 // there is an item with this index in the roadmap - does it match what is requested by
334 // the respective state in the active path?
335 RoadmapTypes::ItemId nPresentItemId
= m_pImpl
->pRoadmap
->GetItemID( nItemIndex
);
336 WizardState nRequiredState
= rActivePath
[ nItemIndex
];
337 if ( nPresentItemId
!= nRequiredState
)
339 m_pImpl
->pRoadmap
->DeleteRoadmapItem( nItemIndex
);
346 DBG_ASSERT( bNeedItem
, "RoadmapWizard::implUpdateRoadmap: ehm - none needed, none present - why did the loop not terminate?" );
347 bInsertItem
= bNeedItem
;
350 WizardState
nState( rActivePath
[ nItemIndex
] );
353 m_pImpl
->pRoadmap
->InsertRoadmapItem(
355 getStateDisplayName( nState
),
360 // if the item is *after* the current state, but the current page does not
361 // allow advancing, the disable the state. This relieves derived classes
362 // from disabling all future states just because the current state does not
363 // (yet) allow advancing.
364 const bool nUnconditionedDisable
= !bCurrentPageCanAdvance
&& ( nItemIndex
> nCurrentStatePathIndex
);
365 const bool bEnable
= !nUnconditionedDisable
&& ( m_pImpl
->aDisabledStates
.find( nState
) == m_pImpl
->aDisabledStates
.end() );
367 m_pImpl
->pRoadmap
->EnableRoadmapItem( m_pImpl
->pRoadmap
->GetItemID( nItemIndex
), bEnable
);
370 m_pImpl
->pRoadmap
->SetRoadmapComplete( !bIncompletePath
);
374 WizardTypes::WizardState
RoadmapWizard::determineNextState( WizardState _nCurrentState
) const
377 sal_Int32 nCurrentStatePathIndex
= -1;
379 Paths::const_iterator aActivePathPos
= m_pImpl
->aPaths
.find( m_pImpl
->nActivePath
);
380 if ( aActivePathPos
!= m_pImpl
->aPaths
.end() )
381 nCurrentStatePathIndex
= RoadmapWizardImpl::getStateIndexInPath( _nCurrentState
, aActivePathPos
->second
);
383 DBG_ASSERT( nCurrentStatePathIndex
!= -1, "RoadmapWizard::determineNextState: ehm - how can we travel if there is no (valid) active path?" );
384 if ( nCurrentStatePathIndex
== -1 )
385 return WZS_INVALID_STATE
;
387 sal_Int32 nNextStateIndex
= nCurrentStatePathIndex
+ 1;
389 while ( ( nNextStateIndex
< (sal_Int32
)aActivePathPos
->second
.size() )
390 && ( m_pImpl
->aDisabledStates
.find( aActivePathPos
->second
[ nNextStateIndex
] ) != m_pImpl
->aDisabledStates
.end() )
396 if ( nNextStateIndex
>= (sal_Int32
)aActivePathPos
->second
.size() )
397 // there is no next state in the current path (at least none which is enabled)
398 return WZS_INVALID_STATE
;
400 return aActivePathPos
->second
[ nNextStateIndex
];
404 bool RoadmapWizard::canAdvance() const
406 if ( !m_pImpl
->bActivePathIsDefinite
)
408 // check how many paths are still allowed
409 const WizardPath
& rActivePath( m_pImpl
->aPaths
[ m_pImpl
->nActivePath
] );
410 sal_Int32 nCurrentStatePathIndex
= RoadmapWizardImpl::getStateIndexInPath( getCurrentState(), rActivePath
);
412 size_t nPossiblePaths(0);
413 for ( Paths::const_iterator aPathPos
= m_pImpl
->aPaths
.begin();
414 aPathPos
!= m_pImpl
->aPaths
.end();
418 // the index from which on both paths differ
419 sal_Int32 nDivergenceIndex
= RoadmapWizardImpl::getFirstDifferentIndex( rActivePath
, aPathPos
->second
);
421 if ( nDivergenceIndex
> nCurrentStatePathIndex
)
422 // this path is still a possible path
426 // if we have more than one path which is still possible, then we assume
427 // to always have a next state. Though there might be scenarios where this
428 // is not true, but this is too sophisticated (means not really needed) right now.
429 if ( nPossiblePaths
> 1 )
433 const WizardPath
& rPath
= m_pImpl
->aPaths
[ m_pImpl
->nActivePath
];
434 if ( *rPath
.rbegin() == getCurrentState() )
441 void RoadmapWizard::updateTravelUI()
443 OWizardMachine::updateTravelUI();
445 // disable the "Previous" button if all states in our history are disabled
446 ::std::vector
< WizardState
> aHistory
;
447 getStateHistory( aHistory
);
448 bool bHaveEnabledState
= false;
449 for ( ::std::vector
< WizardState
>::const_iterator state
= aHistory
.begin();
450 state
!= aHistory
.end() && !bHaveEnabledState
;
454 if ( isStateEnabled( *state
) )
455 bHaveEnabledState
= true;
458 enableButtons( WizardButtonFlags::PREVIOUS
, bHaveEnabledState
);
464 IMPL_LINK_NOARG(RoadmapWizard
, OnRoadmapItemSelected
)
467 RoadmapTypes::ItemId nCurItemId
= m_pImpl
->pRoadmap
->GetCurrentRoadmapItemID();
468 if ( nCurItemId
== getCurrentState() )
472 if ( isTravelingSuspended() )
475 WizardTravelSuspension
aTravelGuard( *this );
477 sal_Int32 nCurrentIndex
= m_pImpl
->getStateIndexInPath( getCurrentState(), m_pImpl
->nActivePath
);
478 sal_Int32 nNewIndex
= m_pImpl
->getStateIndexInPath( nCurItemId
, m_pImpl
->nActivePath
);
480 DBG_ASSERT( ( nCurrentIndex
!= -1 ) && ( nNewIndex
!= -1 ),
481 "RoadmapWizard::OnRoadmapItemSelected: something's wrong here!" );
482 if ( ( nCurrentIndex
== -1 ) || ( nNewIndex
== -1 ) )
488 if ( nNewIndex
> nCurrentIndex
)
490 bResult
= skipUntil( (WizardState
)nCurItemId
);
491 WizardState nTemp
= (WizardState
)nCurItemId
;
494 if( m_pImpl
->aDisabledStates
.find( --nTemp
) != m_pImpl
->aDisabledStates
.end() )
495 removePageFromHistory( nTemp
);
499 bResult
= skipBackwardUntil( (WizardState
)nCurItemId
);
502 m_pImpl
->pRoadmap
->SelectRoadmapItemByID( getCurrentState() );
508 void RoadmapWizard::enterState( WizardState _nState
)
511 OWizardMachine::enterState( _nState
);
513 // synchronize the roadmap
514 implUpdateRoadmap( );
515 m_pImpl
->pRoadmap
->SelectRoadmapItemByID( getCurrentState() );
519 OUString
RoadmapWizard::getStateDisplayName( WizardState _nState
) const
521 OUString sDisplayName
;
523 StateDescriptions::const_iterator pos
= m_pImpl
->aStateDescriptors
.find( _nState
);
524 OSL_ENSURE( pos
!= m_pImpl
->aStateDescriptors
.end(),
525 "RoadmapWizard::getStateDisplayName: no default implementation available for this state!" );
526 if ( pos
!= m_pImpl
->aStateDescriptors
.end() )
527 sDisplayName
= pos
->second
.first
;
533 VclPtr
<TabPage
> RoadmapWizard::createPage( WizardState _nState
)
535 VclPtr
<TabPage
> pPage
;
537 StateDescriptions::const_iterator pos
= m_pImpl
->aStateDescriptors
.find( _nState
);
538 OSL_ENSURE( pos
!= m_pImpl
->aStateDescriptors
.end(),
539 "RoadmapWizard::createPage: no default implementation available for this state!" );
540 if ( pos
!= m_pImpl
->aStateDescriptors
.end() )
542 RoadmapPageFactory pFactory
= pos
->second
.second
;
543 pPage
= (*pFactory
)( *this );
550 void RoadmapWizard::enableState( WizardState _nState
, bool _bEnable
)
553 // remember this (in case the state appears in the roadmap later on)
555 m_pImpl
->aDisabledStates
.erase( _nState
);
558 m_pImpl
->aDisabledStates
.insert( _nState
);
559 removePageFromHistory( _nState
);
562 // if the state is currently in the roadmap, reflect it's new status
563 m_pImpl
->pRoadmap
->EnableRoadmapItem( (RoadmapTypes::ItemId
)_nState
, _bEnable
);
567 bool RoadmapWizard::knowsState( WizardState i_nState
) const
569 for ( Paths::const_iterator path
= m_pImpl
->aPaths
.begin();
570 path
!= m_pImpl
->aPaths
.end();
574 for ( WizardPath::const_iterator state
= path
->second
.begin();
575 state
!= path
->second
.end();
579 if ( *state
== i_nState
)
586 bool RoadmapWizard::isStateEnabled( WizardState _nState
) const
588 return m_pImpl
->aDisabledStates
.find( _nState
) == m_pImpl
->aDisabledStates
.end();
591 void RoadmapWizard::updateRoadmapItemLabel( WizardState _nState
)
593 const WizardPath
& rActivePath( m_pImpl
->aPaths
[ m_pImpl
->nActivePath
] );
594 RoadmapTypes::ItemIndex nUpperStepBoundary
= (RoadmapTypes::ItemIndex
)rActivePath
.size();
595 RoadmapTypes::ItemIndex nLoopUntil
= ::std::max( (RoadmapTypes::ItemIndex
)nUpperStepBoundary
, m_pImpl
->pRoadmap
->GetItemCount() );
596 sal_Int32 nCurrentStatePathIndex
= -1;
597 if ( m_pImpl
->nActivePath
!= -1 )
598 nCurrentStatePathIndex
= m_pImpl
->getStateIndexInPath( getCurrentState(), m_pImpl
->nActivePath
);
599 if (nCurrentStatePathIndex
< 0)
601 for ( RoadmapTypes::ItemIndex nItemIndex
= nCurrentStatePathIndex
; nItemIndex
< nLoopUntil
; ++nItemIndex
)
603 bool bExistentItem
= ( nItemIndex
< m_pImpl
->pRoadmap
->GetItemCount() );
606 // there is an item with this index in the roadmap - does it match what is requested by
607 // the respective state in the active path?
608 RoadmapTypes::ItemId nPresentItemId
= m_pImpl
->pRoadmap
->GetItemID( nItemIndex
);
609 WizardState nRequiredState
= rActivePath
[ nItemIndex
];
610 if ( _nState
== nRequiredState
)
612 m_pImpl
->pRoadmap
->ChangeRoadmapItemLabel( nPresentItemId
, getStateDisplayName( nRequiredState
) );
623 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */