2005-07-29 02:23:02 +00:00
# include "global.h"
# include "ScreenOptionsEditCourse.h"
# include "ScreenManager.h"
# include "RageLog.h"
# include "GameState.h"
# include "SongManager.h"
# include "CommonMetrics.h"
# include "GameManager.h"
# include "song.h"
2005-07-30 04:50:17 +00:00
# include "ScreenMiniMenu.h"
2005-07-30 23:40:26 +00:00
# include "ScreenPrompt.h"
2005-08-03 03:28:13 +00:00
# include "CourseUtil.h"
2005-12-22 03:10:04 +00:00
# include "LocalizedString.h"
2006-01-17 21:23:26 +00:00
# include "OptionRowHandler.h"
2005-07-29 02:23:02 +00:00
enum EditCourseRow
{
2005-08-01 05:12:06 +00:00
EditCourseRow_Type ,
EditCourseRow_Meter ,
2005-07-29 02:23:02 +00:00
NUM_EditCourseRow
} ;
2005-07-30 04:50:17 +00:00
AutoScreenMessage ( SM_BackFromContextMenu )
2005-07-29 02:23:02 +00:00
enum CourseEntryAction
{
CourseEntryAction_Edit ,
CourseEntryAction_InsertEntry ,
CourseEntryAction_Delete ,
NUM_CourseEntryAction
} ;
2006-01-04 22:30:51 +00:00
static const char * CourseEntryActionNames [ ] = {
2005-07-29 02:23:02 +00:00
" Edit " ,
" Insert Entry " ,
" Delete " ,
} ;
XToString ( CourseEntryAction , NUM_CourseEntryAction ) ;
# define FOREACH_CourseEntryAction( i ) FOREACH_ENUM( CourseEntryAction, NUM_CourseEntryAction, i )
2005-07-30 04:50:17 +00:00
static MenuDef g_TempMenu (
" ScreenMiniMenuContext "
) ;
2005-07-29 02:23:02 +00:00
2006-01-15 20:46:15 +00:00
REGISTER_SCREEN_CLASS ( ScreenOptionsEditCourse ) ;
2005-07-29 02:23:02 +00:00
void ScreenOptionsEditCourse : : Init ( )
{
2006-02-20 04:25:53 +00:00
ScreenOptionsEditCourseSubMenu : : Init ( ) ;
2006-03-05 03:52:00 +00:00
SubscribeToMessage ( Message_EditCourseDifficultyChanged ) ;
2005-08-15 02:32:36 +00:00
}
2005-07-29 02:23:02 +00:00
2006-02-20 02:38:29 +00:00
static LocalizedString ENTRY ( " OptionTitles " , " Entry %d " ) ;
2005-08-15 02:32:36 +00:00
void ScreenOptionsEditCourse : : BeginScreen ( )
{
2005-07-29 02:23:02 +00:00
// save a backup that we'll use if we revert.
ASSERT ( GAMESTATE - > m_pCurCourse ) ;
Course * pCourse = GAMESTATE - > m_pCurCourse ;
m_Original = * pCourse ;
2005-08-15 16:02:14 +00:00
SONGMAN - > GetSongs ( m_vpDisplayedSongs ) ;
2005-07-29 02:23:02 +00:00
vector < OptionRowHandler * > vHands ;
2006-01-17 21:23:26 +00:00
OptionRowHandler * pHand = OptionRowHandlerUtil : : MakeNull ( ) ;
pHand - > m_Def . m_sName = " Type " ;
pHand - > m_Def . m_layoutType = LAYOUT_SHOW_ONE_IN_ROW ;
pHand - > m_Def . m_bExportOnChange = true ;
2006-02-20 02:38:29 +00:00
pHand - > m_Def . m_bAllowThemeItems = false ;
2006-02-25 09:33:23 +00:00
pHand - > m_Def . m_bOneChoiceForAllPlayers = true ;
2005-08-01 05:12:06 +00:00
FOREACH_CourseType ( i )
2006-01-17 21:23:26 +00:00
pHand - > m_Def . m_vsChoices . push_back ( CourseTypeToLocalizedString ( i ) ) ;
vHands . push_back ( pHand ) ;
pHand = OptionRowHandlerUtil : : MakeNull ( ) ;
pHand - > m_Def . m_sName = " Meter " ;
pHand - > m_Def . m_layoutType = LAYOUT_SHOW_ONE_IN_ROW ;
pHand - > m_Def . m_bExportOnChange = true ;
2006-02-25 09:33:23 +00:00
pHand - > m_Def . m_bOneChoiceForAllPlayers = true ;
2006-01-17 21:23:26 +00:00
pHand - > m_Def . m_vsChoices . push_back ( " Auto " ) ;
2005-07-29 02:23:02 +00:00
for ( int i = MIN_METER ; i < = MAX_METER ; i + + )
2006-01-17 21:23:26 +00:00
pHand - > m_Def . m_vsChoices . push_back ( ssprintf ( " %d " , i ) ) ;
vHands . push_back ( pHand ) ;
2005-07-29 02:23:02 +00:00
FOREACH_CONST ( CourseEntry , pCourse - > m_vEntries , ce )
{
2006-01-17 21:23:26 +00:00
pHand = OptionRowHandlerUtil : : MakeNull ( ) ;
2005-07-29 02:23:02 +00:00
int iEntryIndex = ce - pCourse - > m_vEntries . begin ( ) ;
2006-01-17 21:23:26 +00:00
pHand - > m_Def . m_layoutType = LAYOUT_SHOW_ONE_IN_ROW ;
pHand - > m_Def . m_bExportOnChange = true ;
2006-02-20 02:38:29 +00:00
pHand - > m_Def . m_sName = ssprintf ( ENTRY . GetValue ( ) , iEntryIndex + 1 ) ;
pHand - > m_Def . m_bAllowThemeItems = false ;
pHand - > m_Def . m_bAllowThemeTitle = false ;
2006-02-25 09:33:23 +00:00
pHand - > m_Def . m_bOneChoiceForAllPlayers = true ;
2006-02-20 06:18:57 +00:00
pHand - > m_Def . m_vsChoices . push_back ( " RANDOM " ) ; // XXX Localize?
2005-08-15 16:02:14 +00:00
FOREACH_CONST ( Song * , m_vpDisplayedSongs , s )
2006-01-17 21:23:26 +00:00
pHand - > m_Def . m_vsChoices . push_back ( ( * s ) - > GetTranslitFullTitle ( ) ) ;
vHands . push_back ( pHand ) ;
2005-07-29 02:23:02 +00:00
}
2006-01-17 21:23:26 +00:00
pHand = OptionRowHandlerUtil : : MakeNull ( ) ;
pHand - > m_Def . m_sName = " " ;
pHand - > m_Def . m_layoutType = LAYOUT_SHOW_ONE_IN_ROW ;
2006-02-21 02:51:34 +00:00
pHand - > m_Def . m_bExportOnChange = false ;
2006-02-25 09:33:23 +00:00
pHand - > m_Def . m_bOneChoiceForAllPlayers = true ;
2006-01-17 21:23:26 +00:00
pHand - > m_Def . m_vsChoices . push_back ( " Insert Entry " ) ;
vHands . push_back ( pHand ) ;
2005-07-29 02:23:02 +00:00
2006-01-17 21:31:50 +00:00
ScreenOptions : : InitMenu ( vHands ) ;
2005-07-29 02:23:02 +00:00
ScreenOptions : : BeginScreen ( ) ;
2005-07-30 23:40:26 +00:00
if ( GAMESTATE - > m_iEditCourseEntryIndex > - 1 )
2006-02-20 06:18:57 +00:00
this - > MoveRowAbsolute ( GAMESTATE - > m_MasterPlayerNumber , NUM_EditCourseRow + GAMESTATE - > m_iEditCourseEntryIndex ) ;
2005-07-30 04:50:17 +00:00
AfterChangeRow ( GAMESTATE - > m_MasterPlayerNumber ) ;
2005-07-29 02:23:02 +00:00
}
2005-12-21 09:56:16 +00:00
static LocalizedString MAXIMUM_COURSE_ENTRIES ( " ScreenOptionsEditCourse " , " The maximum number of entries per course is %d. This course already has %d entries. " ) ;
static bool AreEntriesFull ( )
{
Course * pCourse = GAMESTATE - > m_pCurCourse ;
if ( pCourse - > m_vEntries . size ( ) > = size_t ( MAX_ENTRIES_PER_COURSE ) )
{
2006-01-22 01:00:06 +00:00
RString sError = ssprintf ( MAXIMUM_COURSE_ENTRIES . GetValue ( ) , MAX_ENTRIES_PER_COURSE , pCourse - > m_vEntries . size ( ) ) ;
2005-12-21 09:56:16 +00:00
ScreenPrompt : : Prompt ( SM_None , sError ) ;
return true ;
}
return false ;
}
static LocalizedString CANNOT_DELETE_LAST_ENTRY ( " ScreenOptionsEditCourse " , " You cannot delete the last entry in a course. " ) ;
2005-07-29 02:23:02 +00:00
void ScreenOptionsEditCourse : : HandleScreenMessage ( const ScreenMessage SM )
{
2005-07-30 23:40:26 +00:00
Course * pCourse = GAMESTATE - > m_pCurCourse ;
2005-07-29 02:23:02 +00:00
if ( SM = = SM_GoToNextScreen )
{
int iCurRow = m_iCurrentRow [ GAMESTATE - > m_MasterPlayerNumber ] ;
2005-08-01 05:12:06 +00:00
if ( iCurRow = = ( int ) m_pRows . size ( ) - 1 )
2005-07-29 02:23:02 +00:00
{
2006-03-04 10:27:09 +00:00
m_Original = * pCourse ;
WriteCourse ( ) ;
2005-07-29 02:23:02 +00:00
this - > HandleScreenMessage ( SM_GoToPrevScreen ) ;
return ; // don't call base
}
}
2005-07-30 23:40:26 +00:00
else if ( SM = = SM_GoToPrevScreen )
{
2006-03-04 10:27:09 +00:00
* pCourse = m_Original ;
GAMESTATE - > m_pCurCourse . Set ( pCourse ) ;
2005-07-30 23:40:26 +00:00
GAMESTATE - > m_iEditCourseEntryIndex . Set ( - 1 ) ;
}
2005-07-30 04:50:17 +00:00
else if ( SM = = SM_BackFromContextMenu )
2005-07-29 02:23:02 +00:00
{
2005-07-30 04:50:17 +00:00
if ( ! ScreenMiniMenu : : s_bCancelled )
{
switch ( ScreenMiniMenu : : s_iLastRowCode )
{
case CourseEntryAction_Edit :
ScreenOptions : : BeginFadingOut ( ) ;
break ;
case CourseEntryAction_InsertEntry :
2006-02-20 02:46:22 +00:00
{
if ( AreEntriesFull ( ) )
return ;
CourseEntry ce ;
CourseUtil : : MakeDefaultEditCourseEntry ( ce ) ;
pCourse - > m_vEntries . insert ( pCourse - > m_vEntries . begin ( ) + GetCourseEntryIndexWithFocus ( ) , ce ) ;
2006-03-18 11:02:56 +00:00
ReloadScreen ( ) ;
2005-07-30 04:50:17 +00:00
break ;
2006-02-20 02:46:22 +00:00
}
2005-07-30 04:50:17 +00:00
case CourseEntryAction_Delete :
2006-02-20 02:46:22 +00:00
{
if ( pCourse - > m_vEntries . size ( ) = = 1 )
2005-07-30 04:50:17 +00:00
{
2006-02-20 02:46:22 +00:00
ScreenPrompt : : Prompt ( SM_None , CANNOT_DELETE_LAST_ENTRY ) ;
return ;
2005-07-30 04:50:17 +00:00
}
2006-02-20 02:46:22 +00:00
pCourse - > m_vEntries . erase ( pCourse - > m_vEntries . begin ( ) + GetCourseEntryIndexWithFocus ( ) ) ;
GAMESTATE - > m_iEditCourseEntryIndex . Set ( GAMESTATE - > m_iEditCourseEntryIndex ) ;
2006-03-18 11:02:56 +00:00
ReloadScreen ( ) ;
2005-07-30 04:50:17 +00:00
break ;
}
2006-02-20 02:46:22 +00:00
}
2005-07-30 04:50:17 +00:00
}
2005-07-29 02:23:02 +00:00
}
ScreenOptions : : HandleScreenMessage ( SM ) ;
}
2005-07-30 04:50:17 +00:00
void ScreenOptionsEditCourse : : AfterChangeRow ( PlayerNumber pn )
2005-07-29 02:23:02 +00:00
{
2005-07-30 04:50:17 +00:00
ScreenOptions : : AfterChangeRow ( pn ) ;
int iCourseEntry = GetCourseEntryIndexWithFocus ( ) ;
GAMESTATE - > m_iEditCourseEntryIndex . Set ( iCourseEntry ) ;
2005-07-29 02:23:02 +00:00
}
2005-08-01 05:12:06 +00:00
void ScreenOptionsEditCourse : : AfterChangeValueInRow ( int iRow , PlayerNumber pn )
{
ScreenOptions : : AfterChangeValueInRow ( iRow , pn ) ;
2006-02-21 02:51:34 +00:00
2005-08-01 05:12:06 +00:00
Course * pCourse = GAMESTATE - > m_pCurCourse ;
2006-01-19 07:59:59 +00:00
// Regenerate Trails so that the new values propagate
2006-03-04 02:34:01 +00:00
GAMESTATE - > m_pCurTrail [ PLAYER_1 ] . Set ( NULL ) ;
2006-02-20 04:25:53 +00:00
Trail * pTrail = pCourse - > GetTrailForceRegenCache ( GAMESTATE - > m_stEdit , GAMESTATE - > m_cdEdit ) ;
2005-08-01 05:12:06 +00:00
// cause overlay elements to refresh by changing the course
GAMESTATE - > m_pCurCourse . Set ( pCourse ) ;
2006-03-04 02:34:01 +00:00
GAMESTATE - > m_pCurTrail [ PLAYER_1 ] . Set ( pTrail ) ;
2005-08-01 05:12:06 +00:00
}
2005-07-29 02:23:02 +00:00
void ScreenOptionsEditCourse : : ImportOptions ( int iRow , const vector < PlayerNumber > & vpns )
{
2005-08-01 05:12:06 +00:00
OptionRow & row = * m_pRows [ iRow ] ;
2006-02-21 02:51:34 +00:00
if ( row . GetRowType ( ) = = OptionRow : : RowType_Exit )
return ;
2005-08-01 05:12:06 +00:00
Course * pCourse = GAMESTATE - > m_pCurCourse ;
2006-02-21 11:28:05 +00:00
if ( iRow = = ( int ) pCourse - > m_vEntries . size ( ) + NUM_EditCourseRow )
return ; // "Insert Entry"
2005-07-29 02:23:02 +00:00
switch ( iRow )
{
2005-08-01 05:12:06 +00:00
case EditCourseRow_Type :
row . SetOneSharedSelection ( pCourse - > GetCourseType ( ) ) ;
2006-02-20 06:18:57 +00:00
return ;
2005-08-01 05:12:06 +00:00
case EditCourseRow_Meter :
2006-02-20 02:46:22 +00:00
{
2006-02-20 06:18:57 +00:00
int iMeter = pCourse - > m_iCustomMeter [ GAMESTATE - > m_cdEdit ] ;
2006-02-20 02:46:22 +00:00
if ( iMeter = = - 1 )
row . SetOneSharedSelection ( 0 ) ;
else
row . SetOneSharedSelection ( iMeter + 1 - MIN_METER ) ;
2006-02-20 06:18:57 +00:00
return ;
}
}
const CourseEntry & ce = pCourse - > m_vEntries [ iRow - NUM_EditCourseRow ] ;
2006-02-21 02:51:34 +00:00
if ( ! ce . IsFixedSong ( ) )
2006-02-20 06:18:57 +00:00
{
row . SetOneSharedSelection ( 0 ) ;
2005-07-29 02:23:02 +00:00
}
2006-02-20 06:18:57 +00:00
else
{
vector < Song * > : : const_iterator iter = find ( m_vpDisplayedSongs . begin ( ) , m_vpDisplayedSongs . end ( ) , ce . pSong ) ;
2006-02-21 02:51:34 +00:00
if ( iter = = m_vpDisplayedSongs . end ( ) ) // This song isn't being displayed, set to "RANDOM"
2006-02-20 06:18:57 +00:00
row . SetOneSharedSelection ( 0 ) ;
else
row . SetOneSharedSelection ( iter - m_vpDisplayedSongs . begin ( ) + 1 ) ;
2006-02-20 02:46:22 +00:00
}
2005-07-29 02:23:02 +00:00
}
void ScreenOptionsEditCourse : : ExportOptions ( int iRow , const vector < PlayerNumber > & vpns )
{
2005-08-01 05:12:06 +00:00
OptionRow & row = * m_pRows [ iRow ] ;
2006-02-20 04:25:53 +00:00
2006-02-21 02:51:34 +00:00
if ( row . GetRowType ( ) = = OptionRow : : RowType_Exit )
return ;
Course * pCourse = GAMESTATE - > m_pCurCourse ;
2006-02-21 12:37:58 +00:00
if ( iRow = = ( int ) pCourse - > m_vEntries . size ( ) + NUM_EditCourseRow )
return ; // "Insert Entry"
2006-02-21 02:51:34 +00:00
int iSel = row . GetOneSharedSelection ( ) ;
2005-08-01 05:12:06 +00:00
switch ( iRow )
{
case EditCourseRow_Type :
2006-02-20 02:46:22 +00:00
{
2006-02-21 02:51:34 +00:00
CourseType ct = ( CourseType ) iSel ;
2006-02-20 02:46:22 +00:00
pCourse - > SetCourseType ( ct ) ;
2006-02-21 02:51:34 +00:00
return ;
2006-02-20 02:46:22 +00:00
}
2005-08-01 05:12:06 +00:00
case EditCourseRow_Meter :
2006-02-20 02:46:22 +00:00
{
if ( iSel = = 0 ) // "auto"
2006-02-20 06:18:57 +00:00
pCourse - > m_iCustomMeter [ GAMESTATE - > m_cdEdit ] = - 1 ;
2006-02-20 02:46:22 +00:00
else
2006-02-20 06:18:57 +00:00
pCourse - > m_iCustomMeter [ GAMESTATE - > m_cdEdit ] = iSel - 1 + MIN_METER ;
2006-02-21 02:51:34 +00:00
return ;
2005-08-01 05:12:06 +00:00
}
2006-02-20 02:46:22 +00:00
}
2006-02-20 04:25:53 +00:00
2006-02-21 02:51:34 +00:00
CourseEntry & ce = pCourse - > m_vEntries [ iRow - NUM_EditCourseRow ] ;
if ( iSel = = 0 )
ce . pSong = NULL ;
else
ce . pSong = m_vpDisplayedSongs [ iSel - 1 ] ;
2005-07-29 02:23:02 +00:00
}
2005-09-23 00:44:52 +00:00
void ScreenOptionsEditCourse : : ProcessMenuStart ( const InputEventPlus & input )
2005-07-29 02:23:02 +00:00
{
2006-02-18 03:02:21 +00:00
if ( IsTransitioning ( ) )
return ;
2006-02-03 06:21:03 +00:00
int iCurRow = m_iCurrentRow [ GAMESTATE - > m_MasterPlayerNumber ] ;
2005-07-30 23:40:26 +00:00
Course * pCourse = GAMESTATE - > m_pCurCourse ;
2005-07-29 02:23:02 +00:00
2005-07-30 04:50:17 +00:00
if ( iCurRow < NUM_EditCourseRow )
2006-02-21 02:51:34 +00:00
return ;
if ( iCurRow = = ( int ) m_pRows . size ( ) - 2 ) // "create entry"
2005-07-30 04:50:17 +00:00
{
2005-12-21 09:56:16 +00:00
if ( AreEntriesFull ( ) )
2005-07-30 23:40:26 +00:00
return ;
2005-08-03 03:28:13 +00:00
CourseEntry ce ;
CourseUtil : : MakeDefaultEditCourseEntry ( ce ) ;
pCourse - > m_vEntries . push_back ( ce ) ;
2005-07-30 23:40:26 +00:00
GAMESTATE - > m_iEditCourseEntryIndex . Set ( pCourse - > m_vEntries . size ( ) - 1 ) ;
2006-03-18 11:02:56 +00:00
ReloadScreen ( ) ;
2005-07-30 04:50:17 +00:00
}
2005-08-01 05:12:06 +00:00
else if ( iCurRow = = ( int ) m_pRows . size ( ) - 1 ) // "done"
2005-07-29 02:23:02 +00:00
{
2006-01-30 05:41:46 +00:00
SCREENMAN - > PlayStartSound ( ) ;
2005-07-30 04:50:17 +00:00
this - > BeginFadingOut ( ) ;
}
else // a course entry
{
g_TempMenu . rows . clear ( ) ;
FOREACH_CourseEntryAction ( i )
2005-07-29 02:23:02 +00:00
{
2006-02-13 22:45:17 +00:00
MenuRowDef mrd ( i , CourseEntryActionToString ( i ) , true , EditMode_Home , true , true , 0 , " " ) ;
2005-07-30 04:50:17 +00:00
g_TempMenu . rows . push_back ( mrd ) ;
2005-07-29 02:23:02 +00:00
}
2005-07-30 04:50:17 +00:00
int iWidth , iX , iY ;
2006-02-20 06:18:57 +00:00
this - > GetWidthXY ( GAMESTATE - > m_MasterPlayerNumber , iCurRow , 0 , iWidth , iX , iY ) ;
2005-08-01 05:12:06 +00:00
ScreenMiniMenu : : MiniMenu ( & g_TempMenu , SM_BackFromContextMenu , SM_BackFromContextMenu , ( float ) iX , ( float ) iY ) ;
2005-07-29 02:23:02 +00:00
}
}
2006-03-05 03:52:00 +00:00
void ScreenOptionsEditCourse : : HandleMessage ( const RString & sMessage )
{
if ( sMessage = = " EditCourseDifficultyChanged " )
{
const vector < PlayerNumber > vpns ( 1 , GAMESTATE - > m_MasterPlayerNumber ) ;
2006-03-10 22:29:05 +00:00
OptionRow & row = * m_pRows [ EditCourseRow_Meter ] ;
2006-03-05 03:52:00 +00:00
ImportOptions ( EditCourseRow_Meter , vpns ) ;
2006-03-10 22:29:05 +00:00
row . AfterImportOptions ( GAMESTATE - > m_MasterPlayerNumber ) ;
2006-03-05 03:52:00 +00:00
}
ScreenOptionsEditCourseSubMenu : : HandleMessage ( sMessage ) ;
}
2005-07-29 02:23:02 +00:00
int ScreenOptionsEditCourse : : GetCourseEntryIndexWithFocus ( ) const
{
int iCurRow = m_iCurrentRow [ GAMESTATE - > m_MasterPlayerNumber ] ;
if ( iCurRow < NUM_EditCourseRow ) // not a CourseEntry
return - 1 ;
2005-08-01 05:12:06 +00:00
else if ( iCurRow = = ( int ) m_pRows . size ( ) - 1 ) // "done"
2005-07-29 02:23:02 +00:00
return - 1 ;
else
return iCurRow - NUM_EditCourseRow ;
}
/*
2006-03-05 03:52:00 +00:00
* (c) 2002-2006 Chris Danford, Steve Checkoway
2005-07-29 02:23:02 +00:00
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/