-
Notifications
You must be signed in to change notification settings - Fork 365
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Native File Browser for Graph Editor (#1256)
This PR adds support for using a platform native file browser to the MaterialXGraphEditor.
- Loading branch information
Showing
6 changed files
with
401 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
// | ||
// Copyright Contributors to the MaterialX Project | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
#include "FileDialog.h" | ||
|
||
#include <MaterialXCore/Exception.h> | ||
|
||
#if defined(_WIN32) | ||
#include <windows.h> | ||
#endif | ||
|
||
FileDialog::FileDialog(int flags) : | ||
_flags(flags) | ||
{ | ||
} | ||
|
||
void FileDialog::setTitle(const std::string& title) | ||
{ | ||
_title = title; | ||
} | ||
|
||
void FileDialog::setTypeFilters(const mx::StringVec& typeFilters) | ||
{ | ||
_filetypes.clear(); | ||
|
||
for (auto typefilter : typeFilters) | ||
{ | ||
std::string minus_ext = typefilter.substr(1, typefilter.size() - 1); | ||
std::pair<std::string, std::string> filterPair = { minus_ext, minus_ext }; | ||
_filetypes.push_back(filterPair); | ||
} | ||
} | ||
|
||
void FileDialog::open() | ||
{ | ||
clearSelected(); | ||
_openFlag = true; | ||
} | ||
|
||
bool FileDialog::isOpened() | ||
{ | ||
return _isOpened; | ||
} | ||
|
||
bool FileDialog::hasSelected() | ||
{ | ||
return !_selectedFilenames.empty(); | ||
} | ||
|
||
mx::FilePath FileDialog::getSelected() | ||
{ | ||
if (_selectedFilenames.empty()) | ||
{ | ||
return {}; | ||
} | ||
|
||
return *_selectedFilenames.begin(); | ||
} | ||
|
||
void FileDialog::clearSelected() | ||
{ | ||
_selectedFilenames.clear(); | ||
} | ||
|
||
void FileDialog::display() | ||
{ | ||
// Only call the dialog if it's not already displayed | ||
if (!_openFlag || _isOpened) | ||
{ | ||
return; | ||
} | ||
_openFlag = false; | ||
|
||
// Check if we want to save or open | ||
bool save = !(_flags & FileDialogFlags_SelectDirectory) && | ||
(_flags & FileDialogFlags_EnterNewFilename); | ||
|
||
std::string path = launchFileDialog(_filetypes, save); | ||
if (!path.empty()) | ||
{ | ||
_selectedFilenames.push_back(path); | ||
} | ||
|
||
_isOpened = false; | ||
} | ||
|
||
std::string launchFileDialog(const std::vector<std::pair<std::string, std::string>>& filetypes, bool save) | ||
{ | ||
mx::StringVec result = launchFileDialog(filetypes, save, false); | ||
return result.empty() ? "" : result.front(); | ||
} | ||
|
||
#if !defined(__APPLE__) | ||
mx::StringVec launchFileDialog(const std::vector<std::pair<std::string, std::string>>& filetypes, bool save, bool multiple) | ||
{ | ||
static const int FILE_DIALOG_MAX_BUFFER = 16384; | ||
if (save && multiple) | ||
{ | ||
throw mx::Exception("save and multiple must not both be true."); | ||
} | ||
|
||
#if defined(_WIN32) | ||
OPENFILENAME ofn; | ||
ZeroMemory(&ofn, sizeof(OPENFILENAME)); | ||
ofn.lStructSize = sizeof(OPENFILENAME); | ||
char tmp[FILE_DIALOG_MAX_BUFFER]; | ||
ofn.lpstrFile = tmp; | ||
ZeroMemory(tmp, FILE_DIALOG_MAX_BUFFER); | ||
ofn.nMaxFile = FILE_DIALOG_MAX_BUFFER; | ||
ofn.nFilterIndex = 1; | ||
|
||
std::string filter; | ||
|
||
if (!save && filetypes.size() > 1) | ||
{ | ||
filter.append("Supported file types ("); | ||
for (size_t i = 0; i < filetypes.size(); ++i) | ||
{ | ||
filter.append("*."); | ||
filter.append(filetypes[i].first); | ||
if (i + 1 < filetypes.size()) | ||
filter.append(";"); | ||
} | ||
filter.append(")"); | ||
filter.push_back('\0'); | ||
for (size_t i = 0; i < filetypes.size(); ++i) | ||
{ | ||
filter.append("*."); | ||
filter.append(filetypes[i].first); | ||
if (i + 1 < filetypes.size()) | ||
filter.append(";"); | ||
} | ||
filter.push_back('\0'); | ||
} | ||
for (auto pair : filetypes) | ||
{ | ||
filter.append(pair.second); | ||
filter.append(" (*."); | ||
filter.append(pair.first); | ||
filter.append(")"); | ||
filter.push_back('\0'); | ||
filter.append("*."); | ||
filter.append(pair.first); | ||
filter.push_back('\0'); | ||
} | ||
filter.push_back('\0'); | ||
ofn.lpstrFilter = filter.data(); | ||
|
||
if (save) | ||
{ | ||
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT; | ||
if (GetSaveFileNameA(&ofn) == FALSE) | ||
return {}; | ||
} | ||
else | ||
{ | ||
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; | ||
if (multiple) | ||
ofn.Flags |= OFN_ALLOWMULTISELECT; | ||
if (GetOpenFileNameA(&ofn) == FALSE) | ||
return {}; | ||
} | ||
|
||
size_t i = 0; | ||
mx::StringVec result; | ||
while (tmp[i] != '\0') | ||
{ | ||
result.emplace_back(&tmp[i]); | ||
i += result.back().size() + 1; | ||
} | ||
|
||
if (result.size() > 1) | ||
{ | ||
for (i = 1; i < result.size(); ++i) | ||
{ | ||
result[i] = result[0] + "\\" + result[i]; | ||
} | ||
result.erase(begin(result)); | ||
} | ||
|
||
if (save && ofn.nFilterIndex > 0) | ||
{ | ||
auto ext = filetypes[ofn.nFilterIndex - 1].first; | ||
if (ext != "*") | ||
{ | ||
ext.insert(0, "."); | ||
|
||
auto& name = result.front(); | ||
if (name.size() <= ext.size() || | ||
name.compare(name.size() - ext.size(), ext.size(), ext) != 0) | ||
{ | ||
name.append(ext); | ||
} | ||
} | ||
} | ||
|
||
return result; | ||
#else | ||
char buffer[FILE_DIALOG_MAX_BUFFER]; | ||
buffer[0] = '\0'; | ||
|
||
std::string cmd = "zenity --file-selection "; | ||
// The safest separator for multiple selected paths is /, since / can never occur | ||
// in file names. Only where two paths are concatenated will there be two / following | ||
// each other. | ||
if (multiple) | ||
cmd += "--multiple --separator=\"/\" "; | ||
if (save) | ||
cmd += "--save "; | ||
cmd += "--file-filter=\""; | ||
for (auto pair : filetypes) | ||
cmd += "\"*." + pair.first + "\" "; | ||
cmd += "\""; | ||
FILE* output = popen(cmd.c_str(), "r"); | ||
if (output == nullptr) | ||
throw mx::Exception("popen() failed -- could not launch zenity!"); | ||
while (fgets(buffer, FILE_DIALOG_MAX_BUFFER, output) != NULL) | ||
; | ||
pclose(output); | ||
std::string paths(buffer); | ||
paths.erase(std::remove(paths.begin(), paths.end(), '\n'), paths.end()); | ||
|
||
mx::StringVec result; | ||
while (!paths.empty()) | ||
{ | ||
size_t end = paths.find("//"); | ||
if (end == std::string::npos) | ||
{ | ||
result.emplace_back(paths); | ||
paths = ""; | ||
} | ||
else | ||
{ | ||
result.emplace_back(paths.substr(0, end)); | ||
paths = paths.substr(end + 1); | ||
} | ||
} | ||
|
||
return result; | ||
#endif | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// | ||
// Copyright Contributors to the MaterialX Project | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
#ifndef MATERIALX_FILEDIALOG_H | ||
#define MATERIALX_FILEDIALOG_H | ||
|
||
#include <MaterialXFormat/File.h> | ||
|
||
namespace mx = MaterialX; | ||
|
||
enum FileDialogFlags | ||
{ | ||
FileDialogFlags_SelectDirectory = 1 << 0, // select directory instead of regular file | ||
FileDialogFlags_EnterNewFilename = 1 << 1, // allow user to enter new filename when selecting regular file | ||
FileDialogFlags_NoModal = 1 << 2, // file browsing window is modal by default. specify this to use a popup window | ||
FileDialogFlags_NoTitleBar = 1 << 3, // hide window title bar | ||
FileDialogFlags_NoStatusBar = 1 << 4, // hide status bar at the bottom of browsing window | ||
FileDialogFlags_CloseOnEsc = 1 << 5, // close file browser when pressing 'ESC' | ||
FileDialogFlags_CreateNewDir = 1 << 6, // allow user to create new directory | ||
FileDialogFlags_MultipleSelection = 1 << 7, // allow user to select multiple files. this will hide FileDialogFlags_EnterNewFilename | ||
}; | ||
|
||
// A native file browser class, based on the implementation in NanoGUI. | ||
class FileDialog | ||
{ | ||
public: | ||
FileDialog(int flags = 0); | ||
void setTitle(const std::string& title); | ||
void setTypeFilters(const mx::StringVec& typeFilters); | ||
void open(); | ||
bool isOpened(); | ||
void display(); | ||
bool hasSelected(); | ||
mx::FilePath getSelected(); | ||
void clearSelected(); | ||
|
||
private: | ||
int _flags; | ||
std::string _title; | ||
bool _openFlag = false; | ||
bool _isOpened = false; | ||
std::vector<mx::FilePath> _selectedFilenames; | ||
std::vector<std::pair<std::string, std::string>> _filetypes; | ||
}; | ||
|
||
std::string launchFileDialog(const std::vector<std::pair<std::string, std::string>>& filetypes, bool save); | ||
mx::StringVec launchFileDialog(const std::vector<std::pair<std::string, std::string>>& filetypes, bool save, bool multiple); | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// | ||
// Copyright Contributors to the MaterialX Project | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
#include "FileDialog.h" | ||
|
||
#include <MaterialXCore/Exception.h> | ||
|
||
#import <Cocoa/Cocoa.h> | ||
|
||
mx::StringVec launchFileDialog(const std::vector<std::pair<std::string, std::string>>& filetypes, bool save, bool multiple) | ||
{ | ||
if (save && multiple) | ||
{ | ||
throw mx::Exception("launchFileDialog(): 'save' and 'multiple' must not both be true."); | ||
} | ||
|
||
mx::StringVec result; | ||
if (save) | ||
{ | ||
NSSavePanel* saveDlg = [NSSavePanel savePanel]; | ||
|
||
NSMutableArray* types = [NSMutableArray new]; | ||
for (size_t idx = 0; idx < filetypes.size(); ++idx) | ||
[types addObject:[NSString stringWithUTF8String:filetypes[idx].first.c_str()]]; | ||
|
||
[saveDlg setAllowedFileTypes:types]; | ||
|
||
if ([saveDlg runModal] == NSModalResponseOK) | ||
result.emplace_back([[[saveDlg URL] path] UTF8String]); | ||
} | ||
else | ||
{ | ||
NSOpenPanel* openDlg = [NSOpenPanel openPanel]; | ||
|
||
[openDlg setCanChooseFiles:YES]; | ||
[openDlg setCanChooseDirectories:NO]; | ||
[openDlg setAllowsMultipleSelection:multiple]; | ||
NSMutableArray* types = [NSMutableArray new]; | ||
for (size_t idx = 0; idx < filetypes.size(); ++idx) | ||
[types addObject:[NSString stringWithUTF8String:filetypes[idx].first.c_str()]]; | ||
|
||
[openDlg setAllowedFileTypes:types]; | ||
|
||
if ([openDlg runModal] == NSModalResponseOK) | ||
{ | ||
for (NSURL* url in [openDlg URLs]) | ||
{ | ||
result.emplace_back((char*) [[url path] UTF8String]); | ||
} | ||
} | ||
} | ||
return result; | ||
} |
Oops, something went wrong.