diff --git a/build/Xenia.Cpp.x64.Common.props b/build/Xenia.Cpp.x64.Common.props index ac53c1d86..4c7ccc788 100644 --- a/build/Xenia.Cpp.x64.Common.props +++ b/build/Xenia.Cpp.x64.Common.props @@ -18,7 +18,7 @@ $(SolutionDir)build\bin\$(Configuration)\ - ntdll.lib;wsock32.lib;ws2_32.lib;xinput.lib;xaudio2.lib;glu32.lib;opengl32.lib;comctl32.lib;%(AdditionalDependencies) + ntdll.lib;wsock32.lib;ws2_32.lib;xinput.lib;xaudio2.lib;glu32.lib;opengl32.lib;comctl32.lib;shlwapi.lib;%(AdditionalDependencies) /ignore:4006 /ignore:4221 %(AdditionalOptions) MSVCRTD diff --git a/libxenia.vcxproj b/libxenia.vcxproj index adf121ff8..b3dce1255 100644 --- a/libxenia.vcxproj +++ b/libxenia.vcxproj @@ -203,6 +203,7 @@ + @@ -459,11 +460,13 @@ + + diff --git a/libxenia.vcxproj.filters b/libxenia.vcxproj.filters index 165bf7e35..9ff5950a3 100644 --- a/libxenia.vcxproj.filters +++ b/libxenia.vcxproj.filters @@ -778,6 +778,9 @@ src\xenia\base + + src\xenia\ui\win32 + @@ -1506,6 +1509,12 @@ src\xenia\apu + + src\xenia\ui + + + src\xenia\ui\win32 + diff --git a/src/xenia/ui/file_picker.h b/src/xenia/ui/file_picker.h new file mode 100644 index 000000000..1cd7553bb --- /dev/null +++ b/src/xenia/ui/file_picker.h @@ -0,0 +1,79 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_FILE_PICKER_H_ +#define XENIA_UI_FILE_PICKER_H_ + +#include +#include + +namespace xe { +namespace ui { + +class FilePicker { + public: + enum class Mode { + kOpen = 0, + kSave = 1, + }; + enum class Type { + kFile = 0, + kDirectory = 1, + }; + + FilePicker() + : mode_(Mode::kOpen), + type_(Type::kFile), + title_(L"Select Files"), + multi_selection_(false) {} + virtual ~FilePicker() = default; + + Mode mode() const { return mode_; } + void set_mode(Mode mode) { mode_ = mode; } + + Type type() const { return type_; } + void set_type(Type type) { type_ = type; } + + std::wstring title() const { return title_; } + void set_title(std::wstring title) { title_ = std::move(title); } + + std::vector> extensions() const { + return extensions_; + } + void set_extensions( + std::vector> extensions) { + extensions_ = std::move(extensions); + } + + bool multi_selection() const { return multi_selection_; } + void set_multi_selection(bool multi_selection) { + multi_selection_ = multi_selection; + } + + std::vector selected_files() const { return selected_files_; } + void set_selected_files(std::vector selected_files) { + selected_files_ = std::move(selected_files); + } + + virtual bool Show(void* parent_window_handle = nullptr) = 0; + + private: + Mode mode_; + Type type_; + std::wstring title_; + std::vector> extensions_; + bool multi_selection_; + + std::vector selected_files_; +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_FILE_PICKER_H_ diff --git a/src/xenia/ui/main_window.h b/src/xenia/ui/main_window.h index c4a4c0537..2c75e4953 100644 --- a/src/xenia/ui/main_window.h +++ b/src/xenia/ui/main_window.h @@ -14,6 +14,7 @@ #include "xenia/xbox.h" // TODO(benvanik): only on windows. +#include "xenia/ui/win32/win32_file_picker.h" #include "xenia/ui/win32/win32_loop.h" #include "xenia/ui/win32/win32_menu_item.h" #include "xenia/ui/win32/win32_window.h" @@ -28,6 +29,7 @@ namespace ui { using PlatformLoop = xe::ui::win32::Win32Loop; using PlatformWindow = xe::ui::win32::Win32Window; using PlatformMenu = xe::ui::win32::Win32MenuItem; +using PlatformFilePicker = xe::ui::win32::Win32FilePicker; class MainWindow : public PlatformWindow { public: diff --git a/src/xenia/ui/win32/win32_file_picker.cc b/src/xenia/ui/win32/win32_file_picker.cc new file mode 100644 index 000000000..3f84977f0 --- /dev/null +++ b/src/xenia/ui/win32/win32_file_picker.cc @@ -0,0 +1,210 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/win32/win32_file_picker.h" + +#include +#include +#include +#include +#include + +#include "xenia/base/assert.h" + +namespace xe { +namespace ui { +namespace win32 { + +class CDialogEventHandler : public IFileDialogEvents, + public IFileDialogControlEvents { + public: + // IUnknown methods + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) { + static const QITAB qit[] = { + {&__uuidof(IFileDialogEvents), + (int)OFFSETOFCLASS(IFileDialogEvents, CDialogEventHandler)}, + {&__uuidof(IFileDialogControlEvents), + (int)OFFSETOFCLASS(IFileDialogControlEvents, CDialogEventHandler)}, + {0}, + }; + return QISearch(this, qit, riid, ppv); + } + + IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&_cRef); } + + IFACEMETHODIMP_(ULONG) Release() { + long cRef = InterlockedDecrement(&_cRef); + if (!cRef) delete this; + return cRef; + } + + // IFileDialogEvents methods + IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; }; + IFACEMETHODIMP OnFolderChange(IFileDialog*) { return S_OK; }; + IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; }; + IFACEMETHODIMP OnHelp(IFileDialog*) { return S_OK; }; + IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; }; + IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, + FDE_SHAREVIOLATION_RESPONSE*) { + return S_OK; + }; + IFACEMETHODIMP OnTypeChange(IFileDialog* pfd) { return S_OK; }; + IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, + FDE_OVERWRITE_RESPONSE*) { + return S_OK; + }; + + // IFileDialogControlEvents methods + IFACEMETHODIMP OnItemSelected(IFileDialogCustomize* pfdc, DWORD dwIDCtl, + DWORD dwIDItem) { + return S_OK; + }; + IFACEMETHODIMP OnButtonClicked(IFileDialogCustomize*, DWORD) { return S_OK; }; + IFACEMETHODIMP OnCheckButtonToggled(IFileDialogCustomize*, DWORD, BOOL) { + return S_OK; + }; + IFACEMETHODIMP OnControlActivating(IFileDialogCustomize*, DWORD) { + return S_OK; + }; + + CDialogEventHandler() : _cRef(1){}; + + private: + ~CDialogEventHandler(){}; + long _cRef; +}; + +HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void** ppv) { + *ppv = NULL; + auto dialog_event_handler = new (std::nothrow) CDialogEventHandler(); + HRESULT hr = dialog_event_handler ? S_OK : E_OUTOFMEMORY; + if (SUCCEEDED(hr)) { + hr = dialog_event_handler->QueryInterface(riid, ppv); + dialog_event_handler->Release(); + } + return hr; +} + +Win32FilePicker::Win32FilePicker() = default; + +Win32FilePicker::~Win32FilePicker() = default; + +template +struct com_ptr { + com_ptr() : value(nullptr) {} + ~com_ptr() { reset(); } + void reset() { + if (value) { + value->Release(); + value = nullptr; + } + } + operator T*() { return value; } + T* operator->() const { return value; } + T** addressof() { return &value; } + T* value; +}; + +bool Win32FilePicker::Show(void* parent_window_handle) { + // TODO(benvanik): FileSaveDialog. + assert_true(mode() == Mode::kOpen); + // TODO(benvanik): folder dialogs. + assert_true(type() == Type::kFile); + + com_ptr file_dialog; + HRESULT hr = + CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(file_dialog.addressof())); + if (!SUCCEEDED(hr)) { + return false; + } + + hr = file_dialog->SetTitle(title().c_str()); + if (!SUCCEEDED(hr)) { + return false; + } + + DWORD flags; + hr = file_dialog->GetOptions(&flags); + if (!SUCCEEDED(hr)) { + return false; + } + // FOS_PICKFOLDERS + // FOS_FILEMUSTEXIST + // FOS_PATHMUSTEXIST + flags |= FOS_FORCEFILESYSTEM; + if (multi_selection()) { + flags |= FOS_ALLOWMULTISELECT; + } + hr = file_dialog->SetOptions(flags); + if (!SUCCEEDED(hr)) { + return false; + } + + // Set the file types to display only. Notice that this is a 1-based array. + std::vector save_types; + for (auto& extension : extensions()) { + save_types.push_back({extension.first.c_str(), extension.second.c_str()}); + } + hr = file_dialog->SetFileTypes(static_cast(save_types.size()), + save_types.data()); + if (!SUCCEEDED(hr)) { + return false; + } + + hr = file_dialog->SetFileTypeIndex(1); + if (!SUCCEEDED(hr)) { + return false; + } + + // Create an event handling object, and hook it up to the dialog. + com_ptr file_dialog_events; + hr = CDialogEventHandler_CreateInstance( + IID_PPV_ARGS(file_dialog_events.addressof())); + if (!SUCCEEDED(hr)) { + return false; + } + DWORD cookie; + hr = file_dialog->Advise(file_dialog_events, &cookie); + if (!SUCCEEDED(hr)) { + return false; + } + + // Show the dialog modally. + hr = file_dialog->Show(static_cast(parent_window_handle)); + file_dialog->Unadvise(cookie); + if (!SUCCEEDED(hr)) { + return false; + } + + // Obtain the result once the user clicks the 'Open' button. + // The result is an IShellItem object. + com_ptr shell_item; + hr = file_dialog->GetResult(shell_item.addressof()); + if (!SUCCEEDED(hr)) { + return false; + } + + // We are just going to print out the name of the file for sample sake. + PWSTR file_path = nullptr; + hr = shell_item->GetDisplayName(SIGDN_FILESYSPATH, &file_path); + if (!SUCCEEDED(hr)) { + return false; + } + std::vector selected_files; + selected_files.push_back(std::wstring(file_path)); + set_selected_files(selected_files); + CoTaskMemFree(file_path); + + return true; +} + +} // namespace win32 +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/win32/win32_file_picker.h b/src/xenia/ui/win32/win32_file_picker.h new file mode 100644 index 000000000..ade3c2ba2 --- /dev/null +++ b/src/xenia/ui/win32/win32_file_picker.h @@ -0,0 +1,33 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WIN32_WIN32_FILE_PICKER_H_ +#define XENIA_UI_WIN32_WIN32_FILE_PICKER_H_ + +#include "xenia/ui/file_picker.h" + +namespace xe { +namespace ui { +namespace win32 { + +class Win32FilePicker : public FilePicker { + public: + Win32FilePicker(); + ~Win32FilePicker() override; + + bool Show(void* parent_window_handle) override; + + private: +}; + +} // namespace win32 +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WIN32_WIN32_FILE_PICKER_H_ diff --git a/src/xenia/xenia_main.cc b/src/xenia/xenia_main.cc index a3f09392a..621b94e3a 100644 --- a/src/xenia/xenia_main.cc +++ b/src/xenia/xenia_main.cc @@ -14,6 +14,7 @@ #include "xenia/emulator.h" #include "xenia/kernel/kernel.h" #include "xenia/profiling.h" +#include "xenia/ui/file_picker.h" #include "xenia/ui/main_window.h" DEFINE_string(target, "", "Specifies the target .xex or .iso to execute."); @@ -33,8 +34,8 @@ int xenia_main(std::vector& args) { } // Grab path from the flag or unnamed argument. + std::wstring path; if (!FLAGS_target.empty() || args.size() >= 2) { - std::wstring path; if (!FLAGS_target.empty()) { // Passed as a named argument. // TODO(benvanik): find something better than gflags that supports @@ -44,6 +45,31 @@ int xenia_main(std::vector& args) { // Passed as an unnamed argument. path = args[1]; } + } + + // If no path passed, ask the user. + if (path.empty()) { + ui::PlatformFilePicker file_picker; + file_picker.set_mode(ui::FilePicker::Mode::kOpen); + file_picker.set_type(ui::FilePicker::Type::kFile); + file_picker.set_multi_selection(false); + file_picker.set_title(L"Select Content Package"); + file_picker.set_extensions({ + {L"Supported Files", L"*.iso;*.xex;*.xcp;*.*"}, + {L"Disc Image (*.iso)", L"*.iso"}, + {L"Xbox Executable (*.xex)", L"*.xex"}, + //{ L"Content Package (*.xcp)", L"*.xcp" }, + {L"All Files (*.*)", L"*.*"}, + }); + if (file_picker.Show(emulator->main_window()->hwnd())) { + auto selected_files = file_picker.selected_files(); + if (!selected_files.empty()) { + path = selected_files[0]; + } + } + } + + if (!path.empty()) { // Normalize the path and make absolute. std::wstring abs_path = xe::to_absolute_path(path);