#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; } 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 ); } 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 ); 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. */