📄 phonemeeditor_closecaption.cpp
字号:
}
void PhonemeEditor::CloseCaption_CleanupPhrases( bool prepareundo )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
// 2 pixel gap
float snap_epsilon = 2.49f / GetPixelsPerSecond();
if ( prepareundo )
{
SetDirty( true );
PushUndo();
}
CloseCaption_SortPhrases( false );
for ( int i = 0 ; i < m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ) ; i++ )
{
CCloseCaptionPhrase *phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
if ( !phrase )
continue;
CCloseCaptionPhrase *next = NULL;
if ( i < m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ) - 1 )
{
next = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i + 1 );
}
if ( phrase && next )
{
// Check for phrases close enough
float eps = next->GetStartTime() - phrase->GetEndTime();
if ( eps && eps <= snap_epsilon )
{
float t = (phrase->GetEndTime() + next->GetStartTime()) * 0.5;
phrase->SetEndTime( t );
next->SetStartTime( t );
}
}
float dt = phrase->GetEndTime() - phrase->GetStartTime();
if ( dt <= 0.01 )
{
phrase->SetEndTime( phrase->GetStartTime() + DEFAULT_PHRASE_LENGTH );
}
}
if ( prepareundo )
{
PushRedo();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : index -
//-----------------------------------------------------------------------------
void PhonemeEditor::CloseCaption_SetClickedPhrase( int index, int token )
{
m_nClickedPhrase = index;
m_nClickedPhraseToken = token;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : CCloseCaptionPhrase *phrase -
// Output : int
//-----------------------------------------------------------------------------
int PhonemeEditor::CloseCaption_IndexOfPhrase( CCloseCaptionPhrase *phrase )
{
return m_Tags.FindCloseCaptionPhraseIndex( CC_ENGLISH, phrase );
}
void PhonemeEditor::TraversePhrases( PEPHRASEITERFUNC pfn, float fparam )
{
int c = m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH );
for ( int i = 0; i < c; i++ )
{
CCloseCaptionPhrase *phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
if ( !phrase )
continue;
(this->*pfn)( phrase, fparam );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : amount -
//-----------------------------------------------------------------------------
void PhonemeEditor::ITER_MoveSelectedPhrases( CCloseCaptionPhrase *phrase, float amount )
{
if ( !phrase->GetSelected() )
return;
phrase->SetStartTime( phrase->GetStartTime() + amount );
phrase->SetEndTime( phrase->GetEndTime() + amount );
}
void PhonemeEditor::ITER_ExtendSelectedPhraseEndTimes( CCloseCaptionPhrase *phrase, float amount )
{
if ( !phrase->GetSelected() )
return;
if ( phrase->GetEndTime() + amount <= phrase->GetStartTime() )
return;
phrase->SetEndTime( phrase->GetEndTime() + amount );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : drawHelper -
// rcWorkSpace -
//-----------------------------------------------------------------------------
void PhonemeEditor::CloseCaption_Redraw( CChoreoWidgetDrawHelper& drawHelper, RECT& rcWorkSpace, CSentence& sentence )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
float starttime = m_nLeftOffset / GetPixelsPerSecond();
float endtime = w2() / GetPixelsPerSecond() + starttime;
RECT rcCC;
CloseCaption_GetTrayTopBottom( rcCC );
int ypos = rcCC.top;
const char *fontName = "Arial Unicode MS";
RECT rcTitle = rcCC;
OffsetRect( &rcTitle, 0, -20 );
rcTitle.left = 15;
rcTitle.right = w2();
drawHelper.DrawColoredText( "Arial", 15, FW_BOLD, PEColor( COLOR_PHONEME_CC_TEXT ), rcTitle,
"Close caption..." );
drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_CC_TAG_FILLER_NORMAL ), PS_SOLID, 1,
0, rcCC.top-1, w2(), rcCC.top-1 );
drawHelper.DrawColoredLine( PEColor( COLOR_PHONEME_CC_TAG_FILLER_NORMAL ), PS_SOLID, 1,
0, rcCC.bottom, w2(), rcCC.bottom );
bool drawselected;
for ( int pass = 0; pass < 2 ; pass++ )
{
drawselected = pass == 0 ? false : true;
int count = sentence.GetCloseCaptionPhraseCount( CC_ENGLISH );
for (int k = 0; k < count; k++)
{
CCloseCaptionPhrase *phrase = sentence.GetCloseCaptionPhrase( CC_ENGLISH, k );
if ( !phrase )
continue;
if ( phrase->GetSelected() != drawselected )
continue;
float t1 = phrase->GetStartTime();
float t2 = phrase->GetEndTime();
// Tag it
float frac = ( t1 - starttime ) / ( endtime - starttime );
int xpos = ( int )( frac * rcWorkSpace.right );
//if ( frac <= 0.0 )
// xpos = 0;
// Draw duration
float frac2 = ( t2 - starttime ) / ( endtime - starttime );
if ( frac2 < 0.0 )
continue;
int xpos2 = ( int )( frac2 * rcWorkSpace.right );
// Draw line and vertical ticks
RECT rcPhrase;
CloseCaption_GetPhraseRect( phrase, rcPhrase );
/*
rcPhrase.left = xpos;
rcPhrase.right = xpos2;
rcPhrase.top = ypos;
rcPhrase.bottom = ypos + m_nTickHeight - 1;
*/
drawHelper.DrawFilledRect(
PEColor( phrase->GetSelected() ? COLOR_PHONEME_CC_TAG_SELECTED : COLOR_PHONEME_CC_TAG_FILLER_NORMAL ),
rcPhrase );
COLORREF border = PEColor( phrase->GetSelected() ? COLOR_PHONEME_CC_TAG_BORDER_SELECTED : COLOR_PHONEME_CC_TAG_BORDER );
drawHelper.DrawOutlinedRect( border, PS_SOLID, 1, rcPhrase );
//if ( frac >= 0.0 && frac <= 1.0 )
{
int fontsize = 9;
RECT rcText;
rcText.left = xpos;
rcText.right = xpos2;
rcText.top = rcCC.top;
rcText.bottom = rcCC.bottom;
int availw = xpos2 - xpos;
int tokenCount = phrase->CountTokens();
if ( tokenCount >= 1 )
{
int pixelsPerToken = availw / tokenCount;
rcText.right = rcText.left + pixelsPerToken;
for ( int i = 0; i < tokenCount; i++ )
{
wchar_t const *token = phrase->GetToken( i );
if ( token && token[ 0 ] )
{
int texw = drawHelper.CalcTextWidthW( fontName,
fontsize,
FW_NORMAL,
L"%s", token );
RECT rcOutput = rcText;
if ( texw < pixelsPerToken )
{
rcOutput.left += ( pixelsPerToken - texw ) / 2;
}
if ( i != tokenCount - 1 )
{
// Draw divider
drawHelper.DrawColoredLine( border, PS_SOLID, 1,
rcText.right, rcText.top + 5, rcText.right, rcText.bottom - 5 );
}
drawHelper.DrawColoredTextW(
fontName,
fontsize,
FW_NORMAL,
PEColor( phrase->GetSelected() ? COLOR_PHONEME_CC_TAG_TEXT_SELECTED : COLOR_PHONEME_CC_TAG_TEXT ),
rcOutput,
L"%s", token );
}
OffsetRect( &rcText, pixelsPerToken, 0 );
}
}
}
}
}
RECT rcOutput;
CloseCaption_GetCCAreaRect( rcOutput );
CloseCaption_DrawCCArea( drawHelper, rcOutput );
}
void PhonemeEditor::CloseCaption_GetCCAreaRect( RECT& rcOutput )
{
GetScrubAreaRect( rcOutput );
rcOutput.left = w2() - 200;
rcOutput.right = w2();
rcOutput.top = rcOutput.bottom + 2;
rcOutput.bottom = rcOutput.top + 125;
}
void PhonemeEditor::CloseCaption_DrawCCArea( CChoreoWidgetDrawHelper& drawHelper, RECT& rcOutput )
{
closecaptionmanager->Draw( drawHelper, rcOutput );
}
void PhonemeEditor::ITER_AddFocusRectSelectedPhrases( CCloseCaptionPhrase *phrase, float amount )
{
if ( !phrase->GetSelected() )
return;
RECT phraseRect;
CloseCaption_GetPhraseRect( phrase, phraseRect );
AddFocusRect( phraseRect );
}
void PhonemeEditor::CloseCaption_FinishPhraseMove( int startx, int endx )
{
float clicktime = GetTimeForPixel( startx );
float endtime = GetTimeForPixel( endx );
// Find the phonemes who have the closest start/endtime to the starting click time
CCloseCaptionPhrase *current, *next;
if ( !CloseCaption_FindSpanningPhrases( clicktime, ¤t, &next ) )
{
return;
}
SetDirty( true );
PushUndo();
if ( current && !next )
{
// cap movement
current->SetEndTime( current->GetEndTime() + ( endtime - clicktime ) );
}
else if ( !current && next )
{
// cap movement
next->SetStartTime( next->GetStartTime() + ( endtime - clicktime ) );
}
else
{
// cap movement
endtime = min( endtime, next->GetEndTime() - 1.0f / GetPixelsPerSecond() );
endtime = max( endtime, current->GetStartTime() + 1.0f / GetPixelsPerSecond() );
current->SetEndTime( endtime );
next->SetStartTime( endtime );
}
CloseCaption_CleanupPhrases( false );
PushRedo();
redraw();
}
void PhonemeEditor::CloseCaption_FinishPhraseDrag( int startx, int endx )
{
float clicktime = GetTimeForPixel( startx );
float endtime = GetTimeForPixel( endx );
float dt = endtime - clicktime;
SetDirty( true );
PushUndo();
TraversePhrases( ITER_MoveSelectedPhrases, dt );
CloseCaption_CleanupPhrases( false );
PushRedo();
redraw();
}
void PhonemeEditor::CloseCaption_SelectPhrases( void )
{
if ( GetMode() != MODE_CLOSECAPTION )
return;
for ( int i = 0 ; i < m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ); i++ )
{
CCloseCaptionPhrase *w = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
Assert( w );
w->SetSelected( true );
}
redraw();
}
void PhonemeEditor::CloseCaption_EditDefaultPhrase( void )
{
if ( m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH ) == 0 )
{
SetDirty( true );
PushUndo();
m_Tags.SetCloseCaptionFromText( CC_ENGLISH );
PushRedo();
redraw();
}
else
{
Con_Printf( "Can only set default phrase on a sentence without existing phrases\n" );
}
}
void PhonemeEditor::CloseCaption_SplitPhraseAfterToken( CCloseCaptionPhrase *phrase, int splitToken )
{
int count = phrase->CountTokens();
if ( count < 2 )
{
Con_Printf( "PhonemeEditor::CloseCaption_SplitPhraseAtToken: Can't split %s, %i tokens total\n",
phrase->GetStream(),
count );
return;
}
if ( splitToken >= count - 1 )
{
// After end...sigh
return;
}
wchar_t stream1[ 4096 ];
wchar_t stream2[ 4096 ];
stream1[0] = L'\0';
stream2[0] = L'\0';
wchar_t const *token;
int count1 = 0;
int count2 = 0;
for ( int i = 0; i < count; i++ )
{
token = phrase->GetToken( i );
Assert( token && token[ 0 ] );
if ( i <= splitToken )
{
if ( count1 != 0 )
{
wcscat( stream1, L" " );
}
wcscat( stream1, token );
count1++;
}
else
{
if ( count2 != 0 )
{
wcscat( stream2, L" " );
}
wcscat( stream2, token );
count2++;
}
}
SetDirty( true );
PushUndo();
CCloseCaptionPhrase *newPhrase = new CCloseCaptionPhrase( stream2 );
phrase->SetStream( stream1 );
float oldend = phrase->GetEndTime();
float dt = oldend - phrase->GetStartTime();
float splitTime = dt * (float)(splitToken+1) / (float)(count);
phrase->SetEndTime( phrase->GetStartTime() + splitTime );
newPhrase->SetStartTime( phrase->GetEndTime() );
newPhrase->SetEndTime( oldend );
newPhrase->SetSelected( true );
int first = m_Tags.FindCloseCaptionPhraseIndex( CC_ENGLISH, phrase );
m_Tags.InsertCloseCaptionPhraseAtIndex( CC_ENGLISH, newPhrase, first + 1 );
PushRedo();
redraw();
}
void PhonemeEditor::CloseCaption_SplitPhrase( void )
{
CCloseCaptionPhrase *phrase = CloseCaption_GetClickedPhrase();
if ( !phrase )
return;
int token = CloseCaption_GetClickedPhraseToken();
if ( token < 0 )
return;
CloseCaption_SplitPhraseAfterToken( phrase, token );
redraw();
}
void PhonemeEditor::CloseCaption_MergeSelected( void )
{
CountSelected();
if ( m_nSelectedPhraseCount < 2 )
{
Con_Printf( "CloseCaption_MergeSelected: requires 2 or more selected phrases\n" );
return;
}
if ( !CloseCaption_AreSelectedPhrasesContiguous() )
{
Con_Printf( "CloseCaption_MergeSelected: selected phrases must be contiguous\n" );
return;
}
SetDirty( true );
PushUndo();
CUtlVector< CCloseCaptionPhrase * > selected;
float beststart = 100000.0f;
float bestend = -100000.0f;
int c = m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH );
int i;
int insertslot = c -1;
// Walk backwards and remove
for ( i = c - 1; i >= 0; i-- )
{
CCloseCaptionPhrase *phrase = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
if ( !phrase || !phrase->GetSelected() )
continue;
if ( phrase->GetStartTime() < beststart )
{
beststart = phrase->GetStartTime();
}
if ( phrase->GetEndTime() > bestend )
{
bestend = phrase->GetEndTime();
}
selected.AddToHead( new CCloseCaptionPhrase( *phrase ) );
// Remember the earliest slot
if ( i < insertslot )
{
insertslot = i;
}
m_Tags.RemoveCloseCaptionPhrase( CC_ENGLISH, i );
}
if ( selected.Count() <= 0 )
return;
CCloseCaptionPhrase *newphrase = new CCloseCaptionPhrase( selected[ 0 ]->GetStream() );
Assert( newphrase );
wchar_t sz[ 4096 ];
delete selected[ 0 ];
for ( i = 1; i < selected.Count(); i++ )
{
_snwprintf( sz, sizeof( sz ), newphrase->GetStream() );
// Phrases don't have leading/trailing spaces so it should be safe to just append a space here
wcscat( sz, L" " );
wcscat( sz, selected[ i ]->GetStream() );
newphrase->SetStream( sz );
delete selected[ i ];
}
selected.RemoveAll();
m_Tags.InsertCloseCaptionPhraseAtIndex( CC_ENGLISH, newphrase, insertslot );
newphrase->SetSelected( true );
newphrase->SetStartTime( beststart );
newphrase->SetEndTime( bestend );
PushRedo();
redraw();
}
int PhonemeEditor::CloseCaption_GetPhraseTokenUnderMouse( const CCloseCaptionPhrase *phrase, int mx )
{
int index = -1;
int count = phrase->CountTokens();
if ( count >= 1 )
{
RECT rcPhrase;
CloseCaption_GetPhraseRect( phrase, rcPhrase );
float t = GetTimeForPixel( mx );
float dt = phrase->GetEndTime() - phrase->GetStartTime();
if ( dt > 0.0f )
{
float frac = ( t - phrase->GetStartTime() ) / dt;
if ( frac >= 0 && frac <= 1.0f )
{
int availw = rcPhrase.right - rcPhrase.left;
int clickpoint = frac * availw;
int pixelsPerToken = availw / count;
index = clickpoint / pixelsPerToken;
index = clamp( index, 0, count - 1 );
}
}
}
return index;
}
void PhonemeEditor::CloseCaption_SortPhrases( bool prepareundo )
{
if ( prepareundo )
{
SetDirty( true );
PushUndo();
}
// Just bubble sort by start time
int c = m_Tags.GetCloseCaptionPhraseCount( CC_ENGLISH );
for ( int i = 0; i < c; i++ )
{
for ( int j = i + 1; j < c; j++ )
{
CCloseCaptionPhrase *p1 = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, i );
CCloseCaptionPhrase *p2 = m_Tags.GetCloseCaptionPhrase( CC_ENGLISH, j );
if ( p1->GetStartTime() < p2->GetStartTime() )
continue;
// Swap them
m_Tags.m_CloseCaption[ CC_ENGLISH ][ i ] = p2;
m_Tags.m_CloseCaption[ CC_ENGLISH ][ j ] = p1;
}
}
if ( prepareundo )
{
PushRedo();
}
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -