Support deleting/renaming demo folders, improve demo popups

Support deleting and renaming folders in the demo browser. Only empty folders can be deleted.

Ensure only files and folders in the save directory can be deleted and renamed.

Also check if a folder with a demo rename/render filename already exists.

Fix broken `m_DemolistSelectedIsDir` checks by using `m_vDemos[m_DemolistSelectedIndex].m_IsDir` instead.

Append `.mp4` file extension only internally instead of appending it to the render filename lineinput, as this causes the file extension to appear when rendering doesn't start due an error message.

Use more efficient `FileExists` instead of `FindFile` to check if rendered demo video file already exists.

Change popup preconditions to assertions.
This commit is contained in:
Robert Müller 2023-07-09 12:45:26 +02:00
parent c9642e103f
commit 44bcbef0c1
3 changed files with 110 additions and 67 deletions

View file

@ -1214,7 +1214,8 @@ int CMenus::Render()
} }
else if(m_Popup == POPUP_RENAME_DEMO) else if(m_Popup == POPUP_RENAME_DEMO)
{ {
pTitle = Localize("Rename demo"); dbg_assert(m_DemolistSelectedIndex >= 0, "m_DemolistSelectedIndex invalid for POPUP_RENAME_DEMO");
pTitle = m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Rename folder") : Localize("Rename demo");
} }
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
else if(m_Popup == POPUP_RENDER_DEMO) else if(m_Popup == POPUP_RENDER_DEMO)
@ -1517,31 +1518,32 @@ int CMenus::Render()
{ {
m_Popup = POPUP_NONE; m_Popup = POPUP_NONE;
// rename demo // rename demo
if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir)
{
char aBufOld[IO_MAX_PATH_LENGTH]; char aBufOld[IO_MAX_PATH_LENGTH];
str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename);
char aBufNew[IO_MAX_PATH_LENGTH]; char aBufNew[IO_MAX_PATH_LENGTH];
str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_DemoRenameInput.GetString()); str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_DemoRenameInput.GetString());
if(!str_endswith(aBufNew, ".demo")) if(!m_vDemos[m_DemolistSelectedIndex].m_IsDir && !str_endswith(aBufNew, ".demo"))
str_append(aBufNew, ".demo"); str_append(aBufNew, ".demo");
if(Storage()->FileExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) if(Storage()->FileExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType))
{ {
PopupMessage(Localize("Error"), Localize("A demo with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO); PopupMessage(Localize("Error"), Localize("A demo with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO);
} }
else if(Storage()->FolderExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType))
{
PopupMessage(Localize("Error"), Localize("A folder with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO);
}
else if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType)) else if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType))
{ {
str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString()); str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString());
if(str_endswith(m_aCurrentDemoSelectionName, ".demo")) if(!m_vDemos[m_DemolistSelectedIndex].m_IsDir)
m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0'; fs_split_file_extension(m_DemoRenameInput.GetString(), m_aCurrentDemoSelectionName, sizeof(m_aCurrentDemoSelectionName));
DemolistPopulate(); DemolistPopulate();
DemolistOnUpdate(false); DemolistOnUpdate(false);
} }
else else
{ {
PopupMessage(Localize("Error"), Localize("Unable to rename the demo"), Localize("Ok"), POPUP_RENAME_DEMO); PopupMessage(Localize("Error"), m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Unable to rename the folder") : Localize("Unable to rename the demo"), Localize("Ok"), POPUP_RENAME_DEMO);
}
} }
} }
@ -1558,6 +1560,8 @@ int CMenus::Render()
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
else if(m_Popup == POPUP_RENDER_DEMO) else if(m_Popup == POPUP_RENDER_DEMO)
{ {
dbg_assert(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir, "m_DemolistSelectedIndex invalid for POPUP_RENDER_DEMO");
CUIRect Label, TextBox, Ok, Abort, Button; CUIRect Label, TextBox, Ok, Abort, Button;
Box.HSplitBottom(20.f, &Box, &Part); Box.HSplitBottom(20.f, &Box, &Part);
@ -1581,14 +1585,16 @@ int CMenus::Render()
if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER)) if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
{ {
m_Popup = POPUP_NONE; m_Popup = POPUP_NONE;
// name video // render video
if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir) char aVideoPath[IO_MAX_PATH_LENGTH];
str_format(aVideoPath, sizeof(aVideoPath), "videos/%s", m_DemoRenderInput.GetString());
if(!str_endswith(aVideoPath, ".mp4"))
str_append(aVideoPath, ".mp4");
if(Storage()->FolderExists(aVideoPath, IStorage::TYPE_SAVE))
{ {
if(!str_endswith(m_DemoRenderInput.GetString(), ".mp4")) PopupMessage(Localize("Error"), Localize("A folder with this name already exists"), Localize("Ok"), POPUP_RENDER_DEMO);
m_DemoRenderInput.Append(".mp4"); }
char aWholePath[IO_MAX_PATH_LENGTH]; else if(Storage()->FileExists(aVideoPath, IStorage::TYPE_SAVE))
// store new video filename to origin buffer
if(Storage()->FindFile(m_DemoRenderInput.GetString(), "videos", IStorage::TYPE_ALL, aWholePath, sizeof(aWholePath)))
{ {
char aMessage[128 + IO_MAX_PATH_LENGTH]; char aMessage[128 + IO_MAX_PATH_LENGTH];
str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_DemoRenderInput.GetString()); str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_DemoRenderInput.GetString());
@ -1599,7 +1605,6 @@ int CMenus::Render()
PopupConfirmDemoReplaceVideo(); PopupConfirmDemoReplaceVideo();
} }
} }
}
Box.HSplitBottom(30.f, &Box, 0); Box.HSplitBottom(30.f, &Box, 0);
Box.HSplitBottom(20.f, &Box, &Part); Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(10.f, &Box, 0); Box.HSplitBottom(10.f, &Box, 0);
@ -1789,7 +1794,11 @@ void CMenus::PopupConfirmDemoReplaceVideo()
{ {
char aBuf[IO_MAX_PATH_LENGTH]; char aBuf[IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename);
const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, m_DemoRenderInput.GetString(), m_Speed); char aVideoName[IO_MAX_PATH_LENGTH];
str_copy(aVideoName, m_DemoRenderInput.GetString());
if(!str_endswith(aVideoName, ".mp4"))
str_append(aVideoName, ".mp4");
const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, aVideoName, m_Speed);
m_Speed = 4; m_Speed = 4;
if(pError) if(pError)
PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok")); PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok"));

View file

@ -322,9 +322,10 @@ protected:
char m_aCurrentDemoSelectionName[IO_MAX_PATH_LENGTH]; char m_aCurrentDemoSelectionName[IO_MAX_PATH_LENGTH];
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenameInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenameInput;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoSliceInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoSliceInput;
#if defined(CONF_VIDEORECORDER)
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenderInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenderInput;
#endif
int m_DemolistSelectedIndex; int m_DemolistSelectedIndex;
bool m_DemolistSelectedIsDir;
bool m_DemolistSelectedReveal = false; bool m_DemolistSelectedReveal = false;
int m_DemolistStorageType; int m_DemolistStorageType;
bool m_DemolistMultipleStorages = false; bool m_DemolistMultipleStorages = false;
@ -428,6 +429,7 @@ protected:
void RenderDemoPlayerSliceSavePopup(CUIRect MainView); void RenderDemoPlayerSliceSavePopup(CUIRect MainView);
void RenderDemoList(CUIRect MainView); void RenderDemoList(CUIRect MainView);
void PopupConfirmDeleteDemo(); void PopupConfirmDeleteDemo();
void PopupConfirmDeleteFolder();
// found in menus_start.cpp // found in menus_start.cpp
void RenderStartMenu(CUIRect MainView); void RenderStartMenu(CUIRect MainView);

View file

@ -951,7 +951,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
MainView.VMargin(5.0f, &MainView); MainView.VMargin(5.0f, &MainView);
MainView.HSplitBottom(5.0f, &MainView, 0); MainView.HSplitBottom(5.0f, &MainView, 0);
MainView.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f); MainView.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f);
if(!m_DemolistSelectedIsDir && m_DemolistSelectedIndex >= 0 && m_vDemos[m_DemolistSelectedIndex].m_Valid) if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir && m_vDemos[m_DemolistSelectedIndex].m_Valid)
{ {
CUIRect Left, Right, Labels; CUIRect Left, Right, Labels;
MainView.VMargin(20.0f, &MainView); MainView.VMargin(20.0f, &MainView);
@ -1290,14 +1290,16 @@ void CMenus::RenderDemoList(CUIRect MainView)
} }
GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory that contains the demo files")); GameClient()->m_Tooltips.DoToolTip(&s_DirectoryButtonID, &DirectoryButton, Localize("Open the directory that contains the demo files"));
if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir) if(m_DemolistSelectedIndex >= 0 && m_aCurrentDemoFolder[0] != '\0')
{
if(str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") != 0 && m_vDemos[m_DemolistSelectedIndex].m_StorageType == IStorage::TYPE_SAVE)
{ {
static CButtonContainer s_DeleteButton; static CButtonContainer s_DeleteButton;
if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || UI()->ConsumeHotkey(CUI::HOTKEY_DELETE) || (Input()->KeyPress(KEY_D) && m_pClient->m_GameConsole.IsClosed())) if(DoButton_Menu(&s_DeleteButton, Localize("Delete"), 0, &DeleteRect) || UI()->ConsumeHotkey(CUI::HOTKEY_DELETE) || (Input()->KeyPress(KEY_D) && m_pClient->m_GameConsole.IsClosed()))
{ {
char aBuf[128 + IO_MAX_PATH_LENGTH]; char aBuf[128 + IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename); str_format(aBuf, sizeof(aBuf), m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Are you sure that you want to delete the folder '%s'?") : Localize("Are you sure that you want to delete the demo '%s'?"), m_vDemos[m_DemolistSelectedIndex].m_aFilename);
PopupConfirm(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo); PopupConfirm(m_vDemos[m_DemolistSelectedIndex].m_IsDir ? Localize("Delete folder") : Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), m_vDemos[m_DemolistSelectedIndex].m_IsDir ? &CMenus::PopupConfirmDeleteFolder : &CMenus::PopupConfirmDeleteDemo);
return; return;
} }
@ -1305,12 +1307,24 @@ void CMenus::RenderDemoList(CUIRect MainView)
if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect))
{ {
m_Popup = POPUP_RENAME_DEMO; m_Popup = POPUP_RENAME_DEMO;
if(m_vDemos[m_DemolistSelectedIndex].m_IsDir)
{
m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename);
}
else
{
char aNameWithoutExt[IO_MAX_PATH_LENGTH];
fs_split_file_extension(m_vDemos[m_DemolistSelectedIndex].m_aFilename, aNameWithoutExt, sizeof(aNameWithoutExt));
m_DemoRenameInput.Set(aNameWithoutExt);
}
UI()->SetActiveItem(&m_DemoRenameInput); UI()->SetActiveItem(&m_DemoRenameInput);
return; return;
} }
}
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
if(!m_vDemos[m_DemolistSelectedIndex].m_IsDir)
{
static CButtonContainer s_RenderButton; static CButtonContainer s_RenderButton;
if(DoButton_Menu(&s_RenderButton, Localize("Render"), 0, &RenderRect) || (Input()->KeyPress(KEY_R) && m_pClient->m_GameConsole.IsClosed())) if(DoButton_Menu(&s_RenderButton, Localize("Render"), 0, &RenderRect) || (Input()->KeyPress(KEY_R) && m_pClient->m_GameConsole.IsClosed()))
{ {
@ -1321,6 +1335,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
UI()->SetActiveItem(&m_DemoRenderInput); UI()->SetActiveItem(&m_DemoRenderInput);
return; return;
} }
}
#endif #endif
} }
@ -1343,3 +1358,20 @@ void CMenus::PopupConfirmDeleteDemo()
PopupMessage(Localize("Error"), aError, Localize("Ok")); PopupMessage(Localize("Error"), aError, Localize("Ok"));
} }
} }
void CMenus::PopupConfirmDeleteFolder()
{
char aBuf[IO_MAX_PATH_LENGTH];
str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename);
if(Storage()->RemoveFolder(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType))
{
DemolistPopulate();
DemolistOnUpdate(false);
}
else
{
char aError[128 + IO_MAX_PATH_LENGTH];
str_format(aError, sizeof(aError), Localize("Unable to delete the folder '%s'. Make sure it's empty first."), m_vDemos[m_DemolistSelectedIndex].m_aFilename);
PopupMessage(Localize("Error"), aError, Localize("Ok"));
}
}