diff --git a/install/menu.xml b/install/menu.xml index a3be463e06..697fcd8c3a 100644 --- a/install/menu.xml +++ b/install/menu.xml @@ -4,6 +4,7 @@ + diff --git a/radiant/ui/UserInterfaceModule.cpp b/radiant/ui/UserInterfaceModule.cpp index 5eb7074de8..c030daae53 100644 --- a/radiant/ui/UserInterfaceModule.cpp +++ b/radiant/ui/UserInterfaceModule.cpp @@ -59,6 +59,7 @@ #include "ui/brush/QuerySidesDialog.h" #include "ui/brush/FindBrush.h" #include "ui/mousetool/RegistrationHelper.h" +#include "ui/mapselector/MapSelector.h" #include @@ -408,6 +409,7 @@ void UserInterfaceModule::registerUICommands() GlobalEventManager().addRegistryToggle("TogTexLock", RKEY_ENABLE_TEXTURE_LOCK); GlobalCommandSystem().addCommand("LoadPrefab", ui::loadPrefabDialog); + GlobalCommandSystem().addCommand("OpenMapFromProject", ui::MapSelector::OpenMapFromProject); } void UserInterfaceModule::HandleTextureChanged(radiant::TextureChangedMessage& msg) diff --git a/radiant/ui/mapselector/MapSelector.cpp b/radiant/ui/mapselector/MapSelector.cpp new file mode 100644 index 0000000000..289633cbf5 --- /dev/null +++ b/radiant/ui/mapselector/MapSelector.cpp @@ -0,0 +1,117 @@ +#include "MapSelector.h" + +#include "i18n.h" + +#include +#include + +namespace ui +{ + +// CONSTANTS +namespace +{ + const char* const MAPSELECTOR_TITLE = N_("Choose Map File"); +} + +MapSelector::MapSelector() : + DialogBase(_(MAPSELECTOR_TITLE)), + _treeView(nullptr), + _handlingSelectionChange(false) +{ + SetSizer(new wxBoxSizer(wxVERTICAL)); + + wxBoxSizer* vbox = new wxBoxSizer(wxVERTICAL); + GetSizer()->Add(vbox, 1, wxEXPAND | wxALL, 12); + + setupTreeView(this); + vbox->Add(_treeView, 1, wxEXPAND); + + wxStdDialogButtonSizer* buttonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + wxButton* reloadButton = new wxButton(this, wxID_ANY, _("Rescan")); + reloadButton->Bind(wxEVT_BUTTON, &MapSelector::onRescanPath, this); + + buttonSizer->Prepend(reloadButton, 0, wxRIGHT, 32); + + vbox->Add(buttonSizer, 0, wxALIGN_RIGHT | wxTOP, 12); + + // Set the default size of the window + _position.connect(this); + _position.readPosition(); + + FitToScreen(0.5f, 0.6f); +} + +int MapSelector::ShowModal() +{ + // Populate the tree + populateTree(); + + // Enter the main loop + return DialogBase::ShowModal(); +} + +std::string MapSelector::ChooseMapFile() +{ + auto* dialog = new MapSelector(); + std::string returnValue = ""; + + if (dialog->ShowModal() == wxID_OK) + { + returnValue = dialog->getSelectedPath(); + } + + // Use the instance to select a model. + return returnValue; +} + +void MapSelector::OpenMapFromProject(const cmd::ArgumentList& args) +{ + auto mapPath = ChooseMapFile(); + + if (!mapPath.empty()) + { + GlobalCommandSystem().executeCommand("OpenMap", mapPath); + } +} + +// Helper function to create the TreeView +void MapSelector::setupTreeView(wxWindow* parent) +{ + _treeView = wxutil::FileSystemView::Create(parent, wxBORDER_STATIC | wxDV_NO_HEADER); + + // Get the extensions from all possible patterns (e.g. *.map or *.mapx) + FileTypePatterns patterns = GlobalFiletypes().getPatternsForType(filetype::TYPE_MAP); + + std::set fileExtensions; + + for (const auto& pattern : patterns) + { + fileExtensions.insert(pattern.extension); + } + + _treeView->SetFileExtensions(fileExtensions); +} + +std::string MapSelector::getBaseFolder() +{ + return ""; // use an empty path which resembles the VFS root +} + +void MapSelector::populateTree() +{ + _treeView->SetBasePath(getBaseFolder()); + _treeView->Populate(); +} + +void MapSelector::onRescanPath(wxCommandEvent& ev) +{ + populateTree(); +} + +std::string MapSelector::getSelectedPath() +{ + return _treeView->GetSelectedPath(); +} + +} diff --git a/radiant/ui/mapselector/MapSelector.h b/radiant/ui/mapselector/MapSelector.h new file mode 100644 index 0000000000..1f60ceea60 --- /dev/null +++ b/radiant/ui/mapselector/MapSelector.h @@ -0,0 +1,57 @@ +#pragma once + +#include "icommandsystem.h" + +#include "wxutil/dialog/DialogBase.h" +#include "wxutil/fsview/FileSystemView.h" +#include "wxutil/WindowPosition.h" + +namespace ui +{ + +/// Dialog for browsing and selecting a map from (V)FS +class MapSelector : + public wxutil::DialogBase +{ +private: + wxPanel* _dialogPanel; + + // Main tree view with the folder hierarchy + wxutil::FileSystemView* _treeView; + + // The window position tracker + wxutil::WindowPosition _position; + + bool _handlingSelectionChange; + +private: + // Private constructor, creates widgets + MapSelector(); + + // Helper functions to configure GUI components + void setupTreeView(wxWindow* parent); + + // Populate the tree view with files + void populateTree(); + + // Get the path that should be used for map population + // This reflects the settings made by the user on the top of the selector window + std::string getBaseFolder(); + + // Return the selected map path + std::string getSelectedPath(); + + void onRescanPath(wxCommandEvent& ev); + +public: + int ShowModal() override; + + /** + * Display the Selector return the path of the file selected by the user. + */ + static std::string ChooseMapFile(); + + static void OpenMapFromProject(const cmd::ArgumentList& args); +}; + +} diff --git a/radiantcore/map/Map.cpp b/radiantcore/map/Map.cpp index d1cc4732bd..8ce5c47913 100644 --- a/radiantcore/map/Map.cpp +++ b/radiantcore/map/Map.cpp @@ -702,17 +702,26 @@ void Map::openMap(const cmd::ArgumentList& args) } else if (!candidate.empty()) { - // Next, try to look up the map in the regular maps path - fs::path mapsPath = GlobalGameManager().getMapPath(); - fs::path fullMapPath = mapsPath / candidate; - - if (os::fileOrDirExists(fullMapPath.string())) + // Try to open this file from the VFS (this will hit physical files + // in the active project as well as files in registered PK4) + if (GlobalFileSystem().openTextFile(candidate)) { - mapToLoad = fullMapPath.string(); + mapToLoad = candidate; } else { - throw cmd::ExecutionFailure(fmt::format(_("File doesn't exist: {0}"), fullMapPath.string())); + // Next, try to look up the map in the regular maps path + fs::path mapsPath = GlobalGameManager().getMapPath(); + fs::path fullMapPath = mapsPath / candidate; + + if (os::fileOrDirExists(fullMapPath.string())) + { + mapToLoad = fullMapPath.string(); + } + else + { + throw cmd::ExecutionFailure(fmt::format(_("File doesn't exist: {0}"), candidate)); + } } } diff --git a/tools/msvc/DarkRadiant.vcxproj b/tools/msvc/DarkRadiant.vcxproj index 24036097c2..92b98ccc0f 100644 --- a/tools/msvc/DarkRadiant.vcxproj +++ b/tools/msvc/DarkRadiant.vcxproj @@ -268,6 +268,7 @@ + @@ -454,6 +455,7 @@ + diff --git a/tools/msvc/DarkRadiant.vcxproj.filters b/tools/msvc/DarkRadiant.vcxproj.filters index f9d2c61e24..135cf31a8a 100644 --- a/tools/msvc/DarkRadiant.vcxproj.filters +++ b/tools/msvc/DarkRadiant.vcxproj.filters @@ -173,6 +173,9 @@ {28ae583f-8545-491c-abcc-5e459c3d357b} + + {6ddb8b69-4e5a-4c82-b7d4-aa1306342340} + @@ -667,6 +670,9 @@ src\ui + + src\ui\mapselector + @@ -1284,6 +1290,9 @@ src\ui\common + + src\ui\mapselector +