From 62f54bb7adce9c4b6de94d4d2653c28335fe70f1 Mon Sep 17 00:00:00 2001 From: Attila Kocsis Date: Thu, 25 Apr 2019 22:29:34 +0200 Subject: [PATCH] File dialog added to entry Win32/Linux version is from texturev. OSX uses native dialog instead of zenity. win32 has a fix to support win32 target: OPENFILENAMEA customData is uintptr_t instead of uint64_t. --- examples/common/entry/dialog.cpp | 206 +++++++++++++++++++++++++++++++ examples/common/entry/dialog.h | 30 +++++ examples/common/entry/dialog.mm | 135 ++++++++++++++++++++ tools/texturev/texturev.cpp | 199 +---------------------------- 4 files changed, 373 insertions(+), 197 deletions(-) create mode 100644 examples/common/entry/dialog.cpp create mode 100644 examples/common/entry/dialog.h create mode 100644 examples/common/entry/dialog.mm diff --git a/examples/common/entry/dialog.cpp b/examples/common/entry/dialog.cpp new file mode 100644 index 000000000..27f457e60 --- /dev/null +++ b/examples/common/entry/dialog.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2019 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause + */ + +#include +#include +#include +#include +#include + +#include "dialog.h" + +#if BX_PLATFORM_WINDOWS +extern "C" void* __stdcall GetModuleHandleA(const char* _moduleName); +extern "C" uint32_t __stdcall GetModuleFileNameA(void* _module, char* _outFilePath, uint32_t _size); + +typedef uintptr_t (__stdcall *LPOFNHOOKPROC)(void*, uint32_t, uintptr_t, uint64_t); + +struct OPENFILENAMEA +{ + uint32_t structSize; + void* hwndOwner; + void* hinstance; + const char* filter; + const char* customFilter; + uint32_t maxCustomFilter; + uint32_t filterIndex; + const char* file; + uint32_t maxFile; + const char* fileTitle; + uint32_t maxFileTitle; + const char* initialDir; + const char* title; + uint32_t flags; + uint16_t fileOffset; + uint16_t fileExtension; + const char* defExt; + uintptr_t customData; + LPOFNHOOKPROC hook; + const char* templateName; + void* reserved0; + uint32_t reserved1; + uint32_t flagsEx; +}; + +extern "C" bool __stdcall GetOpenFileNameA(OPENFILENAMEA* _ofn); + +#endif // BX_PLATFORM_WINDOWS + +class Split +{ +public: + Split(const bx::StringView& _str, char _ch) + : m_str(_str) + , m_token(_str.getPtr(), bx::strFind(_str, _ch).getPtr() ) + , m_ch(_ch) + { + } + + bx::StringView next() + { + bx::StringView result = m_token; + m_token = bx::strTrim( + bx::StringView(m_token.getTerm()+1 + , bx::strFind(bx::StringView(m_token.getTerm()+1, m_str.getTerm() ), m_ch).getPtr() ) + , " \t\n" + ); + return result; + } + + bool isDone() const + { + return m_token.isEmpty(); + } + +private: + const bx::StringView& m_str; + bx::StringView m_token; + char m_ch; +}; + +#if !BX_PLATFORM_OSX +bool openFileSelectionDialog( + bx::FilePath& _inOutFilePath + , FileSelectionDialogType::Enum _type + , const bx::StringView& _title + , const bx::StringView& _filter + ) +{ +#if BX_PLATFORM_LINUX + char tmp[4096]; + bx::StaticMemoryBlockWriter writer(tmp, sizeof(tmp) ); + + bx::Error err; + bx::write(&writer, &err + , "--file-selection%s --title \"%.*s\" --filename \"%s\"" + , FileSelectionDialogType::Save == _type ? " --save" : "" + , _title.getLength(), _title.getPtr() + , _inOutFilePath.getCPtr() + ); + + for (bx::LineReader lr(_filter); !lr.isDone();) + { + const bx::StringView line = lr.next(); + + bx::write(&writer, &err + , " --file-filter \"%.*s\"" + , line.getLength(), line.getPtr() + ); + } + + if (err.isOk() ) + { + bx::ProcessReader pr; + + if (bx::open(&pr, "zenity", tmp, &err) ) + { + char buffer[1024]; + int32_t total = bx::read(&pr, buffer, sizeof(buffer), &err); + bx::close(&pr); + + if (0 == pr.getExitCode() ) + { + _inOutFilePath.set(bx::strRTrim(bx::StringView(buffer, total), "\n\r") ); + return true; + } + } + } +#elif BX_PLATFORM_WINDOWS + BX_UNUSED(_type); + + char out[bx::kMaxFilePath] = { '\0' }; + + OPENFILENAMEA ofn; + bx::memSet(&ofn, 0, sizeof(ofn) ); + ofn.structSize = sizeof(OPENFILENAMEA); + ofn.initialDir = _inOutFilePath.getCPtr(); + ofn.file = out; + ofn.maxFile = sizeof(out); + ofn.flags = 0 + | /* OFN_EXPLORER */ 0x00080000 + | /* OFN_FILEMUSTEXIST */ 0x00001000 + | /* OFN_DONTADDTORECENT */ 0x02000000 + ; + + char tmp[4096]; + bx::StaticMemoryBlockWriter writer(tmp, sizeof(tmp) ); + + bx::Error err; + + ofn.title = tmp; + bx::write(&writer, &err, "%.*s", _title.getLength(), _title.getPtr() ); + bx::write(&writer, '\0', &err); + + ofn.filter = tmp + uint32_t(bx::seek(&writer) ); + + for (bx::LineReader lr(_filter); !lr.isDone() && err.isOk();) + { + const bx::StringView line = lr.next(); + const bx::StringView sep = bx::strFind(line, '|'); + + if (!sep.isEmpty() ) + { + bx::write(&writer, bx::strTrim(bx::StringView(line.getPtr(), sep.getPtr() ), " "), &err); + bx::write(&writer, '\0', &err); + + bool first = true; + + for (Split split(bx::strTrim(bx::StringView(sep.getPtr()+1, line.getTerm() ), " "), ' '); !split.isDone() && err.isOk();) + { + const bx::StringView token = split.next(); + if (!first) + { + bx::write(&writer, ';', &err); + } + + first = false; + bx::write(&writer, token, &err); + } + + bx::write(&writer, '\0', &err); + } + else + { + bx::write(&writer, line, &err); + bx::write(&writer, '\0', &err); + bx::write(&writer, '\0', &err); + } + } + + bx::write(&writer, '\0', &err); + + if (err.isOk() + && GetOpenFileNameA(&ofn) ) + { + _inOutFilePath.set(ofn.file); + return true; + } +#else + BX_UNUSED(_inOutFilePath, _type, _title, _filter); +#endif // BX_PLATFORM_LINUX + + return false; +} +#endif !BX_PLATFORM_OSX diff --git a/examples/common/entry/dialog.h b/examples/common/entry/dialog.h new file mode 100644 index 000000000..3b6182ca8 --- /dev/null +++ b/examples/common/entry/dialog.h @@ -0,0 +1,30 @@ +/* + * Copyright 2010-2019 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause + */ + +#ifndef DIALOG_H_HEADER_GUARD +#define DIALOG_H_HEADER_GUARD + +namespace bx { class FilePath; class StringView; } + +struct FileSelectionDialogType +{ + enum Enum + { + Open, + Save, + + Count + }; +}; + +bool openFileSelectionDialog( + bx::FilePath& _inOutFilePath + , FileSelectionDialogType::Enum _type + , const bx::StringView& _title + , const bx::StringView& _filter = "All Files | *" + ); + + +#endif // DIALOG_H_HEADER_GUARD diff --git a/examples/common/entry/dialog.mm b/examples/common/entry/dialog.mm new file mode 100644 index 000000000..97e60e5fc --- /dev/null +++ b/examples/common/entry/dialog.mm @@ -0,0 +1,135 @@ +/* + * Copyright 2019-2019 Attila Kocsis. All rights reserved. + * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause + */ + +#include "entry_p.h" +#if BX_PLATFORM_OSX + +#include +#include +#include +#include +#include +#include + +#import + +#include "dialog.h" + +class Split +{ +public: + Split(const bx::StringView& _str, char _ch) + : m_str(_str) + , m_token(_str.getPtr(), bx::strFind(_str, _ch).getPtr() ) + , m_ch(_ch) + { + } + + bx::StringView next() + { + bx::StringView result = m_token; + m_token = bx::strTrim( + bx::StringView(m_token.getTerm()+1 + , bx::strFind(bx::StringView(m_token.getTerm()+1, m_str.getTerm() ), m_ch).getPtr() ) + , " \t\n" + ); + return result; + } + + bool isDone() const + { + return m_token.isEmpty(); + } + +private: + const bx::StringView& m_str; + bx::StringView m_token; + char m_ch; +}; + + +bool openFileSelectionDialog( + bx::FilePath& _inOutFilePath + , FileSelectionDialogType::Enum _type + , const bx::StringView& _title + , const bx::StringView& _filter + ) +{ + NSMutableArray* fileTypes = [NSMutableArray arrayWithCapacity:10]; + bx::Error err; + + for (bx::LineReader lr(_filter); !lr.isDone() && err.isOk();) + { + const bx::StringView line = lr.next(); + const bx::StringView sep = bx::strFind(line, '|'); + + if (!sep.isEmpty() ) + { + for (Split split(bx::strTrim(bx::StringView(sep.getPtr()+1, line.getTerm() ), " "), ' '); !split.isDone() && err.isOk();) + { + const bx::StringView token = split.next(); + + if ( token.getLength() >= 3 && token.getPtr()[0] == '*' + && token.getPtr()[1] == '.' && bx::isAlphaNum(token.getPtr()[2]) ) + { + NSString* extension = [[NSString alloc] initWithBytes:token.getPtr()+2 length:token.getLength()-2 encoding:NSASCIIStringEncoding]; + [fileTypes addObject: extension]; + } + } + } + } + + __block NSString* fileName = nil; + bx::Semaphore semaphore; + bx::Semaphore* psemaphore = &semaphore; + + CFRunLoopPerformBlock([[NSRunLoop mainRunLoop] getCFRunLoop], + kCFRunLoopCommonModes, + ^{ + NSSavePanel* panel = nil; + + if ( FileSelectionDialogType::Open == _type) + { + NSOpenPanel* openPanel = [NSOpenPanel openPanel]; + openPanel.canChooseFiles = TRUE; + openPanel.allowsMultipleSelection = FALSE; + openPanel.canChooseDirectories = FALSE; + panel = openPanel; + } + else + { + panel = [NSSavePanel savePanel]; + } + + panel.message = [[NSString alloc] initWithBytes:_title.getPtr() length:_title.getLength() encoding:NSASCIIStringEncoding]; + panel.directoryURL = [NSURL URLWithString:@(_inOutFilePath.getCPtr())]; + panel.allowedFileTypes = fileTypes; + + if ([panel runModal] == NSModalResponseOK) + { + NSURL* url = [panel URL]; + if (nil != url) + { + fileName = [url path]; + [fileName retain]; + } + } + [panel close]; + psemaphore->post(); + }); + + semaphore.wait(); + + if ( fileName != nil ) + { + _inOutFilePath.set([fileName UTF8String]); + [fileName release]; + return true; + } + + return false; +} + +#endif diff --git a/tools/texturev/texturev.cpp b/tools/texturev/texturev.cpp index 988df0b6b..045f72e10 100644 --- a/tools/texturev/texturev.cpp +++ b/tools/texturev/texturev.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -239,209 +240,13 @@ static const char* s_filter = "" "Truevision TGA (tga) | *.tga\n" ; -struct FileSelectionDialogType -{ - enum Enum - { - Open, - Save, - - Count - }; -}; - #if BX_PLATFORM_WINDOWS + extern "C" void* __stdcall GetModuleHandleA(const char* _moduleName); extern "C" uint32_t __stdcall GetModuleFileNameA(void* _module, char* _outFilePath, uint32_t _size); -typedef uintptr_t (__stdcall *LPOFNHOOKPROC)(void*, uint32_t, uintptr_t, uint64_t); - -struct OPENFILENAMEA -{ - uint32_t structSize; - void* hwndOwner; - void* hinstance; - const char* filter; - const char* customFilter; - uint32_t maxCustomFilter; - uint32_t filterIndex; - const char* file; - uint32_t maxFile; - const char* fileTitle; - uint32_t maxFileTitle; - const char* initialDir; - const char* title; - uint32_t flags; - uint16_t fileOffset; - uint16_t fileExtension; - const char* defExt; - uint64_t customData; - LPOFNHOOKPROC hook; - const char* templateName; - void* reserved0; - uint32_t reserved1; - uint32_t flagsEx; -}; - -extern "C" bool __stdcall GetOpenFileNameA(OPENFILENAMEA* _ofn); - #endif // BX_PLATFORM_WINDOWS -class Split -{ -public: - Split(const bx::StringView& _str, char _ch) - : m_str(_str) - , m_token(_str.getPtr(), bx::strFind(_str, _ch).getPtr() ) - , m_ch(_ch) - { - } - - bx::StringView next() - { - bx::StringView result = m_token; - m_token = bx::strTrim( - bx::StringView(m_token.getTerm()+1 - , bx::strFind(bx::StringView(m_token.getTerm()+1, m_str.getTerm() ), m_ch).getPtr() ) - , " \t\n" - ); - return result; - } - - bool isDone() const - { - return m_token.isEmpty(); - } - -private: - const bx::StringView& m_str; - bx::StringView m_token; - char m_ch; -}; - -bool openFileSelectionDialog( - bx::FilePath& _inOutFilePath - , FileSelectionDialogType::Enum _type - , const bx::StringView& _title - , const bx::StringView& _filter = "All Files | *" - ) -{ -#if BX_PLATFORM_LINUX || BX_PLATFORM_OSX - char tmp[4096]; - bx::StaticMemoryBlockWriter writer(tmp, sizeof(tmp) ); - - bx::Error err; - bx::write(&writer, &err - , "--file-selection%s --title \"%.*s\" --filename \"%s\"" - , FileSelectionDialogType::Save == _type ? " --save" : "" - , _title.getLength(), _title.getPtr() - , _inOutFilePath.getCPtr() - ); - - for (bx::LineReader lr(_filter); !lr.isDone();) - { - const bx::StringView line = lr.next(); - - bx::write(&writer, &err - , " --file-filter \"%.*s\"" - , line.getLength(), line.getPtr() - ); - } - - if (err.isOk() ) - { - bx::ProcessReader pr; - - if (bx::open(&pr, "zenity", tmp, &err) ) - { - char buffer[1024]; - int32_t total = bx::read(&pr, buffer, sizeof(buffer), &err); - bx::close(&pr); - - if (0 == pr.getExitCode() ) - { - _inOutFilePath.set(bx::strRTrim(bx::StringView(buffer, total), "\n\r") ); - return true; - } - } - } -#elif BX_PLATFORM_WINDOWS - BX_UNUSED(_type); - - char out[bx::kMaxFilePath] = { '\0' }; - - OPENFILENAMEA ofn; - bx::memSet(&ofn, 0, sizeof(ofn) ); - ofn.structSize = sizeof(OPENFILENAMEA); - ofn.initialDir = _inOutFilePath.getCPtr(); - ofn.file = out; - ofn.maxFile = sizeof(out); - ofn.flags = 0 - | /* OFN_EXPLORER */ 0x00080000 - | /* OFN_FILEMUSTEXIST */ 0x00001000 - | /* OFN_DONTADDTORECENT */ 0x02000000 - ; - - char tmp[4096]; - bx::StaticMemoryBlockWriter writer(tmp, sizeof(tmp) ); - - bx::Error err; - - ofn.title = tmp; - bx::write(&writer, &err, "%.*s", _title.getLength(), _title.getPtr() ); - bx::write(&writer, '\0', &err); - - ofn.filter = tmp + uint32_t(bx::seek(&writer) ); - - for (bx::LineReader lr(_filter); !lr.isDone() && err.isOk();) - { - const bx::StringView line = lr.next(); - const bx::StringView sep = bx::strFind(line, '|'); - - if (!sep.isEmpty() ) - { - bx::write(&writer, bx::strTrim(bx::StringView(line.getPtr(), sep.getPtr() ), " "), &err); - bx::write(&writer, '\0', &err); - - bool first = true; - - for (Split split(bx::strTrim(bx::StringView(sep.getPtr()+1, line.getTerm() ), " "), ' '); !split.isDone() && err.isOk();) - { - const bx::StringView token = split.next(); - if (!first) - { - bx::write(&writer, ';', &err); - } - - first = false; - bx::write(&writer, token, &err); - } - - bx::write(&writer, '\0', &err); - } - else - { - bx::write(&writer, line, &err); - bx::write(&writer, '\0', &err); - bx::write(&writer, '\0', &err); - } - } - - bx::write(&writer, '\0', &err); - - if (err.isOk() - && GetOpenFileNameA(&ofn) ) - { - _inOutFilePath.set(ofn.file); - return true; - } -#else - BX_UNUSED(_inOutFilePath, _type, _title, _filter); -#endif // BX_PLATFORM_LINUX || BX_PLATFORM_OSX - - return false; -} - struct View { View()