diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 81ac907bbe..d6676d32ee 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -8,6 +8,11 @@ ________________________________________________________________________________ StepMania 5.0 alpha 3 | 20120711 -------------------------------------------------------------------------------- +2012/07/19 +---------- +* [OptionRow] Set text after running OnCommand so commands like "uppercase,true" + actually have a chance to work. [AJ] + 2012/07/17 ---------- * [SongManager] Fix a bug where DVNO wouldn't always be gold. [AJ] diff --git a/src/OptionRow.cpp b/src/OptionRow.cpp index d64fbe9b07..ea3de64c33 100644 --- a/src/OptionRow.cpp +++ b/src/OptionRow.cpp @@ -1,1000 +1,1001 @@ -#include "global.h" -#include "OptionRow.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "Foreach.h" -#include "OptionRowHandler.h" -#include "CommonMetrics.h" -#include "GameState.h" -#include "Song.h" -#include "Course.h" -#include "Style.h" -#include "ActorUtil.h" - -const RString NEXT_ROW_NAME = "NextRow"; -const RString EXIT_NAME = "Exit"; - -RString OptionRow::GetThemedItemText( int iChoice ) const -{ - RString s = m_pHand->GetThemedItemText( iChoice ); - - // HACK: Always theme the NEXT_ROW and EXIT items. - if( m_bFirstItemGoesDown && iChoice == 0 ) - s = CommonMetrics::LocalizeOptionItem( NEXT_ROW_NAME, false ); - else if( m_RowType == OptionRow::RowType_Exit ) - s = CommonMetrics::LocalizeOptionItem( EXIT_NAME, false ); - - return s; -} - -RString ITEMS_LONG_ROW_X_NAME( size_t p ) { return ssprintf("ItemsLongRowP%dX",int(p+1)); } -RString MOD_ICON_X_NAME( size_t p ) { return ssprintf("ModIconP%dX",int(p+1)); } - -OptionRow::OptionRow( const OptionRowType *pSource ) -{ - m_pParentType = pSource; - m_pHand = NULL; - - m_textTitle = NULL; - ZERO( m_ModIcons ); - - Clear(); - this->AddChild( &m_Frame ); - - m_tsDestination.Init(); -} - -OptionRow::~OptionRow() -{ - Clear(); -} - -void OptionRow::Clear() -{ - ActorFrame::RemoveAllChildren(); - - FOREACH_PlayerNumber( p ) - m_vbSelected[p].clear(); - - m_Frame.DeleteAllChildren(); - m_textItems.clear(); - FOREACH_PlayerNumber( p ) - m_Underline[p].clear(); - - if( m_pHand != NULL ) - { - FOREACH_CONST( RString, m_pHand->m_vsReloadRowMessages, m ) - MESSAGEMAN->Unsubscribe( this, *m ); - } - SAFE_DELETE( m_pHand ); - - m_bFirstItemGoesDown = false; - ZERO( m_bRowHasFocus ); - ZERO( m_iChoiceInRowWithFocus ); -} - -void OptionRowType::Load( const RString &sMetricsGroup, Actor *pParent ) -{ - m_sMetricsGroup = sMetricsGroup; - - ITEMS_START_X .Load(sMetricsGroup,"ItemsStartX"); - ITEMS_END_X .Load(sMetricsGroup,"ItemsEndX"); - ITEMS_GAP_X .Load(sMetricsGroup,"ItemsGapX"); - ITEMS_MIN_BASE_ZOOM .Load(sMetricsGroup,"ItemsMinBaseZoom"); - ITEMS_LONG_ROW_X .Load(sMetricsGroup,ITEMS_LONG_ROW_X_NAME,NUM_PLAYERS); - ITEMS_LONG_ROW_SHARED_X .Load(sMetricsGroup,"ItemsLongRowSharedX"); - MOD_ICON_X .Load(sMetricsGroup,MOD_ICON_X_NAME,NUM_PLAYERS); - COLOR_SELECTED .Load(sMetricsGroup,"ColorSelected"); - COLOR_NOT_SELECTED .Load(sMetricsGroup,"ColorNotSelected"); - COLOR_DISABLED .Load(sMetricsGroup,"ColorDisabled"); - TWEEN_SECONDS .Load(sMetricsGroup,"TweenSeconds"); - SHOW_BPM_IN_SPEED_TITLE .Load(sMetricsGroup,"ShowBpmInSpeedTitle"); - SHOW_MOD_ICONS .Load(sMetricsGroup,"ShowModIcons"); - SHOW_UNDERLINES .Load(sMetricsGroup,"ShowUnderlines"); - MOD_ICON_METRICS_GROUP .Load(sMetricsGroup,"ModIconMetricsGroup"); - - m_textItem.LoadFromFont( THEME->GetPathF(sMetricsGroup,"Item") ); - m_textItem.SetName( "Item" ); - ActorUtil::LoadAllCommands( m_textItem, sMetricsGroup ); - - if( SHOW_UNDERLINES ) - { - FOREACH_PlayerNumber( p ) - m_Underline[p].Load( "OptionsUnderline" + PlayerNumberToString(p), false ); - } - - m_textTitle.LoadFromFont( THEME->GetPathF(sMetricsGroup,"title") ); - m_textTitle.SetName( "Title" ); - ActorUtil::LoadAllCommandsAndSetXY( m_textTitle, sMetricsGroup ); - - m_sprFrame.Load( ActorUtil::MakeActor( THEME->GetPathG(sMetricsGroup,"Frame"), pParent ) ); - m_sprFrame->SetName( "Frame" ); - ActorUtil::LoadAllCommandsAndSetXY( m_sprFrame, sMetricsGroup ); - - if( SHOW_MOD_ICONS ) - { - m_ModIcon.Load( MOD_ICON_METRICS_GROUP ); - m_ModIcon.SetName( "ModIcon" ); - ActorUtil::LoadAllCommands( m_ModIcon, sMetricsGroup ); - } -} - -void OptionRow::LoadNormal( OptionRowHandler *pHand, bool bFirstItemGoesDown ) -{ - m_RowType = OptionRow::RowType_Normal; - m_pHand = pHand; - m_bFirstItemGoesDown = bFirstItemGoesDown; - - FOREACH_CONST( RString, m_pHand->m_vsReloadRowMessages, m ) - MESSAGEMAN->Subscribe( this, *m ); - - ChoicesChanged( RowType_Normal ); -} - -void OptionRow::LoadExit() -{ - m_RowType = OptionRow::RowType_Exit; - OptionRowHandler *pHand = OptionRowHandlerUtil::MakeNull(); - pHand->m_Def.m_selectType = SELECT_NONE; - pHand->m_Def.m_sName = EXIT_NAME; - pHand->m_Def.m_vsChoices.push_back( EXIT_NAME ); - pHand->m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - pHand->m_Def.m_bOneChoiceForAllPlayers = true; - m_pHand = pHand; - - ChoicesChanged( RowType_Exit ); -} - -void OptionRow::ChoicesChanged( RowType type ) -{ - ASSERT_M( !m_pHand->m_Def.m_vsChoices.empty(), m_pHand->m_Def.m_sName + " has no choices" ); - - // Remove the NextRow marker before reloading choices - if( m_pHand->m_Def.m_vsChoices[0] == NEXT_ROW_NAME ) - { - m_pHand->m_Def.m_vsChoices.erase( m_pHand->m_Def.m_vsChoices.begin() ); - FOREACH_PlayerNumber( p ) - m_vbSelected[p].erase( m_vbSelected[p].begin() ); - } - - FOREACH_PlayerNumber( p ) - { - vector &vbSelected = m_vbSelected[p]; - vbSelected.resize( 0 ); - vbSelected.resize( m_pHand->m_Def.m_vsChoices.size(), false ); - - // set select the first item if a SELECT_ONE row - if( vbSelected.size() && m_pHand->m_Def.m_selectType == SELECT_ONE ) - vbSelected[0] = true; - } - - // TRICKY: Insert a down arrow as the first choice in the row. - if( m_bFirstItemGoesDown ) - { - m_pHand->m_Def.m_vsChoices.insert( m_pHand->m_Def.m_vsChoices.begin(), NEXT_ROW_NAME ); - FOREACH_PlayerNumber( p ) - m_vbSelected[p].insert( m_vbSelected[p].begin(), false ); - } - - InitText( type ); - - // When choices change, the old focus position is meaningless; reset it. - FOREACH_PlayerNumber( p ) - SetChoiceInRowWithFocus( p, 0 ); - - m_textTitle->SetText( GetRowTitle() ); -} - -RString OptionRow::GetRowTitle() const -{ - RString sTitle = m_pHand->OptionTitle(); - - // HACK: tack the BPM onto the name of the speed line - if( m_pHand->m_Def.m_sName.CompareNoCase("speed")==0 ) - { - bool bShowBpmInSpeedTitle = m_pParentType->SHOW_BPM_IN_SPEED_TITLE; - - if( GAMESTATE->m_pCurCourse ) - { - const Trail* pTrail = GAMESTATE->m_pCurTrail[GAMESTATE->GetMasterPlayerNumber()]; - ASSERT( pTrail != NULL ); - const int iNumCourseEntries = pTrail->m_vEntries.size(); - if( iNumCourseEntries > CommonMetrics::MAX_COURSE_ENTRIES_BEFORE_VARIOUS ) - bShowBpmInSpeedTitle = false; - } - - if( bShowBpmInSpeedTitle ) - { - DisplayBpms bpms; - if( GAMESTATE->m_pCurSong ) - { - const Song* pSong = GAMESTATE->m_pCurSong; - pSong->GetDisplayBpms( bpms ); - } - else if( GAMESTATE->m_pCurCourse ) - { - const Course *pCourse = GAMESTATE->m_pCurCourse; - StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType; - const Trail* pTrail = pCourse->GetTrail( st ); - ASSERT( pTrail ); - pTrail->GetDisplayBpms( bpms ); - } - - if( bpms.IsSecret() ) - sTitle += ssprintf( " (??" "?)" ); // split so gcc doesn't think this is a trigraph - else if( bpms.BpmIsConstant() ) - sTitle += ssprintf( " (%.0f)", bpms.GetMin() ); - else - sTitle += ssprintf( " (%.0f-%.0f)", bpms.GetMin(), bpms.GetMax() ); - } - } - - return sTitle; -} - -/* Set up text, underlines and titles for options. This can be called as soon as - * m_pHand->m_Def is available. */ -void OptionRow::InitText( RowType type ) -{ - /* If we have elements already, we're being updated from a new set of options. - * Delete the old ones. */ - m_Frame.DeleteAllChildren(); - m_textItems.clear(); - FOREACH_PlayerNumber( p ) - m_Underline[p].clear(); - - m_textTitle = new BitmapText( m_pParentType->m_textTitle ); - m_Frame.AddChild( m_textTitle ); - - m_sprFrame = m_pParentType->m_sprFrame->Copy(); - m_sprFrame->SetDrawOrder(-1); // under title - m_Frame.AddChild( m_sprFrame ); - - if( m_pParentType->SHOW_MOD_ICONS ) - { - switch( m_RowType ) - { - case RowType_Normal: - FOREACH_PlayerNumber( p ) - { - m_ModIcons[p] = new ModIcon( m_pParentType->m_ModIcon ); - m_ModIcons[p]->SetDrawOrder(-1); // under title - m_ModIcons[p]->PlayCommand( "On" ); - - m_Frame.AddChild( m_ModIcons[p] ); - - GameCommand gc; - SetModIcon( p, "", gc ); - } - break; - case RowType_Exit: - break; - } - } - - // If the items will go off the edge of the screen, then force LAYOUT_SHOW_ONE_IN_ROW. - float fBaseZoom = 1.0f; - { - BitmapText bt( m_pParentType->m_textItem ); - bt.PlayCommand( "On" ); - - // Figure out the width of the row. - float fWidth = 0; - for( unsigned c=0; cm_Def.m_vsChoices.size(); c++ ) - { - RString sText = GetThemedItemText( c ); - bt.SetText( sText ); - - fWidth += bt.GetZoomedWidth(); - - if( c != m_pHand->m_Def.m_vsChoices.size()-1 ) - fWidth += m_pParentType->ITEMS_GAP_X; - } - - // Try to fit everything on one line. - float fTotalWidth = m_pParentType->ITEMS_END_X - m_pParentType->ITEMS_START_X; - if( fWidth > fTotalWidth ) - { - float fPossibleBaseZoom = fTotalWidth / fWidth; - if( fPossibleBaseZoom >= m_pParentType->ITEMS_MIN_BASE_ZOOM ) - fBaseZoom = fPossibleBaseZoom; - else - m_pHand->m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; - } - } - - // load m_textItems - switch( m_pHand->m_Def.m_layoutType ) - { - case LAYOUT_SHOW_ONE_IN_ROW: - // init text - FOREACH_PlayerNumber( p ) - { - BitmapText *pText = new BitmapText( m_pParentType->m_textItem ); - m_textItems.push_back( pText ); - - pText->PlayCommand( "On" ); - - if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - { - pText->SetX( m_pParentType->ITEMS_LONG_ROW_SHARED_X ); - break; // only initialize one item since it's shared - } - else - { - pText->SetX( m_pParentType->ITEMS_LONG_ROW_X.GetValue(p) ); - } - - // Set the text now, so SetWidthXY below is correct. - UpdateText( p ); - - // init underlines - if( m_pParentType->SHOW_UNDERLINES && GetRowType() != OptionRow::RowType_Exit ) - { - OptionsCursor *pCursor = new OptionsCursor( m_pParentType->m_Underline[p] ); - m_Underline[p].push_back( pCursor ); - - int iWidth, iX, iY; - GetWidthXY( p, 0, iWidth, iX, iY ); - pCursor->SetX( float(iX) ); - pCursor->SetBarWidth( iWidth ); - } - } - break; - - case LAYOUT_SHOW_ALL_IN_ROW: - { - float fX = m_pParentType->ITEMS_START_X; - for( unsigned c=0; cm_Def.m_vsChoices.size(); c++ ) - { - // init text - BitmapText *bt = new BitmapText( m_pParentType->m_textItem ); - m_textItems.push_back( bt ); - RString sText = GetThemedItemText( c ); - bt->SetText( sText ); - bt->SetBaseZoomX( fBaseZoom ); - bt->PlayCommand( "On" ); - - // set the X position of each item in the line - float fItemWidth = bt->GetZoomedWidth(); - fX += fItemWidth/2; - bt->SetX( fX ); - - // init underlines - if( m_pParentType->SHOW_UNDERLINES ) - { - FOREACH_PlayerNumber( p ) - { - OptionsCursor *ul = new OptionsCursor( m_pParentType->m_Underline[p] ); - m_Underline[p].push_back( ul ); - ul->SetX( fX ); - ul->SetBarWidth( int(fItemWidth) ); - } - } - - fX += fItemWidth/2 + m_pParentType->ITEMS_GAP_X * fBaseZoom; - } - } - break; - - default: - ASSERT(0); - } - - for( unsigned c=0; cSetText( GetRowTitle() ); - m_textTitle->PlayCommand( "On" ); - - m_sprFrame->PlayCommand( "On" ); - - m_Frame.SortByDrawOrder(); - this->SortByDrawOrder(); -} - -// After importing options, choose which item is focused. -void OptionRow::AfterImportOptions( PlayerNumber pn ) -{ - /* We load items for both players on start, since we don't know which players - * will be joined when we're displayed. Hide items for inactive players. */ - if( m_pHand->m_Def.m_layoutType == LAYOUT_SHOW_ONE_IN_ROW && - !m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - m_textItems[pn]->SetVisible( GAMESTATE->IsHumanPlayer(pn) ); - - // Hide underlines for disabled players. - if( !GAMESTATE->IsHumanPlayer(pn) ) - for( unsigned c=0; cSetVisible( false ); - - // Make all selections the same if bOneChoiceForAllPlayers. - // Hack: we only import active players, so if only player 2 is imported, - // we need to copy p2 to p1, not p1 to p2. - if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - { - PlayerNumber pnCopyFrom = GAMESTATE->GetMasterPlayerNumber(); - if( GAMESTATE->GetMasterPlayerNumber() == PLAYER_INVALID ) - pnCopyFrom = PLAYER_1; - FOREACH_PlayerNumber( p ) - m_vbSelected[p] = m_vbSelected[pnCopyFrom]; - } - - switch( m_pHand->m_Def.m_selectType ) - { - case SELECT_ONE: - { - // Make sure the row actually has a selection. - int iSelection = GetOneSelection(pn, true); - if( iSelection == -1 ) - { - ASSERT( !m_vbSelected[pn].empty() ); - m_vbSelected[pn][0] = true; - } - } - default: - break; - } - - ResetFocusFromSelection( pn ); - - PositionUnderlines( pn ); -} - -void OptionRow::PositionUnderlines( PlayerNumber pn ) -{ - vector &vpUnderlines = m_Underline[pn]; - if( vpUnderlines.empty() ) - return; - - PlayerNumber pnTakeSelectedFrom = m_pHand->m_Def.m_bOneChoiceForAllPlayers ? PLAYER_1 : pn; - - for( int i=0; i<(int)vpUnderlines.size(); i++ ) - { - OptionsCursor& ul = *vpUnderlines[i]; - - int iChoiceWithFocus = (m_pHand->m_Def.m_layoutType == LAYOUT_SHOW_ONE_IN_ROW) ? GetChoiceInRowWithFocus(pn) : i; - - float fAlpha = 1.0f; - if( m_pHand->m_Def.m_layoutType == LAYOUT_SHOW_ONE_IN_ROW ) - { - bool bRowEnabled = m_pHand->m_Def.m_vEnabledForPlayers.find(pn) != m_pHand->m_Def.m_vEnabledForPlayers.end(); - - if( !m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - { - if( m_bRowHasFocus[pn] ) fAlpha = m_pParentType->COLOR_SELECTED.GetValue().a; - else if( bRowEnabled ) fAlpha = m_pParentType->COLOR_NOT_SELECTED.GetValue().a; - else fAlpha = m_pParentType->COLOR_DISABLED.GetValue().a; - } - } - - // Don't tween X movement and color changes. - ul.StopTweening(); - - int iWidth, iX, iY; - GetWidthXY( pn, iChoiceWithFocus, iWidth, iX, iY ); - ul.SetX( (float)iX ); - // only set alpha, in case a theme tries to color underlines. -aj - ul.SetDiffuseAlpha( fAlpha ); - - ASSERT( m_vbSelected[pnTakeSelectedFrom].size() == m_pHand->m_Def.m_vsChoices.size() ); - - bool bSelected = (iChoiceWithFocus==-1) ? false : m_vbSelected[pnTakeSelectedFrom][ iChoiceWithFocus ]; - bool bVisible = bSelected && GAMESTATE->IsHumanPlayer(pn); - - ul.BeginTweening( m_pParentType->TWEEN_SECONDS ); - ul.SetVisible( bVisible ); - ul.SetBarWidth( iWidth ); - } -} - -void OptionRow::PositionIcons( PlayerNumber pn ) -{ - ModIcon *pIcon = m_ModIcons[pn]; - if( pIcon == NULL ) - return; - - pIcon->SetX( m_pParentType->MOD_ICON_X.GetValue(pn) ); -} - -// This is called when the focus changes, to update "long row" text. -void OptionRow::UpdateText( PlayerNumber p ) -{ - switch( m_pHand->m_Def.m_layoutType ) - { - case LAYOUT_SHOW_ONE_IN_ROW: - { - unsigned pn = m_pHand->m_Def.m_bOneChoiceForAllPlayers ? 0 : p; - int iChoiceWithFocus = m_iChoiceInRowWithFocus[pn]; - if( iChoiceWithFocus == -1 ) - break; - - RString sText = GetThemedItemText( iChoiceWithFocus ); - - // If player_no is 2 and there is no player 1: - int index = min( pn, m_textItems.size()-1 ); - - // TODO: Always have one textItem for each player - - m_textItems[index]->SetText( sText ); - } - default: break; - } -} - -void OptionRow::SetRowHasFocus( PlayerNumber pn, bool bRowHasFocus ) -{ - m_bRowHasFocus[pn] = bRowHasFocus; -} - -void OptionRow::SetDestination( Actor::TweenState &ts, bool bTween ) -{ - if( m_Frame.DestTweenState() != ts ) - { - m_Frame.StopTweening(); - if( bTween && m_pParentType->TWEEN_SECONDS != 0 ) - m_Frame.BeginTweening( m_pParentType->TWEEN_SECONDS ); - m_Frame.DestTweenState() = ts; - } -} - -void OptionRow::UpdateEnabledDisabled() -{ - bool bThisRowHasFocusByAny = false; - FOREACH_HumanPlayer( p ) - bThisRowHasFocusByAny |= m_bRowHasFocus[p]; - - bool bThisRowHasFocusByAll = true; - FOREACH_HumanPlayer( p ) - bThisRowHasFocusByAll &= m_bRowHasFocus[p]; - - bool bRowEnabled = !m_pHand->m_Def.m_vEnabledForPlayers.empty(); - - // Don't tween selection colors at all. - RString sCmdName; - if( bThisRowHasFocusByAny ) sCmdName = "GainFocus"; - else if( bRowEnabled ) sCmdName = "LoseFocus"; - else sCmdName = "Disabled"; - - RageColor color; - if( bThisRowHasFocusByAny ) color = m_pParentType->COLOR_SELECTED; - else if( bRowEnabled ) color = m_pParentType->COLOR_NOT_SELECTED; - else color = m_pParentType->COLOR_DISABLED; - - m_sprFrame->PlayCommand( sCmdName ); - m_textTitle->PlayCommand( sCmdName ); - - for( unsigned j=0; jPlayCommand( "GainFocus" ); - else - m_textItems[j]->PlayCommand( "LoseFocus" ); - } - - switch( m_pHand->m_Def.m_layoutType ) - { - case LAYOUT_SHOW_ALL_IN_ROW: - for( unsigned j=0; jDestTweenState().diffuse[0] == color ) - continue; - - m_textItems[j]->StopTweening(); - m_textItems[j]->BeginTweening( m_pParentType->TWEEN_SECONDS ); - m_textItems[j]->SetDiffuse( color ); - } - - break; - case LAYOUT_SHOW_ONE_IN_ROW: - FOREACH_HumanPlayer( pn ) - { - bRowEnabled = m_pHand->m_Def.m_vEnabledForPlayers.find(pn) != m_pHand->m_Def.m_vEnabledForPlayers.end(); - - if( !m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - { - if( m_bRowHasFocus[pn] ) color = m_pParentType->COLOR_SELECTED; - else if( bRowEnabled ) color = m_pParentType->COLOR_NOT_SELECTED; - else color = m_pParentType->COLOR_DISABLED; - } - - unsigned item_no = m_pHand->m_Def.m_bOneChoiceForAllPlayers ? 0 : pn; - - // If player_no is 2 and there is no player 1: - item_no = min( item_no, m_textItems.size()-1 ); - - BitmapText &bt = *m_textItems[item_no]; - - if( bt.DestTweenState().diffuse[0] != color ) - { - bt.StopTweening(); - bt.BeginTweening( m_pParentType->TWEEN_SECONDS ); - bt.SetDiffuse( color ); - } - } - break; - default: - ASSERT(0); - } -} - -void OptionRow::Update( float fDeltaTime ) -{ - ActorFrame::Update( fDeltaTime ); - - float fAlpha = m_Frame.GetCurrentDiffuseAlpha(); - m_Frame.SetBaseAlpha( fAlpha ); -} - -void OptionRow::SetModIcon( PlayerNumber pn, const RString &sText, GameCommand &gc ) -{ - // update row frame - Message msg( "Refresh" ); - msg.SetParam( "GameCommand", &gc ); - msg.SetParam( "Text", sText ); - m_sprFrame->HandleMessage( msg ); - if( m_ModIcons[pn] != NULL ) - m_ModIcons[pn]->Set( sText ); -} - -const BitmapText &OptionRow::GetTextItemForRow( PlayerNumber pn, int iChoiceOnRow ) const -{ - bool bOneChoice = m_pHand->m_Def.m_bOneChoiceForAllPlayers; - int index = -1; - switch( m_pHand->m_Def.m_layoutType ) - { - case LAYOUT_SHOW_ONE_IN_ROW: - index = bOneChoice ? 0 : pn; - // If only P2 is enabled, his selections will be in index 0. - if( m_textItems.size() == 1 ) - index = 0; - break; - case LAYOUT_SHOW_ALL_IN_ROW: - index = iChoiceOnRow; - break; - default: - ASSERT(0); - } - - ASSERT_M( index < (int)m_textItems.size(), ssprintf("%i < %i", index, (int)m_textItems.size() ) ); - return *m_textItems[index]; -} - -void OptionRow::GetWidthXY( PlayerNumber pn, int iChoiceOnRow, int &iWidthOut, int &iXOut, int &iYOut ) const -{ - const BitmapText &text = GetTextItemForRow( pn, iChoiceOnRow ); - - iWidthOut = lrintf( text.GetZoomedWidth() ); - iXOut = lrintf( text.GetDestX() ); - iYOut = lrintf( m_Frame.GetDestY() ); -} - -int OptionRow::GetOneSelection( PlayerNumber pn, bool bAllowFail ) const -{ - for( unsigned i=0; i &vb = m_vbSelected[pn]; - if( vb.empty() ) - return; - FOREACH( bool, vb, b ) - *b = false; - vb[iChoice] = true; -} - -void OptionRow::SetOneSharedSelection( int iChoice ) -{ - FOREACH_PlayerNumber( pn ) - SetOneSelection( pn, iChoice ); -} - -void OptionRow::SetOneSharedSelectionIfPresent( const RString &sChoice ) -{ - for( unsigned i=0; im_Def.m_vsChoices.size(); i++ ) - { - if( sChoice == m_pHand->m_Def.m_vsChoices[i] ) - { - SetOneSharedSelection( i ); - break; - } - } -} - -int OptionRow::GetChoiceInRowWithFocus( PlayerNumber pn ) const -{ - if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - pn = PLAYER_1; - if( m_pHand->m_Def.m_vsChoices.empty() ) - return -1; - int iChoice = m_iChoiceInRowWithFocus[pn]; - return iChoice; -} - -int OptionRow::GetChoiceInRowWithFocusShared() const -{ - return GetChoiceInRowWithFocus( PLAYER_1 ); -} - -void OptionRow::SetChoiceInRowWithFocus( PlayerNumber pn, int iChoice ) -{ - if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - pn = PLAYER_1; - ASSERT(iChoice >= 0 && iChoice < (int)m_pHand->m_Def.m_vsChoices.size()); - m_iChoiceInRowWithFocus[pn] = iChoice; - - UpdateText( pn ); - //PositionUnderlines( pn ); -} - -void OptionRow::ResetFocusFromSelection( PlayerNumber pn ) -{ - int iSelection = -1; - switch( m_pHand->m_Def.m_selectType ) - { - case SELECT_ONE: - // Import the focus from the selected option. - iSelection = GetOneSelection( pn, true ); - default: - break; - } - - // HACK: Set focus to one item in the row, which is "go down" - if( m_bFirstItemGoesDown ) - iSelection = 0; - - if( iSelection != -1 ) - SetChoiceInRowWithFocus( pn, iSelection ); -} - -bool OptionRow::GetSelected( PlayerNumber pn, int iChoice ) const -{ - if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - pn = PLAYER_1; - return m_vbSelected[pn][iChoice]; -} - -const OptionRowDefinition &OptionRow::GetRowDef() const -{ - return m_pHand->m_Def; -} - -OptionRowDefinition &OptionRow::GetRowDef() -{ - return m_pHand->m_Def; -} - -void OptionRow::SetSelected( PlayerNumber pn, int iChoice, bool b ) -{ - if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) - pn = PLAYER_1; - m_vbSelected[pn][iChoice] = b; -} - -void OptionRow::SetExitText( RString sExitText ) -{ - BitmapText *bt = m_textItems.back(); - bt->SetText( sExitText ); -} - -void OptionRow::Reload() -{ - // TODO: Nothing uses this yet and it causes skips when changing options. - /* - if( m_pHand->m_Def.m_bExportOnChange ) - { - bool bRowHasFocus[NUM_PLAYERS]; - ZERO( bRowHasFocus ); - ExportOptions( vpns, bRowHasFocus ); - } - */ - - switch( m_pHand->Reload() ) - { - case OptionRowHandler::RELOAD_CHANGED_NONE: - break; - - case OptionRowHandler::RELOAD_CHANGED_ALL: - { - ChoicesChanged( m_RowType ); - - vector vpns; - FOREACH_HumanPlayer( p ) - vpns.push_back( p ); - ImportOptions( vpns ); - FOREACH_HumanPlayer( p ) - AfterImportOptions( p ); - // fall through - } - - case OptionRowHandler::RELOAD_CHANGED_ENABLED: - UpdateEnabledDisabled(); - FOREACH_HumanPlayer( pn ) - PositionUnderlines( pn ); - break; - } - - // TODO: Nothing uses this yet and it causes skips when changing options. - /* - if( m_pHand->m_Def.m_bExportOnChange ) - { - bool bRowHasFocus[NUM_PLAYERS]; - ZERO( bRowHasFocus ); - ExportOptions( vpns, bRowHasFocus ); - } - */ -} - -void OptionRow::HandleMessage( const Message &msg ) -{ - bool bReload = false; - FOREACH_CONST( RString, m_pHand->m_vsReloadRowMessages, m ) - { - if( *m == msg.GetName() ) - bReload = true; - } - if( bReload ) - Reload(); - - ActorFrame::HandleMessage( msg ); -} - - -/* Hack: the NextRow entry is never set, and should be transparent. - * Remove it, and readd it below. */ -#define ERASE_ONE_BOOL_AT_FRONT_IF_NEEDED( vbSelected ) \ - if( GetFirstItemGoesDown() ) \ - vbSelected.erase( vbSelected.begin() ); -#define INSERT_ONE_BOOL_AT_FRONT_IF_NEEDED( vbSelected ) \ - if( GetFirstItemGoesDown() ) \ - vbSelected.insert( vbSelected.begin(), false ); - -void OptionRow::ImportOptions( const vector &vpns ) -{ - ASSERT( m_pHand->m_Def.m_vsChoices.size() > 0 ); - - FOREACH_CONST( PlayerNumber, vpns, iter ) - { - PlayerNumber p = *iter; - - FOREACH( bool, m_vbSelected[p], b ) - *b = false; - - ASSERT( m_vbSelected[p].size() == m_pHand->m_Def.m_vsChoices.size() ); - ERASE_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); - } - - m_pHand->ImportOption( this, vpns, m_vbSelected ); - - FOREACH_CONST( PlayerNumber, vpns, iter ) - { - PlayerNumber p = *iter; - - INSERT_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); - VerifySelected( m_pHand->m_Def.m_selectType, m_vbSelected[p], m_pHand->m_Def.m_sName ); - } -} - -int OptionRow::ExportOptions( const vector &vpns, bool bRowHasFocus[NUM_PLAYERS] ) -{ - ASSERT( m_pHand->m_Def.m_vsChoices.size() > 0 ); - - int iChangeMask = 0; - - FOREACH_CONST( PlayerNumber, vpns, iter ) - { - PlayerNumber p = *iter; - bool bFocus = bRowHasFocus[p]; - - VerifySelected( m_pHand->m_Def.m_selectType, m_vbSelected[p], m_pHand->m_Def.m_sName ); - ASSERT( m_vbSelected[p].size() == m_pHand->m_Def.m_vsChoices.size() ); - ERASE_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); - - // SELECT_NONE rows get exported if they have focus when the user presses Start. - int iChoice = GetChoiceInRowWithFocus( p ); - if( m_pHand->m_Def.m_selectType == SELECT_NONE && bFocus ) - m_vbSelected[p][iChoice] = true; - } - - iChangeMask |= m_pHand->ExportOption( vpns, m_vbSelected ); - - FOREACH_CONST( PlayerNumber, vpns, iter ) - { - PlayerNumber p = *iter; - bool bFocus = bRowHasFocus[p]; - - int iChoice = GetChoiceInRowWithFocus( p ); - if( m_pHand->m_Def.m_selectType == SELECT_NONE && bFocus ) - m_vbSelected[p][iChoice] = false; - - INSERT_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); - } - - return iChangeMask; -} - -// lua start -#include "LuaBinding.h" - -class LunaOptionRow: public Luna -{ -public: - DEFINE_METHOD( FirstItemGoesDown, GetFirstItemGoesDown() ) - static int GetChoiceInRowWithFocus( T* p, lua_State *L ) { lua_pushnumber( L, p->GetChoiceInRowWithFocus(Enum::Check(L, 1)) ); return 1; } - DEFINE_METHOD( GetLayoutType, GetHandler()->m_Def.m_layoutType ) - static int GetName( T* p, lua_State *L ) { lua_pushstring( L, p->GetHandler()->m_Def.m_sName ); return 1; } - static int GetNumChoices( T* p, lua_State *L ) { lua_pushnumber( L, p->GetHandler()->m_Def.m_vsChoices.size() ); return 1; } - DEFINE_METHOD( GetSelectType, GetHandler()->m_Def.m_selectType ) - DEFINE_METHOD( GetRowTitle, GetRowTitle() ) - static int HasFocus( T* p, lua_State *L ) { lua_pushboolean( L, p->GetRowHasFocus(Enum::Check(L, 1)) ); return 1; } - static int OneChoiceForAllPlayers( T* p, lua_State *L ) { lua_pushboolean( L, p->GetHandler()->m_Def.m_bOneChoiceForAllPlayers ); return 1; } - - LunaOptionRow() - { - ADD_METHOD( FirstItemGoesDown ); - ADD_METHOD( GetChoiceInRowWithFocus ); - ADD_METHOD( GetLayoutType ); - ADD_METHOD( GetName ); - ADD_METHOD( GetNumChoices ); - ADD_METHOD( GetRowTitle ); - ADD_METHOD( GetSelectType ); - ADD_METHOD( HasFocus ); - ADD_METHOD( OneChoiceForAllPlayers ); - } -}; - -LUA_REGISTER_DERIVED_CLASS( OptionRow, ActorFrame ) -// lua end - -/* - * (c) 2001-2004 Chris Danford - * 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. - */ +#include "global.h" +#include "OptionRow.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "Foreach.h" +#include "OptionRowHandler.h" +#include "CommonMetrics.h" +#include "GameState.h" +#include "Song.h" +#include "Course.h" +#include "Style.h" +#include "ActorUtil.h" + +const RString NEXT_ROW_NAME = "NextRow"; +const RString EXIT_NAME = "Exit"; + +RString OptionRow::GetThemedItemText( int iChoice ) const +{ + RString s = m_pHand->GetThemedItemText( iChoice ); + + // HACK: Always theme the NEXT_ROW and EXIT items. + if( m_bFirstItemGoesDown && iChoice == 0 ) + s = CommonMetrics::LocalizeOptionItem( NEXT_ROW_NAME, false ); + else if( m_RowType == OptionRow::RowType_Exit ) + s = CommonMetrics::LocalizeOptionItem( EXIT_NAME, false ); + + return s; +} + +RString ITEMS_LONG_ROW_X_NAME( size_t p ) { return ssprintf("ItemsLongRowP%dX",int(p+1)); } +RString MOD_ICON_X_NAME( size_t p ) { return ssprintf("ModIconP%dX",int(p+1)); } + +OptionRow::OptionRow( const OptionRowType *pSource ) +{ + m_pParentType = pSource; + m_pHand = NULL; + + m_textTitle = NULL; + ZERO( m_ModIcons ); + + Clear(); + this->AddChild( &m_Frame ); + + m_tsDestination.Init(); +} + +OptionRow::~OptionRow() +{ + Clear(); +} + +void OptionRow::Clear() +{ + ActorFrame::RemoveAllChildren(); + + FOREACH_PlayerNumber( p ) + m_vbSelected[p].clear(); + + m_Frame.DeleteAllChildren(); + m_textItems.clear(); + FOREACH_PlayerNumber( p ) + m_Underline[p].clear(); + + if( m_pHand != NULL ) + { + FOREACH_CONST( RString, m_pHand->m_vsReloadRowMessages, m ) + MESSAGEMAN->Unsubscribe( this, *m ); + } + SAFE_DELETE( m_pHand ); + + m_bFirstItemGoesDown = false; + ZERO( m_bRowHasFocus ); + ZERO( m_iChoiceInRowWithFocus ); +} + +void OptionRowType::Load( const RString &sMetricsGroup, Actor *pParent ) +{ + m_sMetricsGroup = sMetricsGroup; + + ITEMS_START_X .Load(sMetricsGroup,"ItemsStartX"); + ITEMS_END_X .Load(sMetricsGroup,"ItemsEndX"); + ITEMS_GAP_X .Load(sMetricsGroup,"ItemsGapX"); + ITEMS_MIN_BASE_ZOOM .Load(sMetricsGroup,"ItemsMinBaseZoom"); + ITEMS_LONG_ROW_X .Load(sMetricsGroup,ITEMS_LONG_ROW_X_NAME,NUM_PLAYERS); + ITEMS_LONG_ROW_SHARED_X .Load(sMetricsGroup,"ItemsLongRowSharedX"); + MOD_ICON_X .Load(sMetricsGroup,MOD_ICON_X_NAME,NUM_PLAYERS); + COLOR_SELECTED .Load(sMetricsGroup,"ColorSelected"); + COLOR_NOT_SELECTED .Load(sMetricsGroup,"ColorNotSelected"); + COLOR_DISABLED .Load(sMetricsGroup,"ColorDisabled"); + TWEEN_SECONDS .Load(sMetricsGroup,"TweenSeconds"); + SHOW_BPM_IN_SPEED_TITLE .Load(sMetricsGroup,"ShowBpmInSpeedTitle"); + SHOW_MOD_ICONS .Load(sMetricsGroup,"ShowModIcons"); + SHOW_UNDERLINES .Load(sMetricsGroup,"ShowUnderlines"); + MOD_ICON_METRICS_GROUP .Load(sMetricsGroup,"ModIconMetricsGroup"); + + m_textItem.LoadFromFont( THEME->GetPathF(sMetricsGroup,"Item") ); + m_textItem.SetName( "Item" ); + ActorUtil::LoadAllCommands( m_textItem, sMetricsGroup ); + + if( SHOW_UNDERLINES ) + { + FOREACH_PlayerNumber( p ) + m_Underline[p].Load( "OptionsUnderline" + PlayerNumberToString(p), false ); + } + + m_textTitle.LoadFromFont( THEME->GetPathF(sMetricsGroup,"title") ); + m_textTitle.SetName( "Title" ); + ActorUtil::LoadAllCommandsAndSetXY( m_textTitle, sMetricsGroup ); + + m_sprFrame.Load( ActorUtil::MakeActor( THEME->GetPathG(sMetricsGroup,"Frame"), pParent ) ); + m_sprFrame->SetName( "Frame" ); + ActorUtil::LoadAllCommandsAndSetXY( m_sprFrame, sMetricsGroup ); + + if( SHOW_MOD_ICONS ) + { + m_ModIcon.Load( MOD_ICON_METRICS_GROUP ); + m_ModIcon.SetName( "ModIcon" ); + ActorUtil::LoadAllCommands( m_ModIcon, sMetricsGroup ); + } +} + +void OptionRow::LoadNormal( OptionRowHandler *pHand, bool bFirstItemGoesDown ) +{ + m_RowType = OptionRow::RowType_Normal; + m_pHand = pHand; + m_bFirstItemGoesDown = bFirstItemGoesDown; + + FOREACH_CONST( RString, m_pHand->m_vsReloadRowMessages, m ) + MESSAGEMAN->Subscribe( this, *m ); + + ChoicesChanged( RowType_Normal ); +} + +void OptionRow::LoadExit() +{ + m_RowType = OptionRow::RowType_Exit; + OptionRowHandler *pHand = OptionRowHandlerUtil::MakeNull(); + pHand->m_Def.m_selectType = SELECT_NONE; + pHand->m_Def.m_sName = EXIT_NAME; + pHand->m_Def.m_vsChoices.push_back( EXIT_NAME ); + pHand->m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + pHand->m_Def.m_bOneChoiceForAllPlayers = true; + m_pHand = pHand; + + ChoicesChanged( RowType_Exit ); +} + +void OptionRow::ChoicesChanged( RowType type ) +{ + ASSERT_M( !m_pHand->m_Def.m_vsChoices.empty(), m_pHand->m_Def.m_sName + " has no choices" ); + + // Remove the NextRow marker before reloading choices + if( m_pHand->m_Def.m_vsChoices[0] == NEXT_ROW_NAME ) + { + m_pHand->m_Def.m_vsChoices.erase( m_pHand->m_Def.m_vsChoices.begin() ); + FOREACH_PlayerNumber( p ) + m_vbSelected[p].erase( m_vbSelected[p].begin() ); + } + + FOREACH_PlayerNumber( p ) + { + vector &vbSelected = m_vbSelected[p]; + vbSelected.resize( 0 ); + vbSelected.resize( m_pHand->m_Def.m_vsChoices.size(), false ); + + // set select the first item if a SELECT_ONE row + if( vbSelected.size() && m_pHand->m_Def.m_selectType == SELECT_ONE ) + vbSelected[0] = true; + } + + // TRICKY: Insert a down arrow as the first choice in the row. + if( m_bFirstItemGoesDown ) + { + m_pHand->m_Def.m_vsChoices.insert( m_pHand->m_Def.m_vsChoices.begin(), NEXT_ROW_NAME ); + FOREACH_PlayerNumber( p ) + m_vbSelected[p].insert( m_vbSelected[p].begin(), false ); + } + + InitText( type ); + + // When choices change, the old focus position is meaningless; reset it. + FOREACH_PlayerNumber( p ) + SetChoiceInRowWithFocus( p, 0 ); + + m_textTitle->SetText( GetRowTitle() ); +} + +RString OptionRow::GetRowTitle() const +{ + RString sTitle = m_pHand->OptionTitle(); + + // HACK: tack the BPM onto the name of the speed line + if( m_pHand->m_Def.m_sName.CompareNoCase("speed")==0 ) + { + bool bShowBpmInSpeedTitle = m_pParentType->SHOW_BPM_IN_SPEED_TITLE; + + if( GAMESTATE->m_pCurCourse ) + { + const Trail* pTrail = GAMESTATE->m_pCurTrail[GAMESTATE->GetMasterPlayerNumber()]; + ASSERT( pTrail != NULL ); + const int iNumCourseEntries = pTrail->m_vEntries.size(); + if( iNumCourseEntries > CommonMetrics::MAX_COURSE_ENTRIES_BEFORE_VARIOUS ) + bShowBpmInSpeedTitle = false; + } + + if( bShowBpmInSpeedTitle ) + { + DisplayBpms bpms; + if( GAMESTATE->m_pCurSong ) + { + const Song* pSong = GAMESTATE->m_pCurSong; + pSong->GetDisplayBpms( bpms ); + } + else if( GAMESTATE->m_pCurCourse ) + { + const Course *pCourse = GAMESTATE->m_pCurCourse; + StepsType st = GAMESTATE->GetCurrentStyle()->m_StepsType; + const Trail* pTrail = pCourse->GetTrail( st ); + ASSERT( pTrail ); + pTrail->GetDisplayBpms( bpms ); + } + + if( bpms.IsSecret() ) + sTitle += ssprintf( " (??" "?)" ); // split so gcc doesn't think this is a trigraph + else if( bpms.BpmIsConstant() ) + sTitle += ssprintf( " (%.0f)", bpms.GetMin() ); + else + sTitle += ssprintf( " (%.0f-%.0f)", bpms.GetMin(), bpms.GetMax() ); + } + } + + return sTitle; +} + +/* Set up text, underlines and titles for options. This can be called as soon as + * m_pHand->m_Def is available. */ +void OptionRow::InitText( RowType type ) +{ + /* If we have elements already, we're being updated from a new set of options. + * Delete the old ones. */ + m_Frame.DeleteAllChildren(); + m_textItems.clear(); + FOREACH_PlayerNumber( p ) + m_Underline[p].clear(); + + m_textTitle = new BitmapText( m_pParentType->m_textTitle ); + m_Frame.AddChild( m_textTitle ); + + m_sprFrame = m_pParentType->m_sprFrame->Copy(); + m_sprFrame->SetDrawOrder(-1); // under title + m_Frame.AddChild( m_sprFrame ); + + if( m_pParentType->SHOW_MOD_ICONS ) + { + switch( m_RowType ) + { + case RowType_Normal: + FOREACH_PlayerNumber( p ) + { + m_ModIcons[p] = new ModIcon( m_pParentType->m_ModIcon ); + m_ModIcons[p]->SetDrawOrder(-1); // under title + m_ModIcons[p]->PlayCommand( "On" ); + + m_Frame.AddChild( m_ModIcons[p] ); + + GameCommand gc; + SetModIcon( p, "", gc ); + } + break; + case RowType_Exit: + break; + } + } + + // If the items will go off the edge of the screen, then force LAYOUT_SHOW_ONE_IN_ROW. + float fBaseZoom = 1.0f; + { + BitmapText bt( m_pParentType->m_textItem ); + bt.PlayCommand( "On" ); + + // Figure out the width of the row. + float fWidth = 0; + for( unsigned c=0; cm_Def.m_vsChoices.size(); c++ ) + { + RString sText = GetThemedItemText( c ); + bt.SetText( sText ); + + fWidth += bt.GetZoomedWidth(); + + if( c != m_pHand->m_Def.m_vsChoices.size()-1 ) + fWidth += m_pParentType->ITEMS_GAP_X; + } + + // Try to fit everything on one line. + float fTotalWidth = m_pParentType->ITEMS_END_X - m_pParentType->ITEMS_START_X; + if( fWidth > fTotalWidth ) + { + float fPossibleBaseZoom = fTotalWidth / fWidth; + if( fPossibleBaseZoom >= m_pParentType->ITEMS_MIN_BASE_ZOOM ) + fBaseZoom = fPossibleBaseZoom; + else + m_pHand->m_Def.m_layoutType = LAYOUT_SHOW_ONE_IN_ROW; + } + } + + // load m_textItems + switch( m_pHand->m_Def.m_layoutType ) + { + case LAYOUT_SHOW_ONE_IN_ROW: + // init text + FOREACH_PlayerNumber( p ) + { + BitmapText *pText = new BitmapText( m_pParentType->m_textItem ); + m_textItems.push_back( pText ); + + pText->PlayCommand( "On" ); + + if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + { + pText->SetX( m_pParentType->ITEMS_LONG_ROW_SHARED_X ); + break; // only initialize one item since it's shared + } + else + { + pText->SetX( m_pParentType->ITEMS_LONG_ROW_X.GetValue(p) ); + } + + // Set the text now, so SetWidthXY below is correct. + UpdateText( p ); + + // init underlines + if( m_pParentType->SHOW_UNDERLINES && GetRowType() != OptionRow::RowType_Exit ) + { + OptionsCursor *pCursor = new OptionsCursor( m_pParentType->m_Underline[p] ); + m_Underline[p].push_back( pCursor ); + + int iWidth, iX, iY; + GetWidthXY( p, 0, iWidth, iX, iY ); + pCursor->SetX( float(iX) ); + pCursor->SetBarWidth( iWidth ); + } + } + break; + + case LAYOUT_SHOW_ALL_IN_ROW: + { + float fX = m_pParentType->ITEMS_START_X; + for( unsigned c=0; cm_Def.m_vsChoices.size(); c++ ) + { + // init text + BitmapText *bt = new BitmapText( m_pParentType->m_textItem ); + m_textItems.push_back( bt ); + bt->SetBaseZoomX( fBaseZoom ); + bt->PlayCommand( "On" ); + // Set text after running OnCommand so e.g. uppercase,true works -aj + RString sText = GetThemedItemText( c ); + bt->SetText( sText ); + + // set the X position of each item in the line + float fItemWidth = bt->GetZoomedWidth(); + fX += fItemWidth/2; + bt->SetX( fX ); + + // init underlines + if( m_pParentType->SHOW_UNDERLINES ) + { + FOREACH_PlayerNumber( p ) + { + OptionsCursor *ul = new OptionsCursor( m_pParentType->m_Underline[p] ); + m_Underline[p].push_back( ul ); + ul->SetX( fX ); + ul->SetBarWidth( int(fItemWidth) ); + } + } + + fX += fItemWidth/2 + m_pParentType->ITEMS_GAP_X * fBaseZoom; + } + } + break; + + default: + ASSERT(0); + } + + for( unsigned c=0; cSetText( GetRowTitle() ); + m_textTitle->PlayCommand( "On" ); + + m_sprFrame->PlayCommand( "On" ); + + m_Frame.SortByDrawOrder(); + this->SortByDrawOrder(); +} + +// After importing options, choose which item is focused. +void OptionRow::AfterImportOptions( PlayerNumber pn ) +{ + /* We load items for both players on start, since we don't know which players + * will be joined when we're displayed. Hide items for inactive players. */ + if( m_pHand->m_Def.m_layoutType == LAYOUT_SHOW_ONE_IN_ROW && + !m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + m_textItems[pn]->SetVisible( GAMESTATE->IsHumanPlayer(pn) ); + + // Hide underlines for disabled players. + if( !GAMESTATE->IsHumanPlayer(pn) ) + for( unsigned c=0; cSetVisible( false ); + + // Make all selections the same if bOneChoiceForAllPlayers. + // Hack: we only import active players, so if only player 2 is imported, + // we need to copy p2 to p1, not p1 to p2. + if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + { + PlayerNumber pnCopyFrom = GAMESTATE->GetMasterPlayerNumber(); + if( GAMESTATE->GetMasterPlayerNumber() == PLAYER_INVALID ) + pnCopyFrom = PLAYER_1; + FOREACH_PlayerNumber( p ) + m_vbSelected[p] = m_vbSelected[pnCopyFrom]; + } + + switch( m_pHand->m_Def.m_selectType ) + { + case SELECT_ONE: + { + // Make sure the row actually has a selection. + int iSelection = GetOneSelection(pn, true); + if( iSelection == -1 ) + { + ASSERT( !m_vbSelected[pn].empty() ); + m_vbSelected[pn][0] = true; + } + } + default: + break; + } + + ResetFocusFromSelection( pn ); + + PositionUnderlines( pn ); +} + +void OptionRow::PositionUnderlines( PlayerNumber pn ) +{ + vector &vpUnderlines = m_Underline[pn]; + if( vpUnderlines.empty() ) + return; + + PlayerNumber pnTakeSelectedFrom = m_pHand->m_Def.m_bOneChoiceForAllPlayers ? PLAYER_1 : pn; + + for( int i=0; i<(int)vpUnderlines.size(); i++ ) + { + OptionsCursor& ul = *vpUnderlines[i]; + + int iChoiceWithFocus = (m_pHand->m_Def.m_layoutType == LAYOUT_SHOW_ONE_IN_ROW) ? GetChoiceInRowWithFocus(pn) : i; + + float fAlpha = 1.0f; + if( m_pHand->m_Def.m_layoutType == LAYOUT_SHOW_ONE_IN_ROW ) + { + bool bRowEnabled = m_pHand->m_Def.m_vEnabledForPlayers.find(pn) != m_pHand->m_Def.m_vEnabledForPlayers.end(); + + if( !m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + { + if( m_bRowHasFocus[pn] ) fAlpha = m_pParentType->COLOR_SELECTED.GetValue().a; + else if( bRowEnabled ) fAlpha = m_pParentType->COLOR_NOT_SELECTED.GetValue().a; + else fAlpha = m_pParentType->COLOR_DISABLED.GetValue().a; + } + } + + // Don't tween X movement and color changes. + ul.StopTweening(); + + int iWidth, iX, iY; + GetWidthXY( pn, iChoiceWithFocus, iWidth, iX, iY ); + ul.SetX( (float)iX ); + // only set alpha, in case a theme tries to color underlines. -aj + ul.SetDiffuseAlpha( fAlpha ); + + ASSERT( m_vbSelected[pnTakeSelectedFrom].size() == m_pHand->m_Def.m_vsChoices.size() ); + + bool bSelected = (iChoiceWithFocus==-1) ? false : m_vbSelected[pnTakeSelectedFrom][ iChoiceWithFocus ]; + bool bVisible = bSelected && GAMESTATE->IsHumanPlayer(pn); + + ul.BeginTweening( m_pParentType->TWEEN_SECONDS ); + ul.SetVisible( bVisible ); + ul.SetBarWidth( iWidth ); + } +} + +void OptionRow::PositionIcons( PlayerNumber pn ) +{ + ModIcon *pIcon = m_ModIcons[pn]; + if( pIcon == NULL ) + return; + + pIcon->SetX( m_pParentType->MOD_ICON_X.GetValue(pn) ); +} + +// This is called when the focus changes, to update "long row" text. +void OptionRow::UpdateText( PlayerNumber p ) +{ + switch( m_pHand->m_Def.m_layoutType ) + { + case LAYOUT_SHOW_ONE_IN_ROW: + { + unsigned pn = m_pHand->m_Def.m_bOneChoiceForAllPlayers ? 0 : p; + int iChoiceWithFocus = m_iChoiceInRowWithFocus[pn]; + if( iChoiceWithFocus == -1 ) + break; + + RString sText = GetThemedItemText( iChoiceWithFocus ); + + // If player_no is 2 and there is no player 1: + int index = min( pn, m_textItems.size()-1 ); + + // TODO: Always have one textItem for each player + + m_textItems[index]->SetText( sText ); + } + default: break; + } +} + +void OptionRow::SetRowHasFocus( PlayerNumber pn, bool bRowHasFocus ) +{ + m_bRowHasFocus[pn] = bRowHasFocus; +} + +void OptionRow::SetDestination( Actor::TweenState &ts, bool bTween ) +{ + if( m_Frame.DestTweenState() != ts ) + { + m_Frame.StopTweening(); + if( bTween && m_pParentType->TWEEN_SECONDS != 0 ) + m_Frame.BeginTweening( m_pParentType->TWEEN_SECONDS ); + m_Frame.DestTweenState() = ts; + } +} + +void OptionRow::UpdateEnabledDisabled() +{ + bool bThisRowHasFocusByAny = false; + FOREACH_HumanPlayer( p ) + bThisRowHasFocusByAny |= m_bRowHasFocus[p]; + + bool bThisRowHasFocusByAll = true; + FOREACH_HumanPlayer( p ) + bThisRowHasFocusByAll &= m_bRowHasFocus[p]; + + bool bRowEnabled = !m_pHand->m_Def.m_vEnabledForPlayers.empty(); + + // Don't tween selection colors at all. + RString sCmdName; + if( bThisRowHasFocusByAny ) sCmdName = "GainFocus"; + else if( bRowEnabled ) sCmdName = "LoseFocus"; + else sCmdName = "Disabled"; + + RageColor color; + if( bThisRowHasFocusByAny ) color = m_pParentType->COLOR_SELECTED; + else if( bRowEnabled ) color = m_pParentType->COLOR_NOT_SELECTED; + else color = m_pParentType->COLOR_DISABLED; + + m_sprFrame->PlayCommand( sCmdName ); + m_textTitle->PlayCommand( sCmdName ); + + for( unsigned j=0; jPlayCommand( "GainFocus" ); + else + m_textItems[j]->PlayCommand( "LoseFocus" ); + } + + switch( m_pHand->m_Def.m_layoutType ) + { + case LAYOUT_SHOW_ALL_IN_ROW: + for( unsigned j=0; jDestTweenState().diffuse[0] == color ) + continue; + + m_textItems[j]->StopTweening(); + m_textItems[j]->BeginTweening( m_pParentType->TWEEN_SECONDS ); + m_textItems[j]->SetDiffuse( color ); + } + + break; + case LAYOUT_SHOW_ONE_IN_ROW: + FOREACH_HumanPlayer( pn ) + { + bRowEnabled = m_pHand->m_Def.m_vEnabledForPlayers.find(pn) != m_pHand->m_Def.m_vEnabledForPlayers.end(); + + if( !m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + { + if( m_bRowHasFocus[pn] ) color = m_pParentType->COLOR_SELECTED; + else if( bRowEnabled ) color = m_pParentType->COLOR_NOT_SELECTED; + else color = m_pParentType->COLOR_DISABLED; + } + + unsigned item_no = m_pHand->m_Def.m_bOneChoiceForAllPlayers ? 0 : pn; + + // If player_no is 2 and there is no player 1: + item_no = min( item_no, m_textItems.size()-1 ); + + BitmapText &bt = *m_textItems[item_no]; + + if( bt.DestTweenState().diffuse[0] != color ) + { + bt.StopTweening(); + bt.BeginTweening( m_pParentType->TWEEN_SECONDS ); + bt.SetDiffuse( color ); + } + } + break; + default: + ASSERT(0); + } +} + +void OptionRow::Update( float fDeltaTime ) +{ + ActorFrame::Update( fDeltaTime ); + + float fAlpha = m_Frame.GetCurrentDiffuseAlpha(); + m_Frame.SetBaseAlpha( fAlpha ); +} + +void OptionRow::SetModIcon( PlayerNumber pn, const RString &sText, GameCommand &gc ) +{ + // update row frame + Message msg( "Refresh" ); + msg.SetParam( "GameCommand", &gc ); + msg.SetParam( "Text", sText ); + m_sprFrame->HandleMessage( msg ); + if( m_ModIcons[pn] != NULL ) + m_ModIcons[pn]->Set( sText ); +} + +const BitmapText &OptionRow::GetTextItemForRow( PlayerNumber pn, int iChoiceOnRow ) const +{ + bool bOneChoice = m_pHand->m_Def.m_bOneChoiceForAllPlayers; + int index = -1; + switch( m_pHand->m_Def.m_layoutType ) + { + case LAYOUT_SHOW_ONE_IN_ROW: + index = bOneChoice ? 0 : pn; + // If only P2 is enabled, his selections will be in index 0. + if( m_textItems.size() == 1 ) + index = 0; + break; + case LAYOUT_SHOW_ALL_IN_ROW: + index = iChoiceOnRow; + break; + default: + ASSERT(0); + } + + ASSERT_M( index < (int)m_textItems.size(), ssprintf("%i < %i", index, (int)m_textItems.size() ) ); + return *m_textItems[index]; +} + +void OptionRow::GetWidthXY( PlayerNumber pn, int iChoiceOnRow, int &iWidthOut, int &iXOut, int &iYOut ) const +{ + const BitmapText &text = GetTextItemForRow( pn, iChoiceOnRow ); + + iWidthOut = lrintf( text.GetZoomedWidth() ); + iXOut = lrintf( text.GetDestX() ); + iYOut = lrintf( m_Frame.GetDestY() ); +} + +int OptionRow::GetOneSelection( PlayerNumber pn, bool bAllowFail ) const +{ + for( unsigned i=0; i &vb = m_vbSelected[pn]; + if( vb.empty() ) + return; + FOREACH( bool, vb, b ) + *b = false; + vb[iChoice] = true; +} + +void OptionRow::SetOneSharedSelection( int iChoice ) +{ + FOREACH_PlayerNumber( pn ) + SetOneSelection( pn, iChoice ); +} + +void OptionRow::SetOneSharedSelectionIfPresent( const RString &sChoice ) +{ + for( unsigned i=0; im_Def.m_vsChoices.size(); i++ ) + { + if( sChoice == m_pHand->m_Def.m_vsChoices[i] ) + { + SetOneSharedSelection( i ); + break; + } + } +} + +int OptionRow::GetChoiceInRowWithFocus( PlayerNumber pn ) const +{ + if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + pn = PLAYER_1; + if( m_pHand->m_Def.m_vsChoices.empty() ) + return -1; + int iChoice = m_iChoiceInRowWithFocus[pn]; + return iChoice; +} + +int OptionRow::GetChoiceInRowWithFocusShared() const +{ + return GetChoiceInRowWithFocus( PLAYER_1 ); +} + +void OptionRow::SetChoiceInRowWithFocus( PlayerNumber pn, int iChoice ) +{ + if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + pn = PLAYER_1; + ASSERT(iChoice >= 0 && iChoice < (int)m_pHand->m_Def.m_vsChoices.size()); + m_iChoiceInRowWithFocus[pn] = iChoice; + + UpdateText( pn ); + //PositionUnderlines( pn ); +} + +void OptionRow::ResetFocusFromSelection( PlayerNumber pn ) +{ + int iSelection = -1; + switch( m_pHand->m_Def.m_selectType ) + { + case SELECT_ONE: + // Import the focus from the selected option. + iSelection = GetOneSelection( pn, true ); + default: + break; + } + + // HACK: Set focus to one item in the row, which is "go down" + if( m_bFirstItemGoesDown ) + iSelection = 0; + + if( iSelection != -1 ) + SetChoiceInRowWithFocus( pn, iSelection ); +} + +bool OptionRow::GetSelected( PlayerNumber pn, int iChoice ) const +{ + if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + pn = PLAYER_1; + return m_vbSelected[pn][iChoice]; +} + +const OptionRowDefinition &OptionRow::GetRowDef() const +{ + return m_pHand->m_Def; +} + +OptionRowDefinition &OptionRow::GetRowDef() +{ + return m_pHand->m_Def; +} + +void OptionRow::SetSelected( PlayerNumber pn, int iChoice, bool b ) +{ + if( m_pHand->m_Def.m_bOneChoiceForAllPlayers ) + pn = PLAYER_1; + m_vbSelected[pn][iChoice] = b; +} + +void OptionRow::SetExitText( RString sExitText ) +{ + BitmapText *bt = m_textItems.back(); + bt->SetText( sExitText ); +} + +void OptionRow::Reload() +{ + // TODO: Nothing uses this yet and it causes skips when changing options. + /* + if( m_pHand->m_Def.m_bExportOnChange ) + { + bool bRowHasFocus[NUM_PLAYERS]; + ZERO( bRowHasFocus ); + ExportOptions( vpns, bRowHasFocus ); + } + */ + + switch( m_pHand->Reload() ) + { + case OptionRowHandler::RELOAD_CHANGED_NONE: + break; + + case OptionRowHandler::RELOAD_CHANGED_ALL: + { + ChoicesChanged( m_RowType ); + + vector vpns; + FOREACH_HumanPlayer( p ) + vpns.push_back( p ); + ImportOptions( vpns ); + FOREACH_HumanPlayer( p ) + AfterImportOptions( p ); + // fall through + } + + case OptionRowHandler::RELOAD_CHANGED_ENABLED: + UpdateEnabledDisabled(); + FOREACH_HumanPlayer( pn ) + PositionUnderlines( pn ); + break; + } + + // TODO: Nothing uses this yet and it causes skips when changing options. + /* + if( m_pHand->m_Def.m_bExportOnChange ) + { + bool bRowHasFocus[NUM_PLAYERS]; + ZERO( bRowHasFocus ); + ExportOptions( vpns, bRowHasFocus ); + } + */ +} + +void OptionRow::HandleMessage( const Message &msg ) +{ + bool bReload = false; + FOREACH_CONST( RString, m_pHand->m_vsReloadRowMessages, m ) + { + if( *m == msg.GetName() ) + bReload = true; + } + if( bReload ) + Reload(); + + ActorFrame::HandleMessage( msg ); +} + + +/* Hack: the NextRow entry is never set, and should be transparent. + * Remove it, and readd it below. */ +#define ERASE_ONE_BOOL_AT_FRONT_IF_NEEDED( vbSelected ) \ + if( GetFirstItemGoesDown() ) \ + vbSelected.erase( vbSelected.begin() ); +#define INSERT_ONE_BOOL_AT_FRONT_IF_NEEDED( vbSelected ) \ + if( GetFirstItemGoesDown() ) \ + vbSelected.insert( vbSelected.begin(), false ); + +void OptionRow::ImportOptions( const vector &vpns ) +{ + ASSERT( m_pHand->m_Def.m_vsChoices.size() > 0 ); + + FOREACH_CONST( PlayerNumber, vpns, iter ) + { + PlayerNumber p = *iter; + + FOREACH( bool, m_vbSelected[p], b ) + *b = false; + + ASSERT( m_vbSelected[p].size() == m_pHand->m_Def.m_vsChoices.size() ); + ERASE_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); + } + + m_pHand->ImportOption( this, vpns, m_vbSelected ); + + FOREACH_CONST( PlayerNumber, vpns, iter ) + { + PlayerNumber p = *iter; + + INSERT_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); + VerifySelected( m_pHand->m_Def.m_selectType, m_vbSelected[p], m_pHand->m_Def.m_sName ); + } +} + +int OptionRow::ExportOptions( const vector &vpns, bool bRowHasFocus[NUM_PLAYERS] ) +{ + ASSERT( m_pHand->m_Def.m_vsChoices.size() > 0 ); + + int iChangeMask = 0; + + FOREACH_CONST( PlayerNumber, vpns, iter ) + { + PlayerNumber p = *iter; + bool bFocus = bRowHasFocus[p]; + + VerifySelected( m_pHand->m_Def.m_selectType, m_vbSelected[p], m_pHand->m_Def.m_sName ); + ASSERT( m_vbSelected[p].size() == m_pHand->m_Def.m_vsChoices.size() ); + ERASE_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); + + // SELECT_NONE rows get exported if they have focus when the user presses Start. + int iChoice = GetChoiceInRowWithFocus( p ); + if( m_pHand->m_Def.m_selectType == SELECT_NONE && bFocus ) + m_vbSelected[p][iChoice] = true; + } + + iChangeMask |= m_pHand->ExportOption( vpns, m_vbSelected ); + + FOREACH_CONST( PlayerNumber, vpns, iter ) + { + PlayerNumber p = *iter; + bool bFocus = bRowHasFocus[p]; + + int iChoice = GetChoiceInRowWithFocus( p ); + if( m_pHand->m_Def.m_selectType == SELECT_NONE && bFocus ) + m_vbSelected[p][iChoice] = false; + + INSERT_ONE_BOOL_AT_FRONT_IF_NEEDED( m_vbSelected[p] ); + } + + return iChangeMask; +} + +// lua start +#include "LuaBinding.h" + +class LunaOptionRow: public Luna +{ +public: + DEFINE_METHOD( FirstItemGoesDown, GetFirstItemGoesDown() ) + static int GetChoiceInRowWithFocus( T* p, lua_State *L ) { lua_pushnumber( L, p->GetChoiceInRowWithFocus(Enum::Check(L, 1)) ); return 1; } + DEFINE_METHOD( GetLayoutType, GetHandler()->m_Def.m_layoutType ) + static int GetName( T* p, lua_State *L ) { lua_pushstring( L, p->GetHandler()->m_Def.m_sName ); return 1; } + static int GetNumChoices( T* p, lua_State *L ) { lua_pushnumber( L, p->GetHandler()->m_Def.m_vsChoices.size() ); return 1; } + DEFINE_METHOD( GetSelectType, GetHandler()->m_Def.m_selectType ) + DEFINE_METHOD( GetRowTitle, GetRowTitle() ) + static int HasFocus( T* p, lua_State *L ) { lua_pushboolean( L, p->GetRowHasFocus(Enum::Check(L, 1)) ); return 1; } + static int OneChoiceForAllPlayers( T* p, lua_State *L ) { lua_pushboolean( L, p->GetHandler()->m_Def.m_bOneChoiceForAllPlayers ); return 1; } + + LunaOptionRow() + { + ADD_METHOD( FirstItemGoesDown ); + ADD_METHOD( GetChoiceInRowWithFocus ); + ADD_METHOD( GetLayoutType ); + ADD_METHOD( GetName ); + ADD_METHOD( GetNumChoices ); + ADD_METHOD( GetRowTitle ); + ADD_METHOD( GetSelectType ); + ADD_METHOD( HasFocus ); + ADD_METHOD( OneChoiceForAllPlayers ); + } +}; + +LUA_REGISTER_DERIVED_CLASS( OptionRow, ActorFrame ) +// lua end + +/* + * (c) 2001-2004 Chris Danford + * 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. + */