6752: Ensure `ListDirectory/Info` entries are unique, support selecting storage location in demo browser and editor file browser r=def- a=Robyt3

Description in commits below. Screenshots/video:

- Storage location selection for demos:
![screenshot_2023-06-18_17-25-23](https://github.com/ddnet/ddnet/assets/23437060/bcc94977-50ba-494b-894d-7bd8fea7f91e)

- Storage location selection for maps:
![screenshot_2023-06-18_17-25-33](https://github.com/ddnet/ddnet/assets/23437060/2b8b7822-b4b9-409f-86eb-e44f5c072541)

- Link to "themes" folder:
![screenshot_2023-06-18_17-26-02](https://github.com/ddnet/ddnet/assets/23437060/eba661a6-0bee-4977-ab95-35e400b5b291)

- Video showing navigation between storages (`temp` is the save directory and `temp2`, `temp3` and `data` are other storages)

https://github.com/ddnet/ddnet/assets/23437060/5736d212-3848-44c2-aa81-7f2da9b98008

Closes #5496.

## Checklist

- [X] Tested the change ingame
- [X] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-07-03 19:05:42 +00:00 committed by GitHub
commit 06fe73619e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 361 additions and 138 deletions

View file

@ -1,11 +1,14 @@
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#include "linereader.h"
#include <base/math.h> #include <base/math.h>
#include <base/system.h> #include <base/system.h>
#include <engine/client/updater.h> #include <engine/client/updater.h>
#include <engine/shared/linereader.h>
#include <engine/storage.h> #include <engine/storage.h>
#include <unordered_set>
#ifdef CONF_PLATFORM_HAIKU #ifdef CONF_PLATFORM_HAIKU
#include <cstdlib> #include <cstdlib>
#endif #endif
@ -309,14 +312,38 @@ public:
m_aBinarydir[0] = '\0'; m_aBinarydir[0] = '\0';
} }
int NumPaths() const override
{
return m_NumPaths;
}
struct SListDirectoryInfoUniqueCallbackData
{
FS_LISTDIR_CALLBACK_FILEINFO m_pfnDelegate;
void *m_pDelegateUser;
std::unordered_set<std::string> m_Seen;
};
static int ListDirectoryInfoUniqueCallback(const CFsFileInfo *pInfo, int IsDir, int Type, void *pUser)
{
SListDirectoryInfoUniqueCallbackData *pData = static_cast<SListDirectoryInfoUniqueCallbackData *>(pUser);
auto [_, InsertionTookPlace] = pData->m_Seen.emplace(pInfo->m_pName);
if(InsertionTookPlace)
return pData->m_pfnDelegate(pInfo, IsDir, Type, pData->m_pDelegateUser);
return 0;
}
void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) override
{ {
char aBuffer[IO_MAX_PATH_LENGTH]; char aBuffer[IO_MAX_PATH_LENGTH];
if(Type == TYPE_ALL) if(Type == TYPE_ALL)
{ {
SListDirectoryInfoUniqueCallbackData Data;
Data.m_pfnDelegate = pfnCallback;
Data.m_pDelegateUser = pUser;
// list all available directories // list all available directories
for(int i = TYPE_SAVE; i < m_NumPaths; ++i) for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); fs_listdir_fileinfo(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), ListDirectoryInfoUniqueCallback, i, &Data);
} }
else if(Type >= TYPE_SAVE && Type < m_NumPaths) else if(Type >= TYPE_SAVE && Type < m_NumPaths)
{ {
@ -329,14 +356,33 @@ public:
} }
} }
struct SListDirectoryUniqueCallbackData
{
FS_LISTDIR_CALLBACK m_pfnDelegate;
void *m_pDelegateUser;
std::unordered_set<std::string> m_Seen;
};
static int ListDirectoryUniqueCallback(const char *pName, int IsDir, int Type, void *pUser)
{
SListDirectoryUniqueCallbackData *pData = static_cast<SListDirectoryUniqueCallbackData *>(pUser);
auto [_, InsertionTookPlace] = pData->m_Seen.emplace(pName);
if(InsertionTookPlace)
return pData->m_pfnDelegate(pName, IsDir, Type, pData->m_pDelegateUser);
return 0;
}
void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) override
{ {
char aBuffer[IO_MAX_PATH_LENGTH]; char aBuffer[IO_MAX_PATH_LENGTH];
if(Type == TYPE_ALL) if(Type == TYPE_ALL)
{ {
SListDirectoryUniqueCallbackData Data;
Data.m_pfnDelegate = pfnCallback;
Data.m_pDelegateUser = pUser;
// list all available directories // list all available directories
for(int i = TYPE_SAVE; i < m_NumPaths; ++i) for(int i = TYPE_SAVE; i < m_NumPaths; ++i)
fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), pfnCallback, i, pUser); fs_listdir(GetPath(i, pPath, aBuffer, sizeof(aBuffer)), ListDirectoryUniqueCallback, i, &Data);
} }
else if(Type >= TYPE_SAVE && Type < m_NumPaths) else if(Type >= TYPE_SAVE && Type < m_NumPaths)
{ {

View file

@ -42,6 +42,8 @@ public:
STORAGETYPE_CLIENT, STORAGETYPE_CLIENT,
}; };
virtual int NumPaths() const = 0;
virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0; virtual void ListDirectory(int Type, const char *pPath, FS_LISTDIR_CALLBACK pfnCallback, void *pUser) = 0;
virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) = 0; virtual void ListDirectoryInfo(int Type, const char *pPath, FS_LISTDIR_CALLBACK_FILEINFO pfnCallback, void *pUser) = 0;
virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) = 0; virtual IOHANDLE OpenFile(const char *pFilename, int Flags, int Type, char *pBuffer = nullptr, int BufferSize = 0) = 0;

View file

@ -70,6 +70,7 @@ CMenus::CMenus()
m_ShowStart = true; m_ShowStart = true;
str_copy(m_aCurrentDemoFolder, "demos"); str_copy(m_aCurrentDemoFolder, "demos");
m_DemolistStorageType = IStorage::TYPE_ALL;
m_DemoPlayerState = DEMOPLAYER_NONE; m_DemoPlayerState = DEMOPLAYER_NONE;
m_Dummy = false; m_Dummy = false;
@ -1530,9 +1531,9 @@ int CMenus::Render()
} }
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(g_Config.m_UiDemoSelected, m_DemoRenameInput.GetString()); str_copy(m_aCurrentDemoSelectionName, m_DemoRenameInput.GetString());
if(str_endswith(g_Config.m_UiDemoSelected, ".demo")) if(str_endswith(m_aCurrentDemoSelectionName, ".demo"))
g_Config.m_UiDemoSelected[str_length(g_Config.m_UiDemoSelected) - str_length(".demo")] = '\0'; m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0';
DemolistPopulate(); DemolistPopulate();
DemolistOnUpdate(false); DemolistOnUpdate(false);
} }

View file

@ -259,6 +259,7 @@ protected:
char m_aFilename[IO_MAX_PATH_LENGTH]; char m_aFilename[IO_MAX_PATH_LENGTH];
char m_aName[IO_MAX_PATH_LENGTH]; char m_aName[IO_MAX_PATH_LENGTH];
bool m_IsDir; bool m_IsDir;
bool m_IsLink;
int m_StorageType; int m_StorageType;
time_t m_Date; time_t m_Date;
@ -318,6 +319,7 @@ protected:
}; };
char m_aCurrentDemoFolder[IO_MAX_PATH_LENGTH]; char m_aCurrentDemoFolder[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;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenderInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_DemoRenderInput;
@ -325,6 +327,7 @@ protected:
bool m_DemolistSelectedIsDir; bool m_DemolistSelectedIsDir;
bool m_DemolistSelectedReveal = false; bool m_DemolistSelectedReveal = false;
int m_DemolistStorageType; int m_DemolistStorageType;
bool m_DemolistMultipleStorages = false;
int m_Speed = 4; int m_Speed = 4;
std::chrono::nanoseconds m_DemoPopulateStartTime{0}; std::chrono::nanoseconds m_DemoPopulateStartTime{0};

View file

@ -724,9 +724,9 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView)
{ {
char aPath[IO_MAX_PATH_LENGTH]; char aPath[IO_MAX_PATH_LENGTH];
str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_DemoSliceInput.GetString()); str_format(aPath, sizeof(aPath), "%s/%s", m_aCurrentDemoFolder, m_DemoSliceInput.GetString());
str_copy(g_Config.m_UiDemoSelected, m_DemoSliceInput.GetString()); str_copy(m_aCurrentDemoSelectionName, m_DemoSliceInput.GetString());
if(str_endswith(g_Config.m_UiDemoSelected, ".demo")) if(str_endswith(m_aCurrentDemoSelectionName, ".demo"))
g_Config.m_UiDemoSelected[str_length(g_Config.m_UiDemoSelected) - str_length(".demo")] = '\0'; m_aCurrentDemoSelectionName[str_length(m_aCurrentDemoSelectionName) - str_length(".demo")] = '\0';
m_DemoPlayerState = DEMOPLAYER_NONE; m_DemoPlayerState = DEMOPLAYER_NONE;
Client()->DemoSlice(aPath, CMenus::DemoFilterChat, &s_RemoveChat); Client()->DemoSlice(aPath, CMenus::DemoFilterChat, &s_RemoveChat);
DemolistPopulate(); DemolistPopulate();
@ -741,7 +741,9 @@ void CMenus::RenderDemoPlayerSliceSavePopup(CUIRect MainView)
int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser) int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser)
{ {
CMenus *pSelf = (CMenus *)pUser; CMenus *pSelf = (CMenus *)pUser;
if(str_comp(pInfo->m_pName, ".") == 0 || (str_comp(pInfo->m_pName, "..") == 0 && str_comp(pSelf->m_aCurrentDemoFolder, "demos") == 0) || (!IsDir && !str_endswith(pInfo->m_pName, ".demo"))) if(str_comp(pInfo->m_pName, ".") == 0 ||
(str_comp(pInfo->m_pName, "..") == 0 && (pSelf->m_aCurrentDemoFolder[0] == '\0' || (!pSelf->m_DemolistMultipleStorages && str_comp(pSelf->m_aCurrentDemoFolder, "demos") == 0))) ||
(!IsDir && !str_endswith(pInfo->m_pName, ".demo")))
{ {
return 0; return 0;
} }
@ -762,6 +764,7 @@ int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int Stora
Item.m_Date = pInfo->m_TimeModified; Item.m_Date = pInfo->m_TimeModified;
} }
Item.m_IsDir = IsDir != 0; Item.m_IsDir = IsDir != 0;
Item.m_IsLink = false;
Item.m_StorageType = StorageType; Item.m_StorageType = StorageType;
pSelf->m_vDemos.push_back(Item); pSelf->m_vDemos.push_back(Item);
@ -776,8 +779,52 @@ int CMenus::DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int Stora
void CMenus::DemolistPopulate() void CMenus::DemolistPopulate()
{ {
m_vDemos.clear(); m_vDemos.clear();
if(!str_comp(m_aCurrentDemoFolder, "demos"))
m_DemolistStorageType = IStorage::TYPE_ALL; int NumStoragesWithDemos = 0;
for(int StorageType = IStorage::TYPE_SAVE; StorageType < Storage()->NumPaths(); ++StorageType)
{
if(Storage()->FolderExists("demos", StorageType))
{
NumStoragesWithDemos++;
}
}
m_DemolistMultipleStorages = NumStoragesWithDemos > 1;
if(m_aCurrentDemoFolder[0] == '\0')
{
{
CDemoItem Item;
str_copy(Item.m_aFilename, "demos");
str_copy(Item.m_aName, Localize("All combined"));
Item.m_InfosLoaded = false;
Item.m_Valid = false;
Item.m_Date = 0;
Item.m_IsDir = true;
Item.m_IsLink = true;
Item.m_StorageType = IStorage::TYPE_ALL;
m_vDemos.push_back(Item);
}
for(int StorageType = IStorage::TYPE_SAVE; StorageType < Storage()->NumPaths(); ++StorageType)
{
if(Storage()->FolderExists("demos", StorageType))
{
CDemoItem Item;
str_copy(Item.m_aFilename, "demos");
Storage()->GetCompletePath(StorageType, "demos", Item.m_aName, sizeof(Item.m_aName));
str_append(Item.m_aName, "/", sizeof(Item.m_aName));
Item.m_InfosLoaded = false;
Item.m_Valid = false;
Item.m_Date = 0;
Item.m_IsDir = true;
Item.m_IsLink = true;
Item.m_StorageType = StorageType;
m_vDemos.push_back(Item);
}
}
}
else
{
m_DemoPopulateStartTime = time_get_nanoseconds(); m_DemoPopulateStartTime = time_get_nanoseconds();
Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this); Storage()->ListDirectoryInfo(m_DemolistStorageType, m_aCurrentDemoFolder, DemolistFetchCallback, this);
@ -786,11 +833,12 @@ void CMenus::DemolistPopulate()
std::stable_sort(m_vDemos.begin(), m_vDemos.end()); std::stable_sort(m_vDemos.begin(), m_vDemos.end());
} }
}
void CMenus::DemolistOnUpdate(bool Reset) void CMenus::DemolistOnUpdate(bool Reset)
{ {
if(Reset) if(Reset)
g_Config.m_UiDemoSelected[0] = '\0'; m_aCurrentDemoSelectionName[0] = '\0';
else else
{ {
bool Found = false; bool Found = false;
@ -800,7 +848,7 @@ void CMenus::DemolistOnUpdate(bool Reset)
{ {
SelectedIndex++; SelectedIndex++;
if(str_comp(g_Config.m_UiDemoSelected, Item.m_aName) == 0) if(str_comp(m_aCurrentDemoSelectionName, Item.m_aName) == 0)
{ {
Found = true; Found = true;
break; break;
@ -813,7 +861,6 @@ void CMenus::DemolistOnUpdate(bool Reset)
m_DemolistSelectedIndex = Reset ? !m_vDemos.empty() ? 0 : -1 : m_DemolistSelectedIndex = Reset ? !m_vDemos.empty() ? 0 : -1 :
m_DemolistSelectedIndex >= (int)m_vDemos.size() ? m_vDemos.size() - 1 : m_DemolistSelectedIndex; m_DemolistSelectedIndex >= (int)m_vDemos.size() ? m_vDemos.size() - 1 : m_DemolistSelectedIndex;
m_DemolistSelectedIsDir = m_DemolistSelectedIndex < 0 ? false : m_vDemos[m_DemolistSelectedIndex].m_IsDir;
m_DemolistSelectedReveal = true; m_DemolistSelectedReveal = true;
} }
@ -854,7 +901,9 @@ void CMenus::RenderDemoList(CUIRect MainView)
CDemoItem &Item = m_vDemos[m_DemolistSelectedIndex]; CDemoItem &Item = m_vDemos[m_DemolistSelectedIndex];
if(str_comp(Item.m_aFilename, "..") == 0) if(str_comp(Item.m_aFilename, "..") == 0)
str_copy(aFooterLabel, Localize("Parent Folder")); str_copy(aFooterLabel, Localize("Parent Folder"));
else if(m_DemolistSelectedIsDir) else if(m_vDemos[m_DemolistSelectedIndex].m_IsLink)
str_copy(aFooterLabel, Localize("Folder Link"));
else if(m_vDemos[m_DemolistSelectedIndex].m_IsDir)
str_copy(aFooterLabel, Localize("Folder")); str_copy(aFooterLabel, Localize("Folder"));
else if(!FetchHeader(Item)) else if(!FetchHeader(Item))
str_copy(aFooterLabel, Localize("Invalid Demo")); str_copy(aFooterLabel, Localize("Invalid Demo"));
@ -1090,7 +1139,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
FileIcon.x += 2.0f; FileIcon.x += 2.0f;
const char *pIconType; const char *pIconType;
if(str_comp(Item.m_aFilename, "..") == 0) if(Item.m_IsLink || str_comp(Item.m_aFilename, "..") == 0)
pIconType = FONT_ICON_FOLDER_TREE; pIconType = FONT_ICON_FOLDER_TREE;
else if(Item.m_IsDir) else if(Item.m_IsDir)
pIconType = FONT_ICON_FOLDER; pIconType = FONT_ICON_FOLDER;
@ -1103,7 +1152,9 @@ void CMenus::RenderDemoList(CUIRect MainView)
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->TextColor(IconColor); TextRender()->TextColor(IconColor);
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING);
UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML); UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML);
TextRender()->SetRenderFlags(0);
TextRender()->TextColor(TextRender()->DefaultTextColor()); TextRender()->TextColor(TextRender()->DefaultTextColor());
TextRender()->SetCurFont(nullptr); TextRender()->SetCurFont(nullptr);
@ -1155,7 +1206,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
{ {
m_DemolistSelectedIndex = NewSelected; m_DemolistSelectedIndex = NewSelected;
if(m_DemolistSelectedIndex >= 0) if(m_DemolistSelectedIndex >= 0)
str_copy(g_Config.m_UiDemoSelected, m_vDemos[m_DemolistSelectedIndex].m_aName); str_copy(m_aCurrentDemoSelectionName, m_vDemos[m_DemolistSelectedIndex].m_aName);
DemolistOnUpdate(false); DemolistOnUpdate(false);
} }
@ -1174,22 +1225,41 @@ void CMenus::RenderDemoList(CUIRect MainView)
} }
static CButtonContainer s_PlayButton; static CButtonContainer s_PlayButton;
if(DoButton_Menu(&s_PlayButton, m_DemolistSelectedIsDir ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed())) if(DoButton_Menu(&s_PlayButton, (m_DemolistSelectedIndex >= 0 && m_vDemos[m_DemolistSelectedIndex].m_IsDir) ? Localize("Open") : Localize("Play", "Demo browser"), 0, &PlayRect) || s_ListBox.WasItemActivated() || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (Input()->KeyPress(KEY_P) && m_pClient->m_GameConsole.IsClosed()))
{ {
if(m_DemolistSelectedIndex >= 0) if(m_DemolistSelectedIndex >= 0)
{ {
if(m_DemolistSelectedIsDir) // folder if(m_vDemos[m_DemolistSelectedIndex].m_IsDir) // folder
{ {
if(str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0) // parent folder const bool ParentFolder = str_comp(m_vDemos[m_DemolistSelectedIndex].m_aFilename, "..") == 0;
fs_parent_dir(m_aCurrentDemoFolder); if(ParentFolder) // parent folder
{
str_copy(m_aCurrentDemoSelectionName, fs_filename(m_aCurrentDemoFolder));
str_append(m_aCurrentDemoSelectionName, "/");
if(fs_parent_dir(m_aCurrentDemoFolder))
{
m_aCurrentDemoFolder[0] = '\0';
if(m_DemolistStorageType == IStorage::TYPE_ALL)
{
m_aCurrentDemoSelectionName[0] = '\0'; // will select first list item
}
else
{
Storage()->GetCompletePath(m_DemolistStorageType, "demos", m_aCurrentDemoSelectionName, sizeof(m_aCurrentDemoSelectionName));
str_append(m_aCurrentDemoSelectionName, "/");
}
}
}
else // sub folder else // sub folder
{ {
if(m_aCurrentDemoFolder[0] != '\0')
str_append(m_aCurrentDemoFolder, "/"); str_append(m_aCurrentDemoFolder, "/");
str_append(m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename); else
m_DemolistStorageType = m_vDemos[m_DemolistSelectedIndex].m_StorageType; m_DemolistStorageType = m_vDemos[m_DemolistSelectedIndex].m_StorageType;
str_append(m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename);
} }
DemolistPopulate(); DemolistPopulate();
DemolistOnUpdate(true); DemolistOnUpdate(!ParentFolder);
} }
else // file else // file
{ {
@ -1211,8 +1281,7 @@ void CMenus::RenderDemoList(CUIRect MainView)
if(DoButton_Menu(&s_DirectoryButtonID, Localize("Demos directory"), 0, &DirectoryButton)) if(DoButton_Menu(&s_DirectoryButtonID, Localize("Demos directory"), 0, &DirectoryButton))
{ {
char aBuf[IO_MAX_PATH_LENGTH]; char aBuf[IO_MAX_PATH_LENGTH];
Storage()->GetCompletePath(IStorage::TYPE_SAVE, "demos", aBuf, sizeof(aBuf)); Storage()->GetCompletePath(m_DemolistSelectedIndex >= 0 ? m_vDemos[m_DemolistSelectedIndex].m_StorageType : IStorage::TYPE_SAVE, m_aCurrentDemoFolder[0] == '\0' ? "demos" : m_aCurrentDemoFolder, aBuf, sizeof(aBuf));
Storage()->CreateFolder("demos", IStorage::TYPE_SAVE);
if(!open_file(aBuf)) if(!open_file(aBuf))
{ {
dbg_msg("menus", "couldn't open file '%s'", aBuf); dbg_msg("menus", "couldn't open file '%s'", aBuf);
@ -1220,44 +1289,35 @@ 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_DemolistSelectedIsDir) if(m_DemolistSelectedIndex >= 0 && !m_vDemos[m_DemolistSelectedIndex].m_IsDir)
{ {
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()))
{
if(m_DemolistSelectedIndex >= 0)
{ {
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), 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(Localize("Delete demo"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDeleteDemo);
return; return;
} }
}
static CButtonContainer s_RenameButton; static CButtonContainer s_RenameButton;
if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect)) if(DoButton_Menu(&s_RenameButton, Localize("Rename"), 0, &RenameRect))
{
if(m_DemolistSelectedIndex >= 0)
{ {
m_Popup = POPUP_RENAME_DEMO; m_Popup = POPUP_RENAME_DEMO;
m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); m_DemoRenameInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename);
UI()->SetActiveItem(&m_DemoRenameInput); UI()->SetActiveItem(&m_DemoRenameInput);
return; return;
} }
}
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
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()))
{
if(m_DemolistSelectedIndex >= 0)
{ {
m_Popup = POPUP_RENDER_DEMO; m_Popup = POPUP_RENDER_DEMO;
m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename); m_DemoRenderInput.Set(m_vDemos[m_DemolistSelectedIndex].m_aFilename);
UI()->SetActiveItem(&m_DemoRenderInput); UI()->SetActiveItem(&m_DemoRenderInput);
return; return;
} }
}
#endif #endif
} }

View file

@ -823,7 +823,7 @@ bool CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUse
CEditor *pEditor = (CEditor *)pUser; CEditor *pEditor = (CEditor *)pUser;
if(pEditor->Load(pFileName, StorageType)) if(pEditor->Load(pFileName, StorageType))
{ {
pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder || (pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentLink && str_comp(pEditor->m_aFileDialogCurrentLink, "themes") == 0));
pEditor->m_Dialog = DIALOG_NONE; pEditor->m_Dialog = DIALOG_NONE;
return true; return true;
} }
@ -852,6 +852,8 @@ bool CEditor::CallbackAppendMap(const char *pFileName, int StorageType, void *pU
bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUser) bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUser)
{ {
dbg_assert(StorageType == IStorage::TYPE_SAVE, "Saving only allowed for IStorage::TYPE_SAVE");
CEditor *pEditor = static_cast<CEditor *>(pUser); CEditor *pEditor = static_cast<CEditor *>(pUser);
char aBuf[IO_MAX_PATH_LENGTH]; char aBuf[IO_MAX_PATH_LENGTH];
// add map extension // add map extension
@ -865,7 +867,7 @@ bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUse
if(pEditor->Save(pFileName)) if(pEditor->Save(pFileName))
{ {
str_copy(pEditor->m_aFileName, pFileName); str_copy(pEditor->m_aFileName, pFileName);
pEditor->m_ValidSaveFilename = StorageType == IStorage::TYPE_SAVE && pEditor->m_pFileDialogPath == pEditor->m_aFileDialogCurrentFolder; pEditor->m_ValidSaveFilename = true;
pEditor->m_Map.m_Modified = false; pEditor->m_Map.m_Modified = false;
} }
else else
@ -888,6 +890,8 @@ bool CEditor::CallbackSaveMap(const char *pFileName, int StorageType, void *pUse
bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser) bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void *pUser)
{ {
dbg_assert(StorageType == IStorage::TYPE_SAVE, "Saving only allowed for IStorage::TYPE_SAVE");
CEditor *pEditor = static_cast<CEditor *>(pUser); CEditor *pEditor = static_cast<CEditor *>(pUser);
char aBuf[IO_MAX_PATH_LENGTH]; char aBuf[IO_MAX_PATH_LENGTH];
// add map extension // add map extension
@ -4542,7 +4546,7 @@ static int EditorListdirCallback(const CFsFileInfo *pInfo, int IsDir, int Storag
{ {
CEditor *pEditor = (CEditor *)pUser; CEditor *pEditor = (CEditor *)pUser;
if((pInfo->m_pName[0] == '.' && (pInfo->m_pName[1] == 0 || if((pInfo->m_pName[0] == '.' && (pInfo->m_pName[1] == 0 ||
(pInfo->m_pName[1] == '.' && pInfo->m_pName[2] == 0 && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))) || (pInfo->m_pName[1] == '.' && pInfo->m_pName[2] == 0 && (pEditor->m_FileDialogShowingRoot || (!pEditor->m_FileDialogMultipleStorages && (!str_comp(pEditor->m_pFileDialogPath, "maps") || !str_comp(pEditor->m_pFileDialogPath, "mapres"))))))) ||
(!IsDir && ((pEditor->m_FileDialogFileType == CEditor::FILETYPE_MAP && !str_endswith(pInfo->m_pName, ".map")) || (!IsDir && ((pEditor->m_FileDialogFileType == CEditor::FILETYPE_MAP && !str_endswith(pInfo->m_pName, ".map")) ||
(pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && !str_endswith(pInfo->m_pName, ".png")) || (pEditor->m_FileDialogFileType == CEditor::FILETYPE_IMG && !str_endswith(pInfo->m_pName, ".png")) ||
(pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND && !str_endswith(pInfo->m_pName, ".opus"))))) (pEditor->m_FileDialogFileType == CEditor::FILETYPE_SOUND && !str_endswith(pInfo->m_pName, ".opus")))))
@ -4614,7 +4618,9 @@ void CEditor::RenderFileDialog()
if(m_FileDialogFileType == CEditor::FILETYPE_IMG || m_FileDialogFileType == CEditor::FILETYPE_SOUND) if(m_FileDialogFileType == CEditor::FILETYPE_IMG || m_FileDialogFileType == CEditor::FILETYPE_SOUND)
View.VSplitMid(&View, &Preview); View.VSplitMid(&View, &Preview);
// title // title bar
if(!m_FileDialogShowingRoot)
{
CUIRect ButtonTimeModified, ButtonFileName; CUIRect ButtonTimeModified, ButtonFileName;
Title.VSplitRight(10.0f, &Title, nullptr); Title.VSplitRight(10.0f, &Title, nullptr);
Title.VSplitRight(90.0f, &Title, &ButtonTimeModified); Title.VSplitRight(90.0f, &Title, &ButtonTimeModified);
@ -4663,19 +4669,20 @@ void CEditor::RenderFileDialog()
RefreshFilteredFileList(); RefreshFilteredFileList();
} }
}
Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f); Title.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 4.0f);
Title.VMargin(10.0f, &Title); Title.VMargin(10.0f, &Title);
UI()->DoLabel(&Title, m_pFileDialogTitle, 12.0f, TEXTALIGN_ML); UI()->DoLabel(&Title, m_pFileDialogTitle, 12.0f, TEXTALIGN_ML);
// pathbox // pathbox
if(m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType >= IStorage::TYPE_SAVE)
{
char aPath[IO_MAX_PATH_LENGTH], aBuf[128 + IO_MAX_PATH_LENGTH]; char aPath[IO_MAX_PATH_LENGTH], aBuf[128 + IO_MAX_PATH_LENGTH];
if(m_FilesSelectedIndex != -1)
Storage()->GetCompletePath(m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath)); Storage()->GetCompletePath(m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType, m_pFileDialogPath, aPath, sizeof(aPath));
else
aPath[0] = 0;
str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath); str_format(aBuf, sizeof(aBuf), "Current path: %s", aPath);
UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_ML); UI()->DoLabel(&PathBox, aBuf, 10.0f, TEXTALIGN_ML);
}
const auto &&UpdateFileNameInput = [this]() { const auto &&UpdateFileNameInput = [this]() {
if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir) if(m_FilesSelectedIndex >= 0 && !m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir)
@ -4898,14 +4905,16 @@ void CEditor::RenderFileDialog()
} }
else else
{ {
if(str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0) if(m_vpFilteredFileList[i]->m_IsLink || str_comp(m_vpFilteredFileList[i]->m_aFilename, "..") == 0)
pIconType = FONT_ICON_FOLDER_TREE; pIconType = FONT_ICON_FOLDER_TREE;
else else
pIconType = FONT_ICON_FOLDER; pIconType = FONT_ICON_FOLDER;
} }
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT)); TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING);
UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML); UI()->DoLabel(&FileIcon, pIconType, 12.0f, TEXTALIGN_ML);
TextRender()->SetRenderFlags(0);
TextRender()->SetCurFont(nullptr); TextRender()->SetCurFont(nullptr);
SLabelProperties Props; SLabelProperties Props;
@ -4945,16 +4954,39 @@ void CEditor::RenderFileDialog()
CUIRect Button; CUIRect Button;
ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button); ButtonBar.VSplitRight(50.0f, &ButtonBar, &Button);
bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir; const bool IsDir = m_FilesSelectedIndex >= 0 && m_vpFilteredFileList[m_FilesSelectedIndex]->m_IsDir;
if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || s_ListBox.WasItemActivated()) if(DoButton_Editor(&s_OkButton, IsDir ? "Open" : m_pFileDialogButtonText, 0, &Button, 0, nullptr) || s_ListBox.WasItemActivated())
{ {
if(IsDir) // folder if(IsDir) // folder
{ {
m_FileDialogFilterInput.Clear(); m_FileDialogFilterInput.Clear();
if(str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0) // parent folder const bool ParentFolder = str_comp(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, "..") == 0;
if(ParentFolder) // parent folder
{ {
str_copy(m_aFilesSelectedName, fs_filename(m_pFileDialogPath));
str_append(m_aFilesSelectedName, "/");
if(fs_parent_dir(m_pFileDialogPath)) if(fs_parent_dir(m_pFileDialogPath))
{
if(str_comp(m_pFileDialogPath, m_aFileDialogCurrentFolder) == 0)
{
m_FileDialogShowingRoot = true;
if(m_FileDialogStorageType == IStorage::TYPE_ALL)
{
m_aFilesSelectedName[0] = '\0'; // will select first list item
}
else
{
Storage()->GetCompletePath(m_FileDialogStorageType, m_pFileDialogPath, m_aFilesSelectedName, sizeof(m_aFilesSelectedName));
str_append(m_aFilesSelectedName, "/");
}
}
else
{
m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link m_pFileDialogPath = m_aFileDialogCurrentFolder; // leave the link
str_copy(m_aFilesSelectedName, m_aFileDialogCurrentLink);
str_append(m_aFilesSelectedName, "/");
}
}
} }
else // sub folder else // sub folder
{ {
@ -4969,28 +5001,26 @@ void CEditor::RenderFileDialog()
str_copy(aTemp, m_pFileDialogPath); str_copy(aTemp, m_pFileDialogPath);
str_format(m_pFileDialogPath, IO_MAX_PATH_LENGTH, "%s/%s", aTemp, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); str_format(m_pFileDialogPath, IO_MAX_PATH_LENGTH, "%s/%s", aTemp, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
} }
if(m_FileDialogShowingRoot)
m_FileDialogStorageType = m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType;
m_FileDialogShowingRoot = false;
} }
FilelistPopulate(!str_comp(m_pFileDialogPath, "maps") || !str_comp(m_pFileDialogPath, "mapres") ? m_FileDialogStorageType : FilelistPopulate(m_FileDialogStorageType, ParentFolder);
m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType);
UpdateFileNameInput(); UpdateFileNameInput();
} }
else // file else // file
{ {
const int StorageType = m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType;
str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_FileDialogFileNameInput.GetString()); str_format(m_aFileSaveName, sizeof(m_aFileSaveName), "%s/%s", m_pFileDialogPath, m_FileDialogFileNameInput.GetString());
if(!str_endswith(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType])) if(!str_endswith(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType]))
str_append(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType]); str_append(m_aFileSaveName, FILETYPE_EXTENSIONS[m_FileDialogFileType]);
if(!str_comp(m_pFileDialogButtonText, "Save")) if(!str_comp(m_pFileDialogButtonText, "Save") && Storage()->FileExists(m_aFileSaveName, StorageType))
{
if(Storage()->FileExists(m_aFileSaveName, IStorage::TYPE_SAVE))
{ {
m_PopupEventType = m_pfnFileDialogFunc == &CallbackSaveCopyMap ? POPEVENT_SAVE_COPY : POPEVENT_SAVE; m_PopupEventType = m_pfnFileDialogFunc == &CallbackSaveCopyMap ? POPEVENT_SAVE_COPY : POPEVENT_SAVE;
m_PopupEventActivated = true; m_PopupEventActivated = true;
} }
else if(m_pfnFileDialogFunc) else if(m_pfnFileDialogFunc)
m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser); m_pfnFileDialogFunc(m_aFileSaveName, StorageType, m_pFileDialogUser);
}
else if(m_pfnFileDialogFunc)
m_pfnFileDialogFunc(m_aFileSaveName, m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : m_FileDialogStorageType, m_pFileDialogUser);
} }
} }
@ -5008,9 +5038,11 @@ void CEditor::RenderFileDialog()
ButtonBar.VSplitRight(90.0f, &ButtonBar, &Button); ButtonBar.VSplitRight(90.0f, &ButtonBar, &Button);
if(DoButton_Editor(&s_ShowDirectoryButton, "Show directory", 0, &Button, 0, "Open the current directory in the file browser")) if(DoButton_Editor(&s_ShowDirectoryButton, "Show directory", 0, &Button, 0, "Open the current directory in the file browser"))
{ {
if(!open_file(aPath)) char aOpenPath[IO_MAX_PATH_LENGTH];
Storage()->GetCompletePath(m_FilesSelectedIndex >= 0 ? m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType : IStorage::TYPE_SAVE, m_pFileDialogPath, aOpenPath, sizeof(aOpenPath));
if(!open_file(aOpenPath))
{ {
ShowFileDialogError("Failed to open the directory '%s'.", aPath); ShowFileDialogError("Failed to open the directory '%s'.", aOpenPath);
} }
} }
@ -5051,7 +5083,7 @@ void CEditor::RenderFileDialog()
else else
s_ConfirmDeletePopupContext.Reset(); s_ConfirmDeletePopupContext.Reset();
if(m_FileDialogStorageType == IStorage::TYPE_SAVE) if(!m_FileDialogShowingRoot && m_FileDialogStorageType == IStorage::TYPE_SAVE)
{ {
ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar); ButtonBar.VSplitLeft(70.0f, &Button, &ButtonBar);
if(DoButton_Editor(&s_NewFolderButton, "New folder", 0, &Button, 0, nullptr)) if(DoButton_Editor(&s_NewFolderButton, "New folder", 0, &Button, 0, nullptr))
@ -5076,6 +5108,7 @@ void CEditor::RefreshFilteredFileList()
m_vpFilteredFileList.push_back(&Item); m_vpFilteredFileList.push_back(&Item);
} }
} }
if(!m_FileDialogShowingRoot)
SortFilteredFileList(); SortFilteredFileList();
if(!m_vpFilteredFileList.empty()) if(!m_vpFilteredFileList.empty())
{ {
@ -5104,18 +5137,66 @@ void CEditor::FilelistPopulate(int StorageType, bool KeepSelection)
{ {
m_FileDialogLastPopulatedStorageType = StorageType; m_FileDialogLastPopulatedStorageType = StorageType;
m_vCompleteFileList.clear(); m_vCompleteFileList.clear();
if(m_FileDialogStorageType != IStorage::TYPE_SAVE && !str_comp(m_pFileDialogPath, "maps")) if(m_FileDialogShowingRoot)
{
{
CFilelistItem Item;
str_copy(Item.m_aFilename, m_pFileDialogPath);
str_copy(Item.m_aName, "All combined");
Item.m_IsDir = true;
Item.m_IsLink = true;
Item.m_StorageType = IStorage::TYPE_ALL;
Item.m_TimeModified = 0;
m_vCompleteFileList.push_back(Item);
}
for(int CheckStorageType = IStorage::TYPE_SAVE; CheckStorageType < Storage()->NumPaths(); ++CheckStorageType)
{
if(Storage()->FolderExists(m_pFileDialogPath, CheckStorageType))
{
CFilelistItem Item;
str_copy(Item.m_aFilename, m_pFileDialogPath);
Storage()->GetCompletePath(CheckStorageType, m_pFileDialogPath, Item.m_aName, sizeof(Item.m_aName));
str_append(Item.m_aName, "/", sizeof(Item.m_aName));
Item.m_IsDir = true;
Item.m_IsLink = true;
Item.m_StorageType = CheckStorageType;
Item.m_TimeModified = 0;
m_vCompleteFileList.push_back(Item);
}
}
}
else
{
// Add links for downloadedmaps and themes
if(!str_comp(m_pFileDialogPath, "maps"))
{
if(str_comp(m_pFileDialogButtonText, "Save") != 0 && Storage()->FolderExists("downloadedmaps", StorageType))
{ {
CFilelistItem Item; CFilelistItem Item;
str_copy(Item.m_aFilename, "downloadedmaps"); str_copy(Item.m_aFilename, "downloadedmaps");
str_copy(Item.m_aName, "downloadedmaps/"); str_copy(Item.m_aName, "downloadedmaps/");
Item.m_IsDir = true; Item.m_IsDir = true;
Item.m_IsLink = true; Item.m_IsLink = true;
Item.m_StorageType = IStorage::TYPE_SAVE; Item.m_StorageType = StorageType;
Item.m_TimeModified = 0; Item.m_TimeModified = 0;
m_vCompleteFileList.push_back(Item); m_vCompleteFileList.push_back(Item);
} }
if(Storage()->FolderExists("themes", StorageType))
{
CFilelistItem Item;
str_copy(Item.m_aFilename, "themes");
str_copy(Item.m_aName, "themes/");
Item.m_IsDir = true;
Item.m_IsLink = true;
Item.m_StorageType = StorageType;
Item.m_TimeModified = 0;
m_vCompleteFileList.push_back(Item);
}
}
Storage()->ListDirectoryInfo(StorageType, m_pFileDialogPath, EditorListdirCallback, this); Storage()->ListDirectoryInfo(StorageType, m_pFileDialogPath, EditorListdirCallback, this);
}
RefreshFilteredFileList(); RefreshFilteredFileList();
if(!KeepSelection) if(!KeepSelection)
{ {
@ -5132,8 +5213,25 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
const char *pBasePath, const char *pDefaultName, const char *pBasePath, const char *pDefaultName,
bool (*pfnFunc)(const char *pFileName, int StorageType, void *pUser), void *pUser) bool (*pfnFunc)(const char *pFileName, int StorageType, void *pUser), void *pUser)
{ {
UI()->ClosePopupMenus();
m_FileDialogStorageType = StorageType; m_FileDialogStorageType = StorageType;
if(m_FileDialogStorageType == IStorage::TYPE_ALL)
{
int NumStoragedWithFolder = 0;
for(int CheckStorageType = IStorage::TYPE_SAVE; CheckStorageType < Storage()->NumPaths(); ++CheckStorageType)
{
if(Storage()->FolderExists(m_pFileDialogPath, CheckStorageType))
{
NumStoragedWithFolder++;
}
}
m_FileDialogMultipleStorages = NumStoragedWithFolder > 1;
}
else
{
m_FileDialogMultipleStorages = false;
}
UI()->ClosePopupMenus();
m_pFileDialogTitle = pTitle; m_pFileDialogTitle = pTitle;
m_pFileDialogButtonText = pButtonText; m_pFileDialogButtonText = pButtonText;
m_pfnFileDialogFunc = pfnFunc; m_pfnFileDialogFunc = pfnFunc;
@ -5146,6 +5244,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
m_FileDialogFileType = FileType; m_FileDialogFileType = FileType;
m_FilePreviewState = PREVIEW_UNLOADED; m_FilePreviewState = PREVIEW_UNLOADED;
m_FileDialogOpening = true; m_FileDialogOpening = true;
m_FileDialogShowingRoot = false;
if(pDefaultName) if(pDefaultName)
m_FileDialogFileNameInput.Set(pDefaultName); m_FileDialogFileNameInput.Set(pDefaultName);
@ -7151,8 +7250,15 @@ void CEditor::OnRender()
void CEditor::LoadCurrentMap() void CEditor::LoadCurrentMap()
{ {
Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_ALL); if(Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_SAVE))
{
m_ValidSaveFilename = true; m_ValidSaveFilename = true;
}
else
{
Load(m_pClient->GetCurrentMapPath(), IStorage::TYPE_ALL);
m_ValidSaveFilename = false;
}
CGameClient *pGameClient = (CGameClient *)Kernel()->RequestInterface<IGameClient>(); CGameClient *pGameClient = (CGameClient *)Kernel()->RequestInterface<IGameClient>();
vec2 Center = pGameClient->m_Camera.m_Center; vec2 Center = pGameClient->m_Camera.m_Center;

View file

@ -977,6 +977,8 @@ public:
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_FileDialogFilterInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_FileDialogFilterInput;
char *m_pFileDialogPath; char *m_pFileDialogPath;
int m_FileDialogFileType; int m_FileDialogFileType;
bool m_FileDialogMultipleStorages = false;
bool m_FileDialogShowingRoot = false;
int m_FilesSelectedIndex; int m_FilesSelectedIndex;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_FileDialogNewFolderNameInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_FileDialogNewFolderNameInput;
@ -1004,6 +1006,8 @@ public:
return true; return true;
if(str_comp(pRhs->m_aFilename, "..") == 0) if(str_comp(pRhs->m_aFilename, "..") == 0)
return false; return false;
if(pLhs->m_IsLink != pRhs->m_IsLink)
return pLhs->m_IsLink;
if(pLhs->m_IsDir != pRhs->m_IsDir) if(pLhs->m_IsDir != pRhs->m_IsDir)
return pLhs->m_IsDir; return pLhs->m_IsDir;
return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) < 0; return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) < 0;
@ -1015,6 +1019,8 @@ public:
return true; return true;
if(str_comp(pRhs->m_aFilename, "..") == 0) if(str_comp(pRhs->m_aFilename, "..") == 0)
return false; return false;
if(pLhs->m_IsLink != pRhs->m_IsLink)
return pLhs->m_IsLink;
if(pLhs->m_IsDir != pRhs->m_IsDir) if(pLhs->m_IsDir != pRhs->m_IsDir)
return pLhs->m_IsDir; return pLhs->m_IsDir;
return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) > 0; return str_comp_filenames(pLhs->m_aName, pRhs->m_aName) > 0;

View file

@ -140,7 +140,6 @@ MACRO_CONFIG_COL(UiColor, ui_color, 0xE4A046AF, CFGFLAG_CLIENT | CFGFLAG_SAVE |
MACRO_CONFIG_INT(UiColorizePing, ui_colorize_ping, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight ping") MACRO_CONFIG_INT(UiColorizePing, ui_colorize_ping, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight ping")
MACRO_CONFIG_INT(UiColorizeGametype, ui_colorize_gametype, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight gametype") MACRO_CONFIG_INT(UiColorizeGametype, ui_colorize_gametype, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Highlight gametype")
MACRO_CONFIG_STR(UiDemoSelected, ui_demo_selected, 256, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Selected demo file")
MACRO_CONFIG_INT(UiCloseWindowAfterChangingSetting, ui_close_window_after_changing_setting, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Close window after changing setting") MACRO_CONFIG_INT(UiCloseWindowAfterChangingSetting, ui_close_window_after_changing_setting, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Close window after changing setting")
MACRO_CONFIG_INT(UiUnreadNews, ui_unread_news, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether there is unread news") MACRO_CONFIG_INT(UiUnreadNews, ui_unread_news, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Whether there is unread news")