Bump version to 5.0-14
[LibreOffice.git] / svtools / source / dialogs / roadmapwizard.cxx
blobe63997907de9225c5c52f66810b8d1235ff96e47
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
27 #include <stdarg.h>
29 #include <vector>
30 #include <map>
31 #include <set>
34 namespace svt
36 namespace
38 typedef ::std::set< WizardTypes::WizardState > StateSet;
40 typedef ::std::map<
41 RoadmapWizardTypes::PathId,
42 RoadmapWizardTypes::WizardPath
43 > Paths;
45 typedef ::std::map<
46 WizardTypes::WizardState,
47 ::std::pair<
48 OUString,
49 RoadmapWizardTypes::RoadmapPageFactory
51 > StateDescriptions;
54 struct RoadmapWizardImpl : public RoadmapWizardTypes
56 ScopedVclPtr<ORoadmap> pRoadmap;
57 Paths aPaths;
58 PathId nActivePath;
59 StateDescriptions aStateDescriptors;
60 StateSet aDisabledStates;
61 bool bActivePathIsDefinite;
63 RoadmapWizardImpl()
64 :pRoadmap( NULL )
65 ,nActivePath( -1 )
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 )
85 break;
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 ] )
108 return nCheck;
110 return nMinLength;
113 //= RoadmapWizard
114 RoadmapWizard::RoadmapWizard( vcl::Window* _pParent, const WinBits i_nStyle, WizardButtonFlags _nButtonFlags )
115 :OWizardMachine( _pParent, i_nStyle, _nButtonFlags )
116 ,m_pImpl( new RoadmapWizardImpl )
118 impl_construct();
121 RoadmapWizard::RoadmapWizard( vcl::Window* _pParent, WizardButtonFlags _nButtonFlags )
122 :OWizardMachine( _pParent, _nButtonFlags )
123 ,m_pImpl( new RoadmapWizardImpl )
125 impl_construct();
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()
150 disposeOnce();
153 void RoadmapWizard::dispose()
155 delete m_pImpl;
156 m_pImpl = NULL;
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 );
180 else
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 )
190 return;
192 WizardPath aNewPath;
194 // collect the elements of the path
195 va_list aStateList;
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 ) )
226 // nothing to do
227 return;
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() )
233 return;
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
244 // states
245 if ( (sal_Int32)aNewPathPos->second.size() <= nCurrentStatePathIndex )
246 return;
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!" );
255 return;
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)
275 return;
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();
284 ++aPathPos
287 if ( aPathPos->first == m_pImpl->nActivePath )
288 // it's the path we are just activating -> no need to check anything
289 continue;
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
295 continue;
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() );
307 if ( pCurrentPage )
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;
323 if ( bExistentItem )
325 if ( !bNeedItem )
327 while ( nItemIndex < m_pImpl->pRoadmap->GetItemCount() )
328 m_pImpl->pRoadmap->DeleteRoadmapItem( nItemIndex );
329 break;
331 else
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 );
340 bInsertItem = true;
344 else
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 ] );
351 if ( bInsertItem )
353 m_pImpl->pRoadmap->InsertRoadmapItem(
354 nItemIndex,
355 getStateDisplayName( nState ),
356 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() )
393 ++nNextStateIndex;
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();
415 ++aPathPos
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
423 nPossiblePaths += 1;
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 )
430 return true;
433 const WizardPath& rPath = m_pImpl->aPaths[ m_pImpl->nActivePath ];
434 if ( *rPath.rbegin() == getCurrentState() )
435 return false;
437 return true;
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;
451 ++state
454 if ( isStateEnabled( *state ) )
455 bHaveEnabledState = true;
458 enableButtons( WizardButtonFlags::PREVIOUS, bHaveEnabledState );
460 implUpdateRoadmap();
464 IMPL_LINK_NOARG(RoadmapWizard, OnRoadmapItemSelected)
467 RoadmapTypes::ItemId nCurItemId = m_pImpl->pRoadmap->GetCurrentRoadmapItemID();
468 if ( nCurItemId == getCurrentState() )
469 // nothing to do
470 return 1L;
472 if ( isTravelingSuspended() )
473 return 0;
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 ) )
484 return 0L;
487 bool bResult = true;
488 if ( nNewIndex > nCurrentIndex )
490 bResult = skipUntil( (WizardState)nCurItemId );
491 WizardState nTemp = (WizardState)nCurItemId;
492 while( nTemp )
494 if( m_pImpl->aDisabledStates.find( --nTemp ) != m_pImpl->aDisabledStates.end() )
495 removePageFromHistory( nTemp );
498 else
499 bResult = skipBackwardUntil( (WizardState)nCurItemId );
501 if ( !bResult )
502 m_pImpl->pRoadmap->SelectRoadmapItemByID( getCurrentState() );
504 return 1L;
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;
529 return sDisplayName;
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 );
546 return pPage;
550 void RoadmapWizard::enableState( WizardState _nState, bool _bEnable )
553 // remember this (in case the state appears in the roadmap later on)
554 if ( _bEnable )
555 m_pImpl->aDisabledStates.erase( _nState );
556 else
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();
571 ++path
574 for ( WizardPath::const_iterator state = path->second.begin();
575 state != path->second.end();
576 ++state
579 if ( *state == i_nState )
580 return true;
583 return false;
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)
600 return;
601 for ( RoadmapTypes::ItemIndex nItemIndex = nCurrentStatePathIndex; nItemIndex < nLoopUntil; ++nItemIndex )
603 bool bExistentItem = ( nItemIndex < m_pImpl->pRoadmap->GetItemCount() );
604 if ( bExistentItem )
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 ) );
613 break;
620 } // namespace svt
623 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */