From 473974e52f82b8f05e0d9a067af810a48492c75e Mon Sep 17 00:00:00 2001 From: Colin Wallace Date: Tue, 17 Mar 2015 04:20:02 +0000 Subject: [PATCH] Add ability to delete songs from disk at the song selection screen (Ctrl+Backspace) --- Themes/_fallback/Languages/en.ini | 2 ++ Themes/_fallback/base._ini | 3 +- Themes/_fallback/metrics.ini | 3 +- Themes/default/metrics.ini | 2 +- src/MusicWheel.cpp | 18 ++++++++++ src/MusicWheel.h | 2 ++ src/PrefsManager.cpp | 1 + src/PrefsManager.h | 1 + src/RageFileManager.cpp | 7 ++++ src/RageFileManager.h | 1 + src/ScreenOptionsMasterPrefs.cpp | 5 +-- src/ScreenSelectMusic.cpp | 57 +++++++++++++++++++++++++++++++ src/ScreenSelectMusic.h | 3 ++ src/SongManager.cpp | 28 +++++++++++++-- src/SongManager.h | 3 ++ src/WheelBase.h | 2 ++ 16 files changed, 130 insertions(+), 8 deletions(-) diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 11001f5837..820306cd96 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -342,6 +342,7 @@ Not Supported=Mode not supported while networked. [OptionExplanations] Accel=Accelerate, Decelerate or otherwise adjust the arrow speed. AllowExtraStage=When enabled (and Event Mode isn't on), allows the player to earn extra stages after completing certain requirements. +AllowSongDeletion=Allow players to permanently delete songs from the system by pressing Ctrl+Backspace when browsing the music wheel. AllowW1=Enable or disable fantastic judgement. NEVER - never show Fantastic, COURSES ONLY - only during nonstop/oni modes, ALWAYS - all modes of play. AllowMultipleHighScoreWithSameName=If turned off, only the highest score with a given name will be saved. If turned on, it will allow mutliple high scores with a given name to be saved. Announcer=Choose from this list of installed announcer packs. @@ -934,6 +935,7 @@ Add Attack=Add Attack Add Mod=Add Mod Air=Air AllowExtraStage=Allow Extra Stage +AllowSongDeletion=Allow Song Deletion AllowW1=Fantastic Timing AllowMultipleHighScoreWithSameName=Competitive Mode Alter=Alter diff --git a/Themes/_fallback/base._ini b/Themes/_fallback/base._ini index 07f875edf4..897ee84b81 100644 --- a/Themes/_fallback/base._ini +++ b/Themes/_fallback/base._ini @@ -2751,7 +2751,7 @@ IdleTimeoutSeconds=0 [ScreenOptionsAdvanced] Fallback="ScreenOptionsServiceChild" -LineNames="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31" +LineNames="1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32" Line1="conf,MenuTimer" Line2="conf,ScoringType" Line3="conf,TimingWindowScale" @@ -2783,6 +2783,7 @@ Line28="conf,AutogenSteps" Line29="conf,AutogenGroupCourses" Line30="conf,FastLoad" Line31="conf,OnlyPreferredDifficulties" +Line32="conf,HiddenSongs" [ScreenOptionsGraphicsSound] Fallback="ScreenOptionsServiceChild" diff --git a/Themes/_fallback/metrics.ini b/Themes/_fallback/metrics.ini index 48ed8e3b67..575c0b4d0c 100644 --- a/Themes/_fallback/metrics.ini +++ b/Themes/_fallback/metrics.ini @@ -3012,7 +3012,7 @@ Line20="conf,VisualDelaySeconds" Fallback="ScreenOptionsServiceChild" NextScreen="ScreenOptionsService" PrevScreen="ScreenOptionsService" -LineNames="3,4,8,SI,SM,HN,11,13,14,15,16,28,29,30,31,ECPT" +LineNames="3,4,8,SI,SM,HN,11,13,14,15,16,28,29,30,31,32,ECPT" #LineScore="lua,UserPrefScoringMode()" Line3="conf,TimingWindowScale" Line4="conf,LifeDifficulty" @@ -3029,6 +3029,7 @@ Line28="conf,AutogenSteps" Line29="conf,AutogenGroupCourses" Line30="conf,FastLoad" Line31="conf,FastLoadAdditionalSongs" +Line32="conf,AllowSongDeletion" LineECPT="conf,EditClearPromptThreshold" # unused options diff --git a/Themes/default/metrics.ini b/Themes/default/metrics.ini index 97d67c063a..e203750488 100644 --- a/Themes/default/metrics.ini +++ b/Themes/default/metrics.ini @@ -1322,7 +1322,7 @@ LineFlashyCombo="lua,ThemePrefRow('FlashyCombo')" [ScreenOptionsGraphicsSound] [ScreenOptionsAdvanced] -LineNames="3,4,8,SI,SM,11,13,14,16,28,29,30,RollCombo,ECPT" +LineNames="3,4,8,SI,SM,11,13,14,16,28,29,30,32,RollCombo,ECPT" LineRollCombo="lua,ThemePrefRow('ComboOnRolls')" [ScreenAppearanceOptions] diff --git a/src/MusicWheel.cpp b/src/MusicWheel.cpp index 56790c4516..745bd3670e 100644 --- a/src/MusicWheel.cpp +++ b/src/MusicWheel.cpp @@ -255,6 +255,24 @@ MusicWheel::~MusicWheel() } } +void MusicWheel::ReloadSongList() +{ + int songIdxToPreserve = m_iSelection; + // Remove the song from any sorting caches: + FOREACH_ENUM( SortOrder, so ) { + m_WheelItemDatasStatus[so]=INVALID; + } + // rebuild the info associated with this sort order + readyWheelItemsData(GAMESTATE->m_SortOrder); + // re-open the section to refresh song counts, etc. + SetOpenSection(m_sExpandedSectionName); + // navigate to the song nearest to what was previously selected + m_iSelection = songIdxToPreserve; + RebuildWheelItems(); + // refresh the song preview + SCREENMAN->PostMessageToTopScreen( SM_SongChanged, 0 ); +} + /* If a song or course is set in GAMESTATE and available, select it. Otherwise, choose the * first available song or course. Return true if an item was set, false if no items are * available. */ diff --git a/src/MusicWheel.h b/src/MusicWheel.h index 83c20aa29f..e894820066 100644 --- a/src/MusicWheel.h +++ b/src/MusicWheel.h @@ -50,6 +50,8 @@ public: RString JumpToPrevGroup(); const MusicWheelItemData *GetCurWheelItemData( int i ) { return (const MusicWheelItemData *) m_CurWheelItemData[i]; } + virtual void ReloadSongList(); + // Lua void PushSelf( lua_State *L ); diff --git a/src/PrefsManager.cpp b/src/PrefsManager.cpp index 17629dbc96..3e296a5928 100644 --- a/src/PrefsManager.cpp +++ b/src/PrefsManager.cpp @@ -289,6 +289,7 @@ PrefsManager::PrefsManager() : m_sMachineName ( "MachineName", "" ), m_sCoursesToShowRanking ( "CoursesToShowRanking", "" ), m_MuteActions("MuteActions", false), + m_bAllowSongDeletion("AllowSongDeletion", false), m_bQuirksMode ( "QuirksMode", false ), diff --git a/src/PrefsManager.h b/src/PrefsManager.h index e3cb435dd5..01571298f6 100644 --- a/src/PrefsManager.h +++ b/src/PrefsManager.h @@ -299,6 +299,7 @@ public: Preference m_sMachineName; Preference m_sCoursesToShowRanking; Preference m_MuteActions; + Preference m_bAllowSongDeletion; // Allow the user to remove songs from their collection through UI / keyboard shortcut /** @brief Enable some quirky behavior used by some older versions of StepMania. */ Preference m_bQuirksMode; diff --git a/src/RageFileManager.cpp b/src/RageFileManager.cpp index 453606fabc..cc15a7f24c 100644 --- a/src/RageFileManager.cpp +++ b/src/RageFileManager.cpp @@ -464,6 +464,13 @@ bool RageFileManager::Remove( const RString &sPath_ ) return bDeleted; } +bool RageFileManager::DeleteRecursive( const RString &sPath ) +{ + // On some OS's, non-empty directories cannot be deleted. + // This is a work-around that can delete both files and non-empty directories + return ::DeleteRecursive(sPath); +} + void RageFileManager::CreateDir( const RString &sDir ) { RString sTempFile = sDir + "newdir.temp.newdir"; diff --git a/src/RageFileManager.h b/src/RageFileManager.h index bbc5a730ef..c6ef53520c 100644 --- a/src/RageFileManager.h +++ b/src/RageFileManager.h @@ -29,6 +29,7 @@ public: bool bOnlyDirs= false, bool bReturnPathToo= false); bool Move( const RString &sOldPath, const RString &sNewPath ); bool Remove( const RString &sPath ); + bool DeleteRecursive( const RString &sPath ); void CreateDir( const RString &sDir ); enum FileType { TYPE_FILE, TYPE_DIR, TYPE_NONE }; diff --git a/src/ScreenOptionsMasterPrefs.cpp b/src/ScreenOptionsMasterPrefs.cpp index 25f77f149f..cb87d495dd 100644 --- a/src/ScreenOptionsMasterPrefs.cpp +++ b/src/ScreenOptionsMasterPrefs.cpp @@ -786,12 +786,13 @@ static void InitializeConfOptions() ADD( ConfOption( "AllowMultipleHighScoreWithSameName", MovePref, "Off", "On" ) ); ADD( ConfOption( "ComboContinuesBetweenSongs", MovePref, "Off", "On") ); ADD( ConfOption( "Disqualification", MovePref, "Off","On" ) ); - ADD(ConfOption("HarshHotLifePenalty", MovePref, "Off", "On")); + ADD( ConfOption( "HarshHotLifePenalty", MovePref, "Off", "On") ); ADD( ConfOption( "FailOffForFirstStageEasy", MovePref, "Off","On" ) ); - ADD( ConfOption( "FailOffInBeginner", MovePref, "Off","On" ) ); + ADD( ConfOption( "FailOffInBeginner", MovePref, "Off","On" ) ); ADD( ConfOption( "LockCourseDifficulties", MovePref, "Off", "On" ) ); ADD( ConfOption( "PickExtraStage", MovePref, "Off","On" ) ); ADD( ConfOption( "UseUnlockSystem", MovePref, "Off","On" ) ); + ADD( ConfOption( "AllowSongDeletion", MovePref, "Off","On" ) ); // Machine options ADD( ConfOption( "MenuTimer", MovePref, "Off","On" ) ); diff --git a/src/ScreenSelectMusic.cpp b/src/ScreenSelectMusic.cpp index 29ea05f7f1..ce9838ff76 100644 --- a/src/ScreenSelectMusic.cpp +++ b/src/ScreenSelectMusic.cpp @@ -26,10 +26,12 @@ #include "CommonMetrics.h" #include "BannerCache.h" //#include "BackgroundCache.h" +#include "ScreenPrompt.h" #include "Song.h" #include "InputEventPlus.h" #include "RageInput.h" #include "OptionsList.h" +#include "RageFileManager.h" static const char *SelectionStateNames[] = { "SelectingSong", @@ -50,6 +52,7 @@ AutoScreenMessage( SM_SongChanged ); AutoScreenMessage( SM_SortOrderChanging ); AutoScreenMessage( SM_SortOrderChanged ); AutoScreenMessage( SM_BackFromPlayerOptions ); +AutoScreenMessage( SM_ConfirmDeleteSong ); static RString g_sCDTitlePath; static bool g_bWantFallbackCdTitle; @@ -398,6 +401,8 @@ void ScreenSelectMusic::Update( float fDeltaTime ) CheckBackgroundRequests( false ); } + + bool ScreenSelectMusic::Input( const InputEventPlus &input ) { // HACK: This screen eats mouse inputs if we don't check for them first. @@ -462,6 +467,18 @@ bool ScreenSelectMusic::Input( const InputEventPlus &input ) return true; } } + else if( input.DeviceI.device == DEVICE_KEYBOARD && bHoldingCtrl && input.DeviceI.button == KEY_BACK && input.type == IET_FIRST_PRESS + && m_MusicWheel.IsSettled() ) + { + // Keyboard shortcut to delete a song from disk (ctrl + backspace) + Song* songToDelete = m_MusicWheel.GetSelectedSong(); + if ( songToDelete && PREFSMAN->m_bAllowSongDeletion.Get() ) + { + m_pSongAwaitingDeletionConfirmation = songToDelete; + ScreenPrompt::Prompt(SM_ConfirmDeleteSong, ssprintf("Permanently delete '%s' ( %s ) from disk?", songToDelete->m_sMainTitle.c_str(), songToDelete->GetSongDir().c_str()), PROMPT_YES_NO); + return true; + } + } } if( !input.GameI.IsValid() ) @@ -1169,6 +1186,18 @@ void ScreenSelectMusic::HandleScreenMessage( const ScreenMessage SM ) { CodeDetector::RefreshCacheItems(); // reset for other screens } + else if( SM == SM_ConfirmDeleteSong ) + { + if( ScreenPrompt::s_LastAnswer == ANSWER_YES ) + { + OnConfirmSongDeletion(); + } + else + { + // need to resume the song preview that was automatically paused + m_MusicWheel.ChangeMusic(0); + } + } ScreenWithMenuElements::HandleScreenMessage( SM ); } @@ -1907,6 +1936,34 @@ void ScreenSelectMusic::OpenOptionsList(PlayerNumber pn) } } +void ScreenSelectMusic::OnConfirmSongDeletion() +{ + Song* deletedSong = m_pSongAwaitingDeletionConfirmation; + if ( !deletedSong ) + { + LOG->Warn("Attempted to delete a null song (ScreenSelectMusic::OnConfirmSongDeletion)"); + return; + } + // ensure Stepmania is configured to allow song deletion + if ( !PREFSMAN->m_bAllowSongDeletion.Get() ) + { + LOG->Warn("Attemped to delete a song but AllowSongDeletion was set to false (ScreenSelectMusic::OnConfirmSongDeletion)"); + return; + } + + RString deleteDir = deletedSong->GetSongDir(); + // flush the deleted song from any caches + SONGMAN->UnlistSong(deletedSong); + // refresh the song list + m_MusicWheel.ReloadSongList(); + LOG->Trace("Deleting song: '%s'\n", deleteDir.c_str()); + // delete the song directory from disk + FILEMAN->DeleteRecursive(deleteDir); + + m_pSongAwaitingDeletionConfirmation = NULL; +} + + // lua start #include "LuaBinding.h" diff --git a/src/ScreenSelectMusic.h b/src/ScreenSelectMusic.h index da3c41a836..280c16316d 100644 --- a/src/ScreenSelectMusic.h +++ b/src/ScreenSelectMusic.h @@ -48,6 +48,7 @@ public: MusicWheel *GetMusicWheel() { return &m_MusicWheel; } void OpenOptionsList( PlayerNumber pn ); + void OnConfirmSongDeletion(); // Lua virtual void PushSelf( lua_State *L ); @@ -153,6 +154,8 @@ protected: BackgroundLoader m_BackgroundLoader; RageTexturePreloader m_TexturePreload; + + Song* m_pSongAwaitingDeletionConfirmation; }; #endif diff --git a/src/SongManager.cpp b/src/SongManager.cpp index 65cc5fbb06..a0ff971daa 100644 --- a/src/SongManager.cpp +++ b/src/SongManager.cpp @@ -423,16 +423,38 @@ void SongManager::FreeSongs() for( unsigned i=0; i* songVectors[3] = { &m_pSongs, &m_pPopularSongs, &m_pShuffledSongs }; + for (int songVecIdx=0; songVecIdx<3; ++songVecIdx) { + vector& v = *songVectors[songVecIdx]; + for (int i=0; i m_pSongs; + /** @brief Hold pointers to all the songs that have been deleted from disk but must at least be kept temporarily alive for smooth audio transitions. */ + vector m_pDeletedSongs; /** @brief The most popular songs ranked by number of plays. */ vector m_pPopularSongs; //vector m_pRecentSongs; // songs recently played on the machine diff --git a/src/WheelBase.h b/src/WheelBase.h index 67cf598d49..773144d78a 100644 --- a/src/WheelBase.h +++ b/src/WheelBase.h @@ -56,6 +56,8 @@ public: WheelState GetWheelState() { return m_WheelState; } bool WheelIsLocked() { return (m_WheelState == STATE_LOCKED ? true : false); } void RebuildWheelItems( int dist = INT_MAX ); // INT_MAX = refresh all + // Update the list of songs to match whatever songs are indexed by the song manager (SONGMAN) + virtual void ReloadSongList() {} virtual unsigned int GetNumItems() const { return m_CurWheelItemData.size(); } bool IsEmpty() { return m_bEmpty; }