//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "workspacebrowser.h" #include "workspace.h" #include "project.h" #include #include "resource.h" #include "project.h" #include "vcdfile.h" #include "soundentry.h" #include "scene.h" #include "workspacemanager.h" #include "soundbrowser.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "iscenemanagersound.h" #include "snd_wave_source.h" #include "cmdlib.h" #include "tabwindow.h" #include "soundproperties.h" #include "soundproperties_multiple.h" #include "wavebrowser.h" #include "wavefile.h" #include "inputproperties.h" #include "drawhelper.h" #include "utlbuffer.h" #define ENTRY_ALLSOUNDS "AllSounds" #define ENTRY_ALL_INDEX 0 #define ENTRY_SEARCHRESULTS "Search results" #define ENTRY_SEARCH_INDEX 1 enum { // Controls IDC_SB_LISTVIEW = 101, IDC_SB_FILTERTAB, // Messages IDC_SB_PLAY = 1000, IDC_SB_SOUNDPROPERTIES, IDC_SB_SHOWINWAVEBROWSER, IDC_SB_ADDSOUND, IDC_SB_REMOVESOUND, IDC_SB_GETSENTENCE, }; enum { COL_SOUND = 0, COL_COUNT, COL_WAV, COL_SENTENCE, COL_CHANNEL, COL_VOLUME, COL_SOUNDLEVEL, COL_PITCH, COL_SCRIPT, COL_CC, }; class CSoundList : public mxListView { public: CSoundList( mxWindow *parent, int id = 0 ) : mxListView( parent, 0, 0, 0, 0, id ) { // SendMessage ( (HWND)getHandle(), WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_FIXED_FONT), MAKELPARAM (TRUE, 0)); //HWND wnd = (HWND)getHandle(); //DWORD style = GetWindowLong( wnd, GWL_STYLE ); //style |= LVS_SORTASCENDING; //SetWindowLong( wnd, GWL_STYLE, style ); //SceneManager_AddWindowStyle( this, LVS_SORTASCENDING ); // Add column headers insertTextColumn( COL_SOUND, 200, "Sound" ); insertTextColumn( COL_COUNT, 20, "#" ); insertTextColumn( COL_WAV, 220, "WAV Filename" ); insertTextColumn( COL_SENTENCE, 300, "Sentence Text" ); insertTextColumn( COL_CHANNEL, 100, "Channel" ); insertTextColumn( COL_VOLUME, 100, "Volume" ); insertTextColumn( COL_SOUNDLEVEL, 120, "Soundlevel" ); insertTextColumn( COL_PITCH, 100, "Pitch" ); insertTextColumn( COL_SCRIPT, 150, "Script File" ); insertTextColumn( COL_CC, 300, "CC Text" ); } }; class CSoundFilterTab : public CTabWindow { public: typedef CTabWindow BaseClass; CSoundFilterTab( mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0 ) : CTabWindow( parent, x, y, w, h, id, style ) { SetInverted( true ); SetRowHeight( 20 ); } virtual void ShowRightClickMenu( int mx, int my ) { // Nothing } void Init( CUtlSymbolTable& table, CUtlVector< CUtlSymbol >& scripts ) { add( ENTRY_ALLSOUNDS ); add( ENTRY_SEARCHRESULTS ); int c = scripts.Count(); for ( int i = 0; i < c; i++ ) { CUtlSymbol& sym = scripts[ i ]; add( table.String( sym ) ); } select( 0 ); } void UpdatePrefixes() { int c = getItemCount(); // Skip All and search results for ( int i = 2; i < c; i++ ) { setPrefix( i, "" ); char const *script = getLabel( i ); if ( !script ) continue; int scriptindex = g_pSoundEmitterSystem->FindSoundScript( va( "scripts/%s.txt", script ) ); if ( scriptindex < 0 ) continue; if ( g_pSoundEmitterSystem->IsSoundScriptDirty( scriptindex ) ) { setPrefix( i, "* " ); } } RecomputeLayout( w2() ); redraw(); } }; class COptionsWindow : public mxWindow { typedef mxWindow BaseClass; public: enum { IDC_VOICE_ONLY = 1000, IDC_PLAY_SOUND, IDC_STOP_SOUNDS, IDC_SEARCH, }; COptionsWindow( CSoundBrowser *browser ) : BaseClass( browser, 0, 0, 0, 0 ), m_pBrowser( browser ) { SceneManager_AddWindowStyle( this, WS_CLIPSIBLINGS | WS_CLIPCHILDREN ); m_szSearchString[0]=0; m_pChanVoiceOnly = new mxCheckBox( this, 0, 0, 0, 0, "CHAN_VOICE only", IDC_VOICE_ONLY ); m_pChanVoiceOnly->setChecked( true ); m_pPlay = new mxButton( this, 0, 0, 0, 0, "Play", IDC_PLAY_SOUND ); m_pStopSounds = new mxButton( this, 0, 0, 0, 0, "Stop Sounds", IDC_STOP_SOUNDS ); m_pSearch = new mxButton( this, 0, 0, 0, 0, "Search...", IDC_SEARCH ); m_pSearchString = new mxLabel( this, 0, 0, 0, 0, "" ); } bool PaintBackground( void ) { redraw(); return false; } virtual void redraw() { CDrawHelper drawHelper( this, GetSysColor( COLOR_BTNFACE ) ); } virtual int handleEvent( mxEvent *event ) { int iret = 0; switch ( event->event ) { default: break; case mxEvent::Size: { iret = 1; int split = 120; int x = 1; m_pPlay->setBounds( x, 1, split, h2() - 2 ); x += split + 10; m_pStopSounds->setBounds( x, 1, split, h2()-2 ); x += split + 10; m_pChanVoiceOnly->setBounds( x, 1, split, h2() - 2 ); x += split + 10; m_pSearch->setBounds( x, 1, split, h2() - 2 ); x += split + 10; m_pSearchString->setBounds( x, 2, split * 2, h2() - 4 ); x += split * 2 + 10; } break; case mxEvent::Action: { switch ( event->action ) { case IDC_STOP_SOUNDS: { iret = 1; sound->StopAll(); } break; case IDC_PLAY_SOUND: { iret = 1; m_pBrowser->OnPlay(); } break; case IDC_VOICE_ONLY: { iret = 1; m_pBrowser->RepopulateTree(); }; break; case IDC_SEARCH: { iret = 1; OnSearch(); }; break; default: break; } } break; } return iret; } bool IsChanVoiceOnly() const { return m_pChanVoiceOnly->isChecked(); } char const *GetSearchString() { return m_szSearchString; } void OnSearch() { CInputParams params; memset( ¶ms, 0, sizeof( params ) ); Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "Search" ); Q_strcpy( params.m_szPrompt, "Find:" ); Q_strcpy( params.m_szInputText, m_szSearchString ); if ( !InputProperties( ¶ms ) ) return; Q_strcpy( m_szSearchString, params.m_szInputText ); m_pSearchString->setLabel( va( "Search: '%s'", GetSearchString() ) ); m_pBrowser->OnSearch(); } private: mxCheckBox *m_pChanVoiceOnly; mxButton *m_pStopSounds; mxButton *m_pPlay; mxButton *m_pSearch; mxLabel *m_pSearchString; CSoundBrowser *m_pBrowser; char m_szSearchString[ 256 ]; }; //----------------------------------------------------------------------------- // Purpose: // Input : *parent - //----------------------------------------------------------------------------- CSoundBrowser::CSoundBrowser( mxWindow *parent, CWorkspaceManager *manager, int id ) : BaseClass( parent, 0, 0, 0, 0, "Sound Browser", id ) { m_pManager = manager; SceneManager_MakeToolWindow( this, false ); m_pListView = new CSoundList( this, IDC_SB_LISTVIEW ); m_pFilter = new CSoundFilterTab( this, 0, 0, 0, 0, IDC_SB_FILTERTAB ); m_pOptions = new COptionsWindow( this ); HIMAGELIST list = GetWorkspaceManager()->CreateImageList(); // Associate the image list with the tree-view control. m_pListView->setImageList( (void *)list ); LoadAllSounds(); m_pFilter->select( 0 ); RepopulateTree(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSoundBrowser::OnDelete() { RemoveAllSounds(); } //----------------------------------------------------------------------------- // Purpose: // Input : *event - // Output : int //----------------------------------------------------------------------------- int CSoundBrowser::handleEvent( mxEvent *event ) { int iret = 0; switch ( event->event ) { default: break; case mxEvent::Action: { iret = 1; switch ( event->action ) { default: { iret = 0; } break; case IDC_SB_FILTERTAB: { RepopulateTree(); } break; case IDC_SB_LISTVIEW: { bool rightmouse = ( event->flags == mxEvent::RightClicked ) ? true : false; bool doubleclicked = ( event->flags == mxEvent::DoubleClicked ) ? true : false; if ( rightmouse ) { ShowContextMenu(); } else if ( doubleclicked ) { if ( m_pListView->getNumSelected() == 1 ) { int index = m_pListView->getNextSelectedItem( -1 ); if ( index >= 0 ) { CSoundEntry *se = (CSoundEntry *)m_pListView->getUserData( index, 0 ); if ( se ) { se->Play(); CWaveBrowser *wb = GetWorkspaceManager()->GetWaveBrowser(); if ( wb && se->GetWaveCount() > 0 ) { CWaveFile *firstwave = se->GetWave( 0 ); Assert( firstwave ); if ( firstwave ) { wb->JumpToItem( firstwave ); } } } } } } } break; case IDC_SB_PLAY: { OnPlay(); } break; case IDC_SB_GETSENTENCE: { OnGetSentence(); } break; case IDC_SB_SOUNDPROPERTIES: { OnSoundProperties(); } break; case IDC_SB_SHOWINWAVEBROWSER: { OnShowInWaveBrowser(); } break; case IDC_SB_ADDSOUND: { OnAddSound(); } break; case IDC_SB_REMOVESOUND: { OnRemoveSound(); } break; } } break; case mxEvent::Size: { int optionsh = 20; int filterh = m_pFilter->GetBestHeight( w2() ); m_pOptions->setBounds( 0, 0, w2(), optionsh ); m_pListView->setBounds( 0, optionsh, w2(), h2() - filterh - optionsh ); m_pFilter->setBounds( 0, h2() - filterh, w2(), filterh ); GetWorkspaceManager()->SetWorkspaceDirty(); iret = 1; } break; case mxEvent::Close: { iret = 1; } break; } return iret; } static bool NameLessFunc( CSoundEntry *const& name1, CSoundEntry *const& name2 ) { if ( Q_stricmp( name1->GetName(), name2->GetName() ) < 0 ) return true; return false; } void CSoundBrowser::LoadAllSounds() { RemoveAllSounds(); // int c = g_pSoundEmitterSystem->GetSoundCount(); int added = 0; int i; for ( i = g_pSoundEmitterSystem->First(); i != g_pSoundEmitterSystem->InvalidIndex(); i = g_pSoundEmitterSystem->Next( i ) ) { char const *name = g_pSoundEmitterSystem->GetSoundName( i ); CSoundEntry *se = new CSoundEntry( NULL, name ); m_AllSounds.AddToTail( se ); char filebase [ 512 ]; Q_FileBase( g_pSoundEmitterSystem->GetSourceFileForSound( i ), filebase, sizeof( filebase ) ); // Add script file symbol CUtlSymbol script_sym = m_ScriptTable.AddString( filebase ); if ( m_Scripts.Find( script_sym ) == m_Scripts.InvalidIndex() ) { m_Scripts.AddToTail( script_sym ); } ++added; if ( !( added % 500 ) ) { // Con_Printf( "CSoundBrowser: loaded %i sounds\n", added ); } } m_pFilter->Init( m_ScriptTable, m_Scripts ); } void CSoundBrowser::RemoveAllSounds() { int c = m_AllSounds.Count(); for ( int i = 0; i < c; i++ ) { CSoundEntry *se = m_AllSounds[ i ]; delete se; } m_AllSounds.RemoveAll(); m_Scripts.RemoveAll(); m_CurrentSelection.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSoundBrowser::PopulateTree( bool voiceonly, char const *scriptonly ) { int i; CUtlRBTree< CSoundEntry *, int > m_Sorted( 0, 0, NameLessFunc ); bool textsearch = false; char const *texttofind = NULL; if ( scriptonly ) { if ( !Q_stricmp( scriptonly, ENTRY_ALLSOUNDS ) ) { scriptonly = NULL; } else if ( !Q_stricmp( scriptonly, ENTRY_SEARCHRESULTS ) ) { scriptonly = NULL; textsearch = true; texttofind = GetSearchString(); } } int c = m_AllSounds.Count(); for ( i = 0; i < c; i++ ) { CSoundEntry *se = m_AllSounds[ i ]; char const *name = se->GetName(); CSoundParametersInternal *params = se->GetSoundParameters(); if ( !params ) continue; if ( voiceonly && params->GetChannel() != CHAN_VOICE ) continue; if ( scriptonly ) { if ( Q_stricmp( scriptonly, se->GetScriptFile() ) ) continue; } if ( textsearch && texttofind ) { bool keep = false; if ( Q_stristr( name, texttofind ) ) { keep = true; } else { int waveCount = se->GetWaveCount(); for ( int wave = 0; wave < waveCount; wave++ ) { CWaveFile *w = se->GetWave( wave ); if ( !w ) continue; char const *wavename = w->GetFileName(); if ( !wavename ) { Assert( 0 ); continue; } if ( !Q_stristr( wavename, texttofind ) ) { continue; } keep = true; break; } } if ( !keep ) { continue; } } m_Sorted.Insert( se ); } // Repopulate tree m_pListView->removeAll(); int loadcount = 0; m_pListView->setDrawingEnabled( false ); for ( i = m_Sorted.FirstInorder(); i != m_Sorted.InvalidIndex(); i = m_Sorted.NextInorder( i ) ) { CSoundEntry *se = m_Sorted[ i ]; char const *name = se->GetName(); CSoundParametersInternal *params = se->GetSoundParameters(); if ( !params ) continue; int slot = m_pListView->add( name ); m_pListView->setUserData( slot, COL_SOUND, (void *)se ); m_pListView->setImage( slot, COL_SOUND, se->GetIconIndex() ); int waveCount = params->NumSoundNames(); if ( waveCount >= 1 ) { m_pListView->setLabel( slot, COL_COUNT, waveCount > 1 ? va( "%i", waveCount ) : "" ); m_pListView->setLabel( slot, COL_WAV, g_pSoundEmitterSystem->GetWaveName( params->GetSoundNames()[ 0 ].symbol ) ); } m_pListView->setLabel( slot, COL_SENTENCE, se->GetSentenceText( 0 ) ); m_pListView->setLabel( slot, COL_CHANNEL, params->ChannelToString() ); m_pListView->setLabel( slot, COL_VOLUME, params->VolumeToString() ); m_pListView->setLabel( slot, COL_SOUNDLEVEL, params->SoundLevelToString() ); m_pListView->setLabel( slot, COL_PITCH, params->PitchToString() ); wchar_t buf[ 1024 ]; se->GetCCText( buf, 1024 ); m_pListView->setLabel( slot, COL_CC, buf ); char filebase [ 512 ]; int soundIndex = g_pSoundEmitterSystem->GetSoundIndex( name ); Q_FileBase( g_pSoundEmitterSystem->GetSourceFileForSound( soundIndex ), filebase, sizeof( filebase ) ); m_pListView->setLabel( slot, COL_SCRIPT, filebase ); ++loadcount; } m_pListView->setDrawingEnabled( true ); // Con_Printf( "CSoundBrowser: selected %i sounds\n", loadcount ); } CWorkspaceManager *CSoundBrowser::GetManager() { return m_pManager; } void CSoundBrowser::RepopulateTree() { bool voiceonly = m_pOptions->IsChanVoiceOnly(); int slot = m_pFilter->getSelectedIndex(); if ( 0 >= slot ) { PopulateTree( voiceonly, NULL ); } else { PopulateTree( voiceonly, m_pFilter->getLabel( slot ) ); } m_pFilter->UpdatePrefixes(); } void CSoundBrowser::BuildSelectionList( CUtlVector< CSoundEntry * >& selected ) { selected.RemoveAll(); int idx = -1; do { idx = m_pListView->getNextSelectedItem( idx ); if ( idx != -1 ) { CSoundEntry *se = (CSoundEntry *)m_pListView->getUserData( idx, 0 ); if ( se ) { selected.AddToTail( se ); } } } while ( idx != -1 ); } void CSoundBrowser::ShowContextMenu( void ) { BuildSelectionList( m_CurrentSelection ); if ( m_CurrentSelection.Count() <= 0 ) return; POINT pt; GetCursorPos( &pt ); ScreenToClient( (HWND)getHandle(), &pt ); // New scene, edit comments mxPopupMenu *pop = new mxPopupMenu(); if ( m_CurrentSelection.Count() == 1 ) { pop->add ("&Play", IDC_SB_PLAY ); pop->addSeparator(); } pop->add( "Refresh sentence data", IDC_SB_GETSENTENCE ); pop->add( "Add sound entry...", IDC_SB_ADDSOUND ); if ( m_CurrentSelection.Count() >= 1 ) { pop->add( "Remove sound(s)", IDC_SB_REMOVESOUND ); } pop->addSeparator(); pop->add( "Show in Wave Browser", IDC_SB_SHOWINWAVEBROWSER ); pop->add( "&Properties...", IDC_SB_SOUNDPROPERTIES ); pop->popup( this, pt.x, pt.y ); } void CSoundBrowser::OnPlay() { BuildSelectionList( m_CurrentSelection ); if ( m_CurrentSelection.Count() == 1 ) { CSoundEntry *se = m_CurrentSelection[ 0 ]; if ( se ) { se->Play(); } } } void CSoundBrowser::JumpToItem( CSoundEntry *se ) { char const *script = se->GetScriptFile(); bool voiceonly = m_pOptions->IsChanVoiceOnly(); if ( !script || !script[ 0 ] ) { PopulateTree( voiceonly, NULL ); } else { PopulateTree( voiceonly, script ); } int idx = 0; int c = m_pListView->getItemCount(); for ( ; idx < c; idx++ ) { CSoundEntry *item = (CSoundEntry *)m_pListView->getUserData( idx, 0 ); if ( !Q_stricmp( item->GetName(), se->GetName() ) ) { break; } } if ( idx < c ) { m_pListView->scrollToItem( idx ); } } void CSoundBrowser::OnSoundProperties() { BuildSelectionList( m_CurrentSelection ); if ( m_CurrentSelection.Count() < 1 ) { Con_Printf( "No selection\n" ); return; } CSoundParams params; memset( ¶ms, 0, sizeof( params ) ); Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "Sound Properties" ); int c = m_CurrentSelection.Count(); for ( int i = 0 ; i < c; i++ ) { CSoundEntry *entry = m_CurrentSelection[ i ]; if ( !entry ) continue; params.items.AddToTail( entry ); } if ( params.items.Count() > 1 ) { SoundProperties_Multiple( ¶ms ); } else { SoundProperties( ¶ms ); } } void CSoundBrowser::OnShowInWaveBrowser() { if ( m_pListView->getNumSelected() == 1 ) { int index = m_pListView->getNextSelectedItem( -1 ); if ( index >= 0 ) { CSoundEntry *se = (CSoundEntry *)m_pListView->getUserData( index, 0 ); if ( se ) { CWaveBrowser *wb = GetWorkspaceManager()->GetWaveBrowser(); if ( wb && se->GetWaveCount() > 0 ) { CWaveFile *firstwave = se->GetWave( 0 ); Assert( firstwave ); if ( firstwave ) { wb->JumpToItem( firstwave ); } } } } } } void CSoundBrowser::OnSearch() { m_pFilter->select( ENTRY_SEARCH_INDEX ); RepopulateTree(); } char const *CSoundBrowser::GetSearchString() { return m_pOptions->GetSearchString(); } void CSoundBrowser::OnAddSound() { CSoundParams params; memset( ¶ms, 0, sizeof( params ) ); params.addsound = true; Q_snprintf( params.m_szDialogTitle, sizeof( params.m_szDialogTitle ), "New Sound" ); if ( !SoundProperties( ¶ms ) ) return; if ( params.items.Count() == 1 ) { CSoundEntry *newItem = params.items[ 0 ]; m_AllSounds.AddToTail( newItem ); int slot = g_pSoundEmitterSystem->GetSoundIndex( newItem->GetName() ); if ( g_pSoundEmitterSystem->IsValidIndex( slot ) ) { CSoundParametersInternal *p = g_pSoundEmitterSystem->InternalGetParametersForSound( slot ); if ( p ) { CWaveBrowser *wb = GetWorkspaceManager()->GetWaveBrowser(); Assert( wb ); int waveCount = p->NumSoundNames(); for ( int wave = 0; wave < waveCount; wave++ ) { char const *wavname = g_pSoundEmitterSystem->GetWaveName( p->GetSoundNames()[ wave ].symbol ); if ( wavname ) { CWaveFile *wavefile = wb->FindEntry( wavname, true ); if ( wavefile ) { newItem->AddWave( wavefile ); } } } } } } // Repopulate things GetWorkspaceManager()->RefreshBrowsers(); } void CSoundBrowser::OnRemoveSound() { BuildSelectionList( m_CurrentSelection ); if ( m_CurrentSelection.Count() < 1 ) { Con_Printf( "No selection\n" ); return; } int c = m_CurrentSelection.Count(); for ( int i = c - 1; i >= 0 ; i-- ) { CSoundEntry *se = m_CurrentSelection[ i ]; Assert( se ); // FIXME: See if still referenced by a vcd? g_pSoundEmitterSystem->RemoveSound( se->GetName() ); m_AllSounds.FindAndRemove( se ); delete se; } // Repopulate things GetWorkspaceManager()->RefreshBrowsers(); } void CSoundBrowser::OnGetSentence() { BuildSelectionList( m_CurrentSelection ); if ( m_CurrentSelection.Count() < 1 ) { Con_Printf( "No selection\n" ); return; } int c = m_CurrentSelection.Count(); for ( int i = c - 1; i >= 0 ; i-- ) { CSoundEntry *se = m_CurrentSelection[ i ]; Assert( se ); int c = se->GetWaveCount(); for ( int i = 0; i < c; ++i ) { CWaveFile *wav = se->GetWave( i ); if ( !wav->HasLoadedSentenceInfo() ) { wav->EnsureSentence(); } } } // Repopulate things GetWorkspaceManager()->RefreshBrowsers(); }