* Add UI::openFileBrowser that matches Window::openFileBrowser * Add empty implementation so it builds * Move file browser dialog implementation into its own file Signed-off-by: falkTX <falktx@falktx.com> * Fix warnings Signed-off-by: falkTX <falktx@falktx.com> * Fix tests; Add non-implemented saving flag Signed-off-by: falkTX <falktx@falktx.com> * Initial DBus/freedesktop file browser implementation Signed-off-by: falkTX <falktx@falktx.com> * Build fixes Signed-off-by: falkTX <falktx@falktx.com> * Fix window id Signed-off-by: falkTX <falktx@falktx.com> * More build fixes Signed-off-by: falkTX <falktx@falktx.com> * More file dialog tweaks Signed-off-by: falkTX <falktx@falktx.com> * Attempted fixes Signed-off-by: falkTX <falktx@falktx.com> * Fix C++98 build Signed-off-by: falkTX <falktx@falktx.com> * Fix windows build Signed-off-by: falkTX <falktx@falktx.com> * Really fix windows builds Signed-off-by: falkTX <falktx@falktx.com> * Fix for MSVC Signed-off-by: falkTX <falktx@falktx.com> * Yet another fix attempt Signed-off-by: falkTX <falktx@falktx.com> * Also fix macOS side Signed-off-by: falkTX <falktx@falktx.com> * More attempted fixes, this is getting annoying... Signed-off-by: falkTX <falktx@falktx.com> * FileBrowserDialog: Implement saving in Windows Signed-off-by: falkTX <falktx@falktx.com> * FileBrowserDialog: Implement saving on macOS Signed-off-by: falkTX <falktx@falktx.com> * Rework last commit Signed-off-by: falkTX <falktx@falktx.com> * One more macOS fix needed Signed-off-by: falkTX <falktx@falktx.com> * unref dbus connection on close Signed-off-by: falkTX <falktx@falktx.com> * More build fixes Signed-off-by: falkTX <falktx@falktx.com> * Hopefully final macOS fix Signed-off-by: falkTX <falktx@falktx.com> * Add libdbus-1-dev to CI Signed-off-by: falkTX <falktx@falktx.com> * Check that org.freedesktop.portal.Desktop exists before connecting Signed-off-by: falkTX <falktx@falktx.com> * Less indentation Signed-off-by: falkTX <falktx@falktx.com> * Fix macOS buildpull/351/head
@@ -30,6 +30,7 @@ jobs: | |||
liblo-dev \ | |||
libgl-dev \ | |||
libcairo2-dev \ | |||
libdbus-1-dev \ | |||
libx11-dev | |||
- name: Create Build Environment | |||
shell: bash | |||
@@ -26,7 +26,7 @@ jobs: | |||
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list | |||
echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list | |||
sudo apt-get update -qq | |||
sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static | |||
sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libdbus-1-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static | |||
# fix broken Ubuntu packages missing pkg-config file in multi-arch package | |||
sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev | |||
sudo ln -s /usr/lib/aarch64-linux-gnu/liblo.so.7 /usr/lib/aarch64-linux-gnu/liblo.so | |||
@@ -63,7 +63,7 @@ jobs: | |||
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list | |||
echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list | |||
sudo apt-get update -qq | |||
sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static | |||
sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libdbus-1-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static | |||
# fix broken Ubuntu packages missing pkg-config file in multi-arch package | |||
sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev | |||
sudo ln -s /usr/lib/arm-linux-gnueabihf/liblo.so.7 /usr/lib/arm-linux-gnueabihf/liblo.so | |||
@@ -96,7 +96,7 @@ jobs: | |||
run: | | |||
sudo dpkg --add-architecture i386 | |||
sudo apt-get update -qq | |||
sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386 | |||
sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libdbus-1-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386 | |||
- name: Build linux x86 | |||
env: | |||
CFLAGS: -m32 | |||
@@ -124,7 +124,7 @@ jobs: | |||
- name: Set up dependencies | |||
run: | | |||
sudo apt-get update -qq | |||
sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev | |||
sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev | |||
- name: Build linux x86_64 | |||
env: | |||
LDFLAGS: -static-libgcc -static-libstdc++ | |||
@@ -250,7 +250,7 @@ jobs: | |||
sudo dpkg -i kxstudio-repos_10.0.3_all.deb | |||
sudo apt-get update -qq | |||
# build-deps | |||
sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev | |||
sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev | |||
# runtime testing | |||
sudo apt-get install -yq carla-git lilv-utils lv2-dev lv2lint valgrind | |||
- name: Build plugins | |||
@@ -20,7 +20,7 @@ jobs: | |||
- name: Set up dependencies | |||
run: | | |||
sudo apt-get update -qq | |||
sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb | |||
sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb | |||
- name: Without any warnings | |||
env: | |||
CFLAGS: -Werror | |||
@@ -238,6 +238,7 @@ HAVE_OPENGL = true | |||
else | |||
HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true) | |||
ifneq ($(HAIKU),true) | |||
HAVE_DBUS = $(shell $(PKG_CONFIG) --exists dbus-1 && echo true) | |||
HAVE_X11 = $(shell $(PKG_CONFIG) --exists x11 && echo true) | |||
HAVE_XCURSOR = $(shell $(PKG_CONFIG) --exists xcursor && echo true) | |||
HAVE_XEXT = $(shell $(PKG_CONFIG) --exists xext && echo true) | |||
@@ -284,6 +285,10 @@ DGL_SYSTEM_LIBS += -lgdi32 -lcomdlg32 | |||
endif | |||
ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) | |||
ifeq ($(HAVE_DBUS),true) | |||
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags dbus-1) -DHAVE_DBUS | |||
DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs dbus-1) | |||
endif | |||
ifeq ($(HAVE_X11),true) | |||
DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags x11) -DHAVE_X11 | |||
DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs x11) | |||
@@ -476,6 +481,7 @@ features: | |||
$(call print_available,UNIX) | |||
@echo === Detected features | |||
$(call print_available,HAVE_ALSA) | |||
$(call print_available,HAVE_DBUS) | |||
$(call print_available,HAVE_CAIRO) | |||
$(call print_available,HAVE_DGL) | |||
$(call print_available,HAVE_LIBLO) | |||
@@ -19,9 +19,14 @@ | |||
#include "Geometry.hpp" | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
# include "../distrho/extra/FileBrowserDialog.hpp" | |||
#endif | |||
START_NAMESPACE_DGL | |||
class Application; | |||
class PluginWindow; | |||
class TopLevelWidget; | |||
// ----------------------------------------------------------------------- | |||
@@ -53,53 +58,9 @@ class Window | |||
public: | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
/** | |||
File browser options. | |||
@see Window::openFileBrowser | |||
*/ | |||
struct FileBrowserOptions { | |||
/** | |||
File browser button state. | |||
This allows to customize the behaviour of the file browse dialog buttons. | |||
Note these are merely hints, not all systems support them. | |||
*/ | |||
enum ButtonState { | |||
kButtonInvisible, | |||
kButtonVisibleUnchecked, | |||
kButtonVisibleChecked, | |||
}; | |||
/** Start directory, uses current working directory if null */ | |||
const char* startDir; | |||
/** File browser dialog window title, uses "FileBrowser" if null */ | |||
const char* title; | |||
// TODO file filter | |||
/** | |||
File browser buttons. | |||
*/ | |||
struct Buttons { | |||
/** Whether to list all files vs only those with matching file extension */ | |||
ButtonState listAllFiles; | |||
/** Whether to show hidden files */ | |||
ButtonState showHidden; | |||
/** Whether to show list of places (bookmarks) */ | |||
ButtonState showPlaces; | |||
/** Constructor for default values */ | |||
Buttons() | |||
: listAllFiles(kButtonVisibleChecked), | |||
showHidden(kButtonVisibleUnchecked), | |||
showPlaces(kButtonVisibleChecked) {} | |||
} buttons; | |||
/** Constructor for default values */ | |||
FileBrowserOptions() | |||
: startDir(nullptr), | |||
title(nullptr), | |||
buttons() {} | |||
}; | |||
#endif // DGL_FILE_BROWSER_DISABLED | |||
typedef DISTRHO_NAMESPACE::FileBrowserHandle FileBrowserHandle; | |||
typedef DISTRHO_NAMESPACE::FileBrowserOptions FileBrowserOptions; | |||
#endif | |||
/** | |||
Window graphics context as a scoped struct. | |||
@@ -361,7 +322,7 @@ public: | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
/** | |||
Open a file browser dialog with this window as parent. | |||
Open a file browser dialog with this window as transient parent. | |||
A few options can be specified to setup the dialog. | |||
If a path is selected, onFileSelected() will be called with the user chosen path. | |||
@@ -19,18 +19,6 @@ | |||
#include "pugl.hpp" | |||
#include "../../distrho/extra/String.hpp" | |||
#ifdef DISTRHO_OS_WINDOWS | |||
# include <direct.h> | |||
# include <process.h> | |||
# include <winsock2.h> | |||
# include <windows.h> | |||
# include <vector> | |||
#else | |||
# include <unistd.h> | |||
#endif | |||
// #define DGL_DEBUG_EVENTS | |||
#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) | |||
@@ -64,11 +52,6 @@ START_NAMESPACE_DGL | |||
// ----------------------------------------------------------------------- | |||
#ifdef DISTRHO_OS_WINDOWS | |||
// static pointer used for direct comparisons | |||
static const char* const kWin32SelectedFileCancelled = "__dpf_cancelled__"; | |||
#endif | |||
static double getDesktopScaleFactor(const PuglView* const view) | |||
{ | |||
// allow custom scale for testing | |||
@@ -83,154 +66,6 @@ static double getDesktopScaleFactor(const PuglView* const view) | |||
// ----------------------------------------------------------------------- | |||
#ifdef DISTRHO_OS_WINDOWS | |||
struct FileBrowserThread::PrivateData { | |||
OPENFILENAMEW ofn; | |||
volatile bool threadCancelled; | |||
uintptr_t threadHandle; | |||
std::vector<WCHAR> fileNameW; | |||
std::vector<WCHAR> startDirW; | |||
std::vector<WCHAR> titleW; | |||
const bool isEmbed; | |||
const char*& win32SelectedFile; | |||
PrivateData(const bool embed, const char*& file) | |||
: threadCancelled(false), | |||
threadHandle(0), | |||
fileNameW(32768), | |||
isEmbed(embed), | |||
win32SelectedFile(file) | |||
{ | |||
std::memset(&ofn, 0, sizeof(ofn)); | |||
ofn.lStructSize = sizeof(ofn); | |||
ofn.lpstrFile = fileNameW.data(); | |||
ofn.nMaxFile = (DWORD)fileNameW.size(); | |||
} | |||
void setup(const char* const startDir, | |||
const char* const title, | |||
const uintptr_t winId, | |||
const Window::FileBrowserOptions options) | |||
{ | |||
ofn.hwndOwner = (HWND)winId; | |||
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; | |||
if (options.buttons.showHidden == Window::FileBrowserOptions::kButtonVisibleChecked) | |||
ofn.Flags |= OFN_FORCESHOWHIDDEN; | |||
ofn.FlagsEx = 0x0; | |||
if (options.buttons.showPlaces == Window::FileBrowserOptions::kButtonInvisible) | |||
ofn.FlagsEx |= OFN_EX_NOPLACESBAR; | |||
startDirW.resize(std::strlen(startDir) + 1); | |||
if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size()))) | |||
ofn.lpstrInitialDir = startDirW.data(); | |||
titleW.resize(std::strlen(title) + 1); | |||
if (MultiByteToWideChar(CP_UTF8, 0, title, -1, titleW.data(), static_cast<int>(titleW.size()))) | |||
ofn.lpstrTitle = titleW.data(); | |||
} | |||
void run() | |||
{ | |||
const char* nextFile = nullptr; | |||
if (GetOpenFileNameW(&ofn)) | |||
{ | |||
if (threadCancelled) | |||
{ | |||
threadHandle = 0; | |||
return; | |||
} | |||
// back to UTF-8 | |||
std::vector<char> fileNameA(4 * 32768); | |||
if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1, | |||
fileNameA.data(), (int)fileNameA.size(), | |||
nullptr, nullptr)) | |||
{ | |||
nextFile = strdup(fileNameA.data()); | |||
} | |||
} | |||
if (threadCancelled) | |||
{ | |||
threadHandle = 0; | |||
return; | |||
} | |||
if (nextFile == nullptr) | |||
nextFile = kWin32SelectedFileCancelled; | |||
win32SelectedFile = nextFile; | |||
threadHandle = 0; | |||
} | |||
}; | |||
FileBrowserThread::FileBrowserThread(const bool isEmbed, const char*& file) | |||
: pData(new PrivateData(isEmbed, file)) {} | |||
FileBrowserThread::~FileBrowserThread() | |||
{ | |||
stop(); | |||
delete pData; | |||
} | |||
unsigned __stdcall FileBrowserThread__run(void* const arg) | |||
{ | |||
// CoInitializeEx(nullptr, COINIT_MULTITHREADED); | |||
static_cast<FileBrowserThread*>(arg)->pData->run(); | |||
// CoUninitialize(); | |||
_endthreadex(0); | |||
return 0; | |||
} | |||
void FileBrowserThread::start(const char* const startDir, | |||
const char* const title, | |||
const uintptr_t winId, | |||
const Window::FileBrowserOptions options) | |||
{ | |||
pData->setup(startDir, title, winId, options); | |||
uint threadId; | |||
pData->threadCancelled = false; | |||
pData->threadHandle = _beginthreadex(nullptr, 0, FileBrowserThread__run, this, 0, &threadId); | |||
} | |||
void FileBrowserThread::stop() | |||
{ | |||
pData->threadCancelled = true; | |||
if (pData->threadHandle == 0) | |||
return; | |||
// if previous dialog running, carefully close its window | |||
const HWND owner = pData->isEmbed ? GetParent(pData->ofn.hwndOwner) : pData->ofn.hwndOwner; | |||
if (owner != nullptr && owner != INVALID_HANDLE_VALUE) | |||
{ | |||
const HWND window = GetWindow(owner, GW_HWNDFIRST); | |||
if (window != nullptr && window != INVALID_HANDLE_VALUE) | |||
{ | |||
SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0); | |||
SendMessage(window, WM_CLOSE, 0, 0); | |||
WaitForSingleObject((HANDLE)pData->threadHandle, 5000); | |||
} | |||
} | |||
// not good if thread still running, but let's close the handle anyway | |||
if (pData->threadHandle != 0) | |||
{ | |||
CloseHandle((HANDLE)pData->threadHandle); | |||
pData->threadHandle = 0; | |||
} | |||
} | |||
#endif // DISTRHO_OS_WINDOWS && !_MSC_VER | |||
// ----------------------------------------------------------------------- | |||
Window::PrivateData::PrivateData(Application& a, Window* const s) | |||
: app(a), | |||
appData(a.pData), | |||
@@ -249,9 +84,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s) | |||
keepAspectRatio(false), | |||
ignoreIdleCallbacks(false), | |||
filenameToRenderInto(nullptr), | |||
#ifdef DISTRHO_OS_WINDOWS | |||
win32SelectedFile(nullptr), | |||
win32FileThread(false, win32SelectedFile), | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
fileBrowserHandle(nullptr), | |||
#endif | |||
modal() | |||
{ | |||
@@ -276,9 +110,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c | |||
keepAspectRatio(false), | |||
ignoreIdleCallbacks(false), | |||
filenameToRenderInto(nullptr), | |||
#ifdef DISTRHO_OS_WINDOWS | |||
win32SelectedFile(nullptr), | |||
win32FileThread(false, win32SelectedFile), | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
fileBrowserHandle(nullptr), | |||
#endif | |||
modal(ppData) | |||
{ | |||
@@ -307,9 +140,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
keepAspectRatio(false), | |||
ignoreIdleCallbacks(false), | |||
filenameToRenderInto(nullptr), | |||
#ifdef DISTRHO_OS_WINDOWS | |||
win32SelectedFile(nullptr), | |||
win32FileThread(isEmbed, win32SelectedFile), | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
fileBrowserHandle(nullptr), | |||
#endif | |||
modal() | |||
{ | |||
@@ -340,9 +172,8 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, | |||
keepAspectRatio(false), | |||
ignoreIdleCallbacks(false), | |||
filenameToRenderInto(nullptr), | |||
#ifdef DISTRHO_OS_WINDOWS | |||
win32SelectedFile(nullptr), | |||
win32FileThread(isEmbed, win32SelectedFile), | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
fileBrowserHandle(nullptr), | |||
#endif | |||
modal() | |||
{ | |||
@@ -363,8 +194,9 @@ Window::PrivateData::~PrivateData() | |||
if (isEmbed) | |||
{ | |||
#ifdef HAVE_X11 | |||
sofdFileDialogClose(); | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
if (fileBrowserHandle != nullptr) | |||
fileBrowserClose(fileBrowserHandle); | |||
#endif | |||
puglHide(view); | |||
appData->oneWindowClosed(); | |||
@@ -372,13 +204,6 @@ Window::PrivateData::~PrivateData() | |||
isVisible = false; | |||
} | |||
#ifdef DISTRHO_OS_WINDOWS | |||
win32FileThread.stop(); | |||
if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled) | |||
std::free(const_cast<char*>(win32SelectedFile)); | |||
#endif | |||
puglFreeView(view); | |||
} | |||
@@ -522,9 +347,14 @@ void Window::PrivateData::hide() | |||
if (modal.enabled) | |||
stopModal(); | |||
#ifdef HAVE_X11 | |||
sofdFileDialogClose(); | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
if (fileBrowserHandle != nullptr) | |||
{ | |||
fileBrowserClose(fileBrowserHandle); | |||
fileBrowserHandle = nullptr; | |||
} | |||
#endif | |||
puglHide(view); | |||
isVisible = false; | |||
@@ -566,20 +396,12 @@ void Window::PrivateData::setResizable(const bool resizable) | |||
void Window::PrivateData::idleCallback() | |||
{ | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
# ifdef DISTRHO_OS_WINDOWS | |||
if (const char* path = win32SelectedFile) | |||
if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle)) | |||
{ | |||
win32SelectedFile = nullptr; | |||
if (path == kWin32SelectedFileCancelled) | |||
path = nullptr; | |||
self->onFileSelected(path); | |||
std::free(const_cast<char*>(path)); | |||
self->onFileSelected(fileBrowserGetPath(fileBrowserHandle)); | |||
fileBrowserClose(fileBrowserHandle); | |||
fileBrowserHandle = nullptr; | |||
} | |||
# endif | |||
# ifdef HAVE_X11 | |||
if (sofdFileDialogIdle(view)) | |||
self->onFileSelected(sofdFileDialogGetPath()); | |||
# endif | |||
#endif | |||
} | |||
@@ -619,120 +441,23 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) | |||
// ----------------------------------------------------------------------- | |||
// file handling | |||
bool Window::PrivateData::openFileBrowser(const Window::FileBrowserOptions& options) | |||
bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) | |||
{ | |||
using DISTRHO_NAMESPACE::String; | |||
// -------------------------------------------------------------------------- | |||
// configure start dir | |||
// TODO: get abspath if needed | |||
// TODO: cross-platform | |||
String startDir(options.startDir); | |||
if (startDir.isEmpty()) | |||
{ | |||
// TESTING verify this whole thing... | |||
# ifdef DISTRHO_OS_WINDOWS | |||
if (char* const cwd = _getcwd(nullptr, 0)) | |||
{ | |||
startDir = cwd; | |||
std::free(cwd); | |||
} | |||
# else | |||
if (char* const cwd = getcwd(nullptr, 0)) | |||
{ | |||
startDir = cwd; | |||
std::free(cwd); | |||
} | |||
# endif | |||
} | |||
DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false); | |||
if (! startDir.endsWith(DISTRHO_OS_SEP)) | |||
startDir += DISTRHO_OS_SEP_STR; | |||
// -------------------------------------------------------------------------- | |||
// configure title | |||
String title(options.title); | |||
if (title.isEmpty()) | |||
{ | |||
title = puglGetWindowTitle(view); | |||
if (title.isEmpty()) | |||
title = "FileBrowser"; | |||
} | |||
// -------------------------------------------------------------------------- | |||
// show | |||
# ifdef DISTRHO_OS_MAC | |||
uint flags = 0x0; | |||
if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked) | |||
flags |= 0x001; | |||
else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked) | |||
flags |= 0x002; | |||
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) | |||
flags |= 0x010; | |||
else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked) | |||
flags |= 0x020; | |||
if (fileBrowserHandle != nullptr) | |||
fileBrowserClose(fileBrowserHandle); | |||
if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked) | |||
flags |= 0x100; | |||
else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked) | |||
flags |= 0x200; | |||
FileBrowserOptions options2 = options; | |||
return puglMacOSFilePanelOpen(view, startDir, title, flags, openPanelCallback); | |||
# endif | |||
# ifdef DISTRHO_OS_WINDOWS | |||
// only one possible at a time | |||
DISTRHO_SAFE_ASSERT_RETURN(win32FileThread.pData->threadHandle == 0, false); | |||
if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled) | |||
std::free(const_cast<char*>(win32SelectedFile)); | |||
win32SelectedFile = nullptr; | |||
win32FileThread.start(startDir, title, puglGetNativeWindow(view), options); | |||
return true; | |||
# endif | |||
# ifdef HAVE_X11 | |||
uint flags = 0x0; | |||
if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked) | |||
flags |= 0x001; | |||
else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked) | |||
flags |= 0x002; | |||
if (options2.title == nullptr) | |||
options2.title = puglGetWindowTitle(view); | |||
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) | |||
flags |= 0x010; | |||
else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked) | |||
flags |= 0x020; | |||
fileBrowserHandle = fileBrowserCreate(isEmbed, | |||
puglGetNativeWindow(view), | |||
autoScaling ? autoScaleFactor : scaleFactor, | |||
options2); | |||
if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked) | |||
flags |= 0x100; | |||
else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked) | |||
flags |= 0x200; | |||
return sofdFileDialogShow(view, startDir, title, flags, autoScaling ? autoScaleFactor : scaleFactor); | |||
# endif | |||
return false; | |||
return fileBrowserHandle != nullptr; | |||
} | |||
# ifdef DISTRHO_OS_MAC | |||
void Window::PrivateData::openPanelCallback(PuglView* const view, const char* const path) | |||
{ | |||
((Window::PrivateData*)puglGetHandle(view))->self->onFileSelected(path); | |||
} | |||
# endif | |||
#endif // ! DGL_FILE_BROWSER_DISABLED | |||
// ----------------------------------------------------------------------- | |||
@@ -31,21 +31,6 @@ class TopLevelWidget; | |||
// ----------------------------------------------------------------------- | |||
#ifdef DISTRHO_OS_WINDOWS | |||
struct FileBrowserThread | |||
{ | |||
struct PrivateData; | |||
PrivateData* const pData; | |||
FileBrowserThread(bool isEmbed, const char*& win32SelectedFile); | |||
~FileBrowserThread(); | |||
void start(const char* startDir, const char* title, uintptr_t winId, Window::FileBrowserOptions options); | |||
void stop(); | |||
}; | |||
#endif | |||
// ----------------------------------------------------------------------- | |||
struct Window::PrivateData : IdleCallback { | |||
/** Reference to the DGL Application class this (private data) window associates with. */ | |||
Application& app; | |||
@@ -95,11 +80,9 @@ struct Window::PrivateData : IdleCallback { | |||
/** Render to a picture file when non-null, automatically free+unset after saving. */ | |||
char* filenameToRenderInto; | |||
#ifdef DISTRHO_OS_WINDOWS | |||
/** Selected file for openFileBrowser on windows, stored for fake async operation. */ | |||
const char* win32SelectedFile; | |||
/** Thread where the openFileBrowser runs. */ | |||
FileBrowserThread win32FileThread; | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
/** Handle for file browser dialog operations. */ | |||
FileBrowserHandle fileBrowserHandle; | |||
#endif | |||
/** Modal window setup. */ | |||
@@ -176,10 +159,7 @@ struct Window::PrivateData : IdleCallback { | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
// file handling | |||
bool openFileBrowser(const Window::FileBrowserOptions& options); | |||
# ifdef DISTRHO_OS_MAC | |||
static void openPanelCallback(PuglView* view, const char* path); | |||
# endif | |||
bool openFileBrowser(const FileBrowserOptions& options); | |||
#endif | |||
static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); | |||
@@ -42,6 +42,7 @@ | |||
# endif | |||
#elif defined(DISTRHO_OS_WINDOWS) | |||
# include <wctype.h> | |||
# include <winsock2.h> | |||
# include <windows.h> | |||
# include <windowsx.h> | |||
# ifdef DGL_CAIRO | |||
@@ -57,6 +58,7 @@ | |||
# endif | |||
#else | |||
# include <dlfcn.h> | |||
# include <unistd.h> | |||
# include <sys/select.h> | |||
# include <sys/time.h> | |||
# include <X11/X.h> | |||
@@ -90,10 +92,12 @@ | |||
# endif | |||
#endif | |||
#ifdef HAVE_X11 | |||
# define DBLCLKTME 400 | |||
# include "sofd/libsofd.h" | |||
# include "sofd/libsofd.c" | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
# ifdef DISTRHO_OS_MAC | |||
# import "../../distrho/extra/FileBrowserDialog.cpp" | |||
# else | |||
# include "../../distrho/extra/FileBrowserDialog.cpp" | |||
# endif | |||
#endif | |||
#ifndef DISTRHO_OS_MAC | |||
@@ -528,53 +532,6 @@ void puglMacOSShowCentered(PuglView* const view) | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// macOS specific, setup file browser dialog | |||
bool puglMacOSFilePanelOpen(PuglView* const view, | |||
const char* const startDir, const char* const title, const uint flags, | |||
openPanelCallback callback) | |||
{ | |||
PuglInternals* impl = view->impl; | |||
NSOpenPanel* const panel = [NSOpenPanel openPanel]; | |||
[panel setAllowsMultipleSelection:NO]; | |||
[panel setCanChooseDirectories:NO]; | |||
[panel setCanChooseFiles:YES]; | |||
[panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]]; | |||
// TODO file filter using allowedContentTypes: [UTType] | |||
if (flags & 0x001) | |||
[panel setAllowsOtherFileTypes:YES]; | |||
if (flags & 0x010) | |||
[panel setShowsHiddenFiles:YES]; | |||
NSString* titleString = [[NSString alloc] | |||
initWithBytes:title | |||
length:strlen(title) | |||
encoding:NSUTF8StringEncoding]; | |||
[panel setTitle:titleString]; | |||
dispatch_async(dispatch_get_main_queue(), ^ | |||
{ | |||
[panel beginSheetModalForWindow:(impl->window ? impl->window : [view->impl->wrapperView window]) | |||
completionHandler:^(NSModalResponse result) | |||
{ | |||
if (result == NSModalResponseOK && [[panel URL] isFileURL]) | |||
{ | |||
NSString* const path = [[panel URL] path]; | |||
callback(view, [path UTF8String]); | |||
} | |||
else | |||
{ | |||
callback(view, nullptr); | |||
} | |||
}]; | |||
}); | |||
return true; | |||
} | |||
#endif | |||
#ifdef DISTRHO_OS_WINDOWS | |||
@@ -680,96 +637,7 @@ void puglX11SetWindowTypeAndPID(const PuglView* const view) | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// X11 specific stuff for sofd | |||
static Display* sofd_display; | |||
static char* sofd_filename; | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// X11 specific, show file dialog via sofd | |||
bool sofdFileDialogShow(PuglView* const view, | |||
const char* const startDir, const char* const title, | |||
const uint flags, const double scaleFactor) | |||
{ | |||
// only one possible at a time | |||
DISTRHO_SAFE_ASSERT_RETURN(sofd_display == nullptr, false); | |||
sofd_display = XOpenDisplay(nullptr); | |||
DISTRHO_SAFE_ASSERT_RETURN(sofd_display != nullptr, false); | |||
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false); | |||
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false); | |||
x_fib_cfg_buttons(3, flags & 0x001 ? 1 : flags & 0x002 ? 0 : -1); | |||
x_fib_cfg_buttons(1, flags & 0x010 ? 1 : flags & 0x020 ? 0 : -1); | |||
x_fib_cfg_buttons(2, flags & 0x100 ? 1 : flags & 0x200 ? 0 : -1); | |||
return (x_fib_show(sofd_display, view->impl->win, 0, 0, scaleFactor + 0.5) == 0); | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection) | |||
bool sofdFileDialogIdle(PuglView* const view) | |||
{ | |||
if (sofd_display == nullptr) | |||
return false; | |||
XEvent event; | |||
while (XPending(sofd_display) > 0) | |||
{ | |||
XNextEvent(sofd_display, &event); | |||
if (x_fib_handle_events(sofd_display, &event) == 0) | |||
continue; | |||
if (sofd_filename != nullptr) | |||
std::free(sofd_filename); | |||
if (x_fib_status() > 0) | |||
sofd_filename = x_fib_filename(); | |||
else | |||
sofd_filename = nullptr; | |||
x_fib_close(sofd_display); | |||
XCloseDisplay(sofd_display); | |||
sofd_display = nullptr; | |||
return true; | |||
} | |||
return false; | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// X11 specific, close sofd file dialog | |||
void sofdFileDialogClose() | |||
{ | |||
if (sofd_display != nullptr) | |||
{ | |||
x_fib_close(sofd_display); | |||
XCloseDisplay(sofd_display); | |||
sofd_display = nullptr; | |||
} | |||
if (sofd_filename != nullptr) | |||
{ | |||
std::free(sofd_filename); | |||
sofd_filename = nullptr; | |||
} | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// X11 specific, get path chosen via sofd file dialog | |||
const char* sofdFileDialogGetPath() | |||
{ | |||
return sofd_filename; | |||
} | |||
#endif | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
#endif // HAVE_X11 | |||
#ifndef DISTRHO_OS_MAC | |||
END_NAMESPACE_DGL | |||
@@ -111,10 +111,6 @@ puglMacOSRemoveChildWindow(PuglView* view, PuglView* child); | |||
// macOS specific, center view based on parent coordinates (if there is one) | |||
PUGL_API void | |||
puglMacOSShowCentered(PuglView* view); | |||
// macOS specific, setup file browser dialog | |||
typedef void (*openPanelCallback)(PuglView* view, const char* path); | |||
bool puglMacOSFilePanelOpen(PuglView* view, const char* startDir, const char* title, uint flags, openPanelCallback callback); | |||
#endif | |||
#ifdef DISTRHO_OS_WINDOWS | |||
@@ -139,22 +135,6 @@ puglX11GrabFocus(const PuglView* view); | |||
// X11 specific, set dialog window type and pid hints | |||
PUGL_API void | |||
puglX11SetWindowTypeAndPID(const PuglView* view); | |||
// X11 specific, show file dialog via sofd | |||
PUGL_API bool | |||
sofdFileDialogShow(PuglView* view, const char* startDir, const char* title, uint flags, double scaleFactor); | |||
// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection) | |||
PUGL_API bool | |||
sofdFileDialogIdle(PuglView* const view); | |||
// X11 specific, close sofd file dialog | |||
PUGL_API void | |||
sofdFileDialogClose(); | |||
// X11 specific, get path chosen via sofd file dialog | |||
PUGL_API const char* | |||
sofdFileDialogGetPath(); | |||
#endif | |||
PUGL_END_DECLS | |||
@@ -48,6 +48,10 @@ typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget; | |||
typedef DGL_NAMESPACE::TopLevelWidget UIWidget; | |||
#endif | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
# include "extra/FileBrowserDialog.hpp" | |||
#endif | |||
START_NAMESPACE_DGL | |||
class PluginWindow; | |||
END_NAMESPACE_DGL | |||
@@ -183,6 +187,22 @@ public: | |||
void sendNote(uint8_t channel, uint8_t note, uint8_t velocity); | |||
#endif | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
/** | |||
Open a file browser dialog with this window as transient parent.@n | |||
A few options can be specified to setup the dialog. | |||
If a path is selected, onFileSelected() will be called with the user chosen path. | |||
If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename. | |||
This function does not block the event loop. | |||
@note This is exactly the same API as provided by the Window class, | |||
but redeclared here so that non-embed/DGL based UIs can still use file browser related functions. | |||
*/ | |||
bool openFileBrowser(const FileBrowserOptions& options = FileBrowserOptions()); | |||
#endif | |||
#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS | |||
/* -------------------------------------------------------------------------------------------------------- | |||
* Direct DSP access - DO NOT USE THIS UNLESS STRICTLY NECESSARY!! */ | |||
@@ -297,8 +317,9 @@ protected: | |||
The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code. | |||
*/ | |||
virtual void uiReshape(uint width, uint height); | |||
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
# ifndef DGL_FILE_BROWSER_DISABLED | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
/** | |||
Window file selected function, called when a path is selected by the user, as triggered by openFileBrowser(). | |||
This function is for plugin UIs to be able to override Window::onFileSelected(const char*). | |||
@@ -309,8 +330,7 @@ protected: | |||
If you need to use files as plugin state, please setup and use DISTRHO_PLUGIN_WANT_STATEFILES instead. | |||
*/ | |||
virtual void uiFileBrowserSelected(const char* filename); | |||
# endif | |||
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
#endif | |||
/* -------------------------------------------------------------------------------------------------------- | |||
* UI Resize Handling, internal */ | |||
@@ -22,10 +22,15 @@ | |||
#include "src/DistrhoDefines.h" | |||
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
#import <Cocoa/Cocoa.h> | |||
#include <algorithm> | |||
#include <cmath> | |||
# import <Cocoa/Cocoa.h> | |||
# include <algorithm> | |||
# include <cmath> | |||
# ifndef DGL_FILE_BROWSER_DISABLED | |||
# import "extra/FileBrowserDialog.cpp" | |||
# endif | |||
// Declared in DistrhoUI.cpp but defined here because it uses Obj-C | |||
START_NAMESPACE_DISTRHO | |||
double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||
{ | |||
@@ -40,19 +45,19 @@ double getDesktopScaleFactor(const uintptr_t parentWindowHandle) | |||
return [NSScreen mainScreen].backingScaleFactor; | |||
} | |||
END_NAMESPACE_DISTRHO | |||
#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
#include "../dgl/Base.hpp" | |||
#define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, SEP, PUGL_NS, INTERFACE) DGL_NS ## SEP ## PUGL_NS ## SEP ## INTERFACE | |||
#define DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NS, PUGL_NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, _, PUGL_NS, INTERFACE) | |||
#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
#define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, CairoView) | |||
#define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, OpenGLView) | |||
#define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, StubView) | |||
#define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, VulkanView) | |||
#define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, Window) | |||
#define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WindowDelegate) | |||
#define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WrapperView) | |||
# include "../dgl/Base.hpp" | |||
# define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, SEP, PUGL_NS, INTERFACE) DGL_NS ## SEP ## PUGL_NS ## SEP ## INTERFACE | |||
# define DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NS, PUGL_NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, _, PUGL_NS, INTERFACE) | |||
# define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, CairoView) | |||
# define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, OpenGLView) | |||
# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, StubView) | |||
# define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, VulkanView) | |||
# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, Window) | |||
# define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WindowDelegate) | |||
# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WrapperView) | |||
# import "src/pugl.mm" | |||
#import "src/pugl.mm" | |||
#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI |
@@ -0,0 +1,569 @@ | |||
/* | |||
* DISTRHO Plugin Framework (DPF) | |||
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
* | |||
* Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
* or without fee is hereby granted, provided that the above copyright notice and this | |||
* permission notice appear in all copies. | |||
* | |||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
*/ | |||
#include "FileBrowserDialog.hpp" | |||
#include "ScopedPointer.hpp" | |||
#include "String.hpp" | |||
#ifdef DISTRHO_OS_MAC | |||
# import <Cocoa/Cocoa.h> | |||
#endif | |||
#ifdef DISTRHO_OS_WINDOWS | |||
# include <direct.h> | |||
# include <process.h> | |||
# include <winsock2.h> | |||
# include <windows.h> | |||
# include <vector> | |||
#else | |||
# include <unistd.h> | |||
#endif | |||
#ifdef HAVE_DBUS | |||
# include <dbus/dbus.h> | |||
#endif | |||
#ifdef HAVE_X11 | |||
# define DBLCLKTME 400 | |||
# include "sofd/libsofd.h" | |||
# include "sofd/libsofd.c" | |||
#endif | |||
START_NAMESPACE_DISTRHO | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// static pointer used for signal null/none action taken | |||
static const char* const kSelectedFileCancelled = "__dpf_cancelled__"; | |||
struct FileBrowserData { | |||
const char* selectedFile; | |||
#ifdef DISTRHO_OS_MAC | |||
NSSavePanel* nsBasePanel; | |||
NSOpenPanel* nsOpenPanel; | |||
#endif | |||
#ifdef HAVE_DBUS | |||
DBusConnection* dbuscon; | |||
#endif | |||
#ifdef HAVE_X11 | |||
Display* x11display; | |||
#endif | |||
#ifdef DISTRHO_OS_WINDOWS | |||
OPENFILENAMEW ofn; | |||
volatile bool threadCancelled; | |||
uintptr_t threadHandle; | |||
std::vector<WCHAR> fileNameW; | |||
std::vector<WCHAR> startDirW; | |||
std::vector<WCHAR> titleW; | |||
const bool saving; | |||
bool isEmbed; | |||
FileBrowserData(const bool save) | |||
: selectedFile(nullptr), | |||
threadCancelled(false), | |||
threadHandle(0), | |||
fileNameW(32768), | |||
saving(save), | |||
isEmbed(false) | |||
{ | |||
std::memset(&ofn, 0, sizeof(ofn)); | |||
ofn.lStructSize = sizeof(ofn); | |||
ofn.lpstrFile = fileNameW.data(); | |||
ofn.nMaxFile = (DWORD)fileNameW.size(); | |||
} | |||
~FileBrowserData() | |||
{ | |||
if (cancelAndStop() && selectedFile != nullptr && selectedFile != kSelectedFileCancelled) | |||
std::free(const_cast<char*>(selectedFile)); | |||
} | |||
void setupAndStart(const bool embed, | |||
const char* const startDir, | |||
const char* const windowTitle, | |||
const uintptr_t winId, | |||
const FileBrowserOptions options) | |||
{ | |||
isEmbed = embed; | |||
ofn.hwndOwner = (HWND)winId; | |||
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; | |||
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) | |||
ofn.Flags |= OFN_FORCESHOWHIDDEN; | |||
ofn.FlagsEx = 0x0; | |||
if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible) | |||
ofn.FlagsEx |= OFN_EX_NOPLACESBAR; | |||
startDirW.resize(std::strlen(startDir) + 1); | |||
if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size()))) | |||
ofn.lpstrInitialDir = startDirW.data(); | |||
titleW.resize(std::strlen(windowTitle) + 1); | |||
if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size()))) | |||
ofn.lpstrTitle = titleW.data(); | |||
uint threadId; | |||
threadCancelled = false; | |||
threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId); | |||
} | |||
bool cancelAndStop() | |||
{ | |||
threadCancelled = true; | |||
if (threadHandle == 0) | |||
return true; | |||
// if previous dialog running, carefully close its window | |||
const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner; | |||
if (owner != nullptr && owner != INVALID_HANDLE_VALUE) | |||
{ | |||
const HWND window = GetWindow(owner, GW_HWNDFIRST); | |||
if (window != nullptr && window != INVALID_HANDLE_VALUE) | |||
{ | |||
SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0); | |||
SendMessage(window, WM_CLOSE, 0, 0); | |||
WaitForSingleObject((HANDLE)threadHandle, 5000); | |||
} | |||
} | |||
if (threadHandle == 0) | |||
return true; | |||
// not good if thread still running, but let's close the handle anyway | |||
CloseHandle((HANDLE)threadHandle); | |||
threadHandle = 0; | |||
return false; | |||
} | |||
void run() | |||
{ | |||
const char* nextFile = nullptr; | |||
if (saving ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn)) | |||
{ | |||
if (threadCancelled) | |||
{ | |||
threadHandle = 0; | |||
return; | |||
} | |||
// back to UTF-8 | |||
std::vector<char> fileNameA(4 * 32768); | |||
if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1, | |||
fileNameA.data(), (int)fileNameA.size(), | |||
nullptr, nullptr)) | |||
{ | |||
nextFile = strdup(fileNameA.data()); | |||
} | |||
} | |||
if (threadCancelled) | |||
{ | |||
threadHandle = 0; | |||
return; | |||
} | |||
if (nextFile == nullptr) | |||
nextFile = kSelectedFileCancelled; | |||
selectedFile = nextFile; | |||
threadHandle = 0; | |||
} | |||
static unsigned __stdcall _run(void* const arg) | |||
{ | |||
// CoInitializeEx(nullptr, COINIT_MULTITHREADED); | |||
static_cast<FileBrowserData*>(arg)->run(); | |||
// CoUninitialize(); | |||
_endthreadex(0); | |||
return 0; | |||
} | |||
#else // DISTRHO_OS_WINDOWS | |||
FileBrowserData(const bool saving) | |||
: selectedFile(nullptr) | |||
{ | |||
#ifdef DISTRHO_OS_MAC | |||
if (saving) | |||
{ | |||
nsOpenPanel = nullptr; | |||
nsBasePanel = [[NSSavePanel savePanel]retain]; | |||
} | |||
else | |||
{ | |||
nsOpenPanel = [[NSOpenPanel openPanel]retain]; | |||
nsBasePanel = nsOpenPanel; | |||
} | |||
#endif | |||
#ifdef HAVE_DBUS | |||
if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, nullptr)) != nullptr) | |||
dbus_connection_set_exit_on_disconnect(dbuscon, false); | |||
#endif | |||
#ifdef HAVE_X11 | |||
x11display = XOpenDisplay(nullptr); | |||
#endif | |||
// maybe unused | |||
return; (void)saving; | |||
} | |||
~FileBrowserData() | |||
{ | |||
#ifdef DISTRHO_OS_MAC | |||
[nsBasePanel release]; | |||
#endif | |||
#ifdef HAVE_DBUS | |||
if (dbuscon != nullptr) | |||
dbus_connection_unref(dbuscon); | |||
#endif | |||
#ifdef HAVE_X11 | |||
if (x11display != nullptr) | |||
XCloseDisplay(x11display); | |||
#endif | |||
if (selectedFile != nullptr && selectedFile != kSelectedFileCancelled) | |||
std::free(const_cast<char*>(selectedFile)); | |||
} | |||
#endif | |||
}; | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE | |||
namespace DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE { | |||
#endif | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
FileBrowserHandle fileBrowserCreate(const bool isEmbed, | |||
const uintptr_t windowId, | |||
const double scaleFactor, | |||
const FileBrowserOptions& options) | |||
{ | |||
String startDir(options.startDir); | |||
if (startDir.isEmpty()) | |||
{ | |||
#ifdef DISTRHO_OS_WINDOWS | |||
if (char* const cwd = _getcwd(nullptr, 0)) | |||
{ | |||
startDir = cwd; | |||
std::free(cwd); | |||
} | |||
#else | |||
if (char* const cwd = getcwd(nullptr, 0)) | |||
{ | |||
startDir = cwd; | |||
std::free(cwd); | |||
} | |||
#endif | |||
} | |||
DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), nullptr); | |||
if (! startDir.endsWith(DISTRHO_OS_SEP)) | |||
startDir += DISTRHO_OS_SEP_STR; | |||
String windowTitle(options.title); | |||
if (windowTitle.isEmpty()) | |||
windowTitle = "FileBrowser"; | |||
ScopedPointer<FileBrowserData> handle(new FileBrowserData(options.saving)); | |||
#ifdef DISTRHO_OS_MAC | |||
NSSavePanel* const nsBasePanel = handle->nsBasePanel; | |||
DISTRHO_SAFE_ASSERT_RETURN(nsBasePanel != nullptr, nullptr); | |||
if (! options.saving) | |||
{ | |||
NSOpenPanel* const nsOpenPanel = handle->nsOpenPanel; | |||
DISTRHO_SAFE_ASSERT_RETURN(nsOpenPanel != nullptr, nullptr); | |||
[nsOpenPanel setAllowsMultipleSelection:NO]; | |||
[nsOpenPanel setCanChooseDirectories:NO]; | |||
[nsOpenPanel setCanChooseFiles:YES]; | |||
} | |||
[nsBasePanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]]; | |||
// TODO file filter using allowedContentTypes: [UTType] | |||
if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked) | |||
[nsBasePanel setAllowsOtherFileTypes:YES]; | |||
if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) | |||
[nsBasePanel setShowsHiddenFiles:YES]; | |||
NSString* const titleString = [[NSString alloc] | |||
initWithBytes:windowTitle | |||
length:strlen(windowTitle) | |||
encoding:NSUTF8StringEncoding]; | |||
[nsBasePanel setTitle:titleString]; | |||
FileBrowserData* const handleptr = handle.get(); | |||
dispatch_async(dispatch_get_main_queue(), ^ | |||
{ | |||
[nsBasePanel beginSheetModalForWindow:[(NSView*)windowId window] | |||
completionHandler:^(NSModalResponse result) | |||
{ | |||
if (result == NSModalResponseOK && [[nsBasePanel URL] isFileURL]) | |||
{ | |||
NSString* const path = [[nsBasePanel URL] path]; | |||
handleptr->selectedFile = strdup([path UTF8String]); | |||
} | |||
else | |||
{ | |||
handleptr->selectedFile = kSelectedFileCancelled; | |||
} | |||
}]; | |||
}); | |||
#endif | |||
#ifdef DISTRHO_OS_WINDOWS | |||
handle->setupAndStart(isEmbed, startDir, windowTitle, windowId, options); | |||
#endif | |||
#ifdef HAVE_DBUS | |||
// optional, can be null | |||
DBusConnection* const dbuscon = handle->dbuscon; | |||
if (dbuscon != nullptr && dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr)) | |||
{ | |||
// https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.FileChooser | |||
if (DBusMessage* const message = dbus_message_new_method_call("org.freedesktop.portal.Desktop", | |||
"/org/freedesktop/portal/desktop", | |||
"org.freedesktop.portal.FileChooser", | |||
options.saving ? "SaveFile" : "OpenFile")) | |||
{ | |||
char windowIdStr[32]; | |||
memset(windowIdStr, 0, sizeof(windowIdStr)); | |||
# ifdef HAVE_X11 | |||
snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId); | |||
# endif | |||
const char* windowIdStrPtr = windowIdStr; | |||
dbus_message_append_args(message, | |||
DBUS_TYPE_STRING, &windowIdStrPtr, | |||
DBUS_TYPE_STRING, &windowTitle, | |||
DBUS_TYPE_INVALID); | |||
DBusMessageIter iter, array; | |||
dbus_message_iter_init_append(message, &iter); | |||
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array); | |||
/* here merely as example, in case we need to configure it | |||
{ | |||
DBusMessageIter dict, variant; | |||
const char* const property = "property"; | |||
const char* const value = "value"; | |||
dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict); | |||
dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property); | |||
dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "s", &variant); | |||
dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &value); | |||
dbus_message_iter_close_container(&dict, &variant); | |||
dbus_message_iter_close_container(&array, &dict); | |||
} | |||
*/ | |||
dbus_message_iter_close_container(&iter, &array); | |||
dbus_connection_send(dbuscon, message, nullptr); | |||
dbus_message_unref(message); | |||
return handle.release(); | |||
} | |||
} | |||
#endif | |||
#ifdef HAVE_X11 | |||
Display* const x11display = handle->x11display; | |||
DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr); | |||
// unsupported at the moment | |||
if (options.saving) | |||
return nullptr; | |||
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr); | |||
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr); | |||
const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1 | |||
: options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1; | |||
const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1 | |||
: options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1; | |||
const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1 | |||
: options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1; | |||
x_fib_cfg_buttons(1, button1); | |||
x_fib_cfg_buttons(2, button2); | |||
x_fib_cfg_buttons(3, button3); | |||
if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0) | |||
return nullptr; | |||
#endif | |||
return handle.release(); | |||
// might be unused | |||
(void)isEmbed; | |||
(void)windowId; | |||
(void)scaleFactor; | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// returns true if dialog was closed (with or without a file selection) | |||
bool fileBrowserIdle(const FileBrowserHandle handle) | |||
{ | |||
#ifdef HAVE_DBUS | |||
if (DBusConnection* dbuscon = handle->dbuscon) | |||
{ | |||
while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {} | |||
dbus_connection_read_write_dispatch(dbuscon, 0); | |||
if (DBusMessage* const message = dbus_connection_pop_message(dbuscon)) | |||
{ | |||
const char* const interface = dbus_message_get_interface(message); | |||
const char* const member = dbus_message_get_member(message); | |||
if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0 | |||
&& member != nullptr && std::strcmp(member, "Response") == 0) | |||
{ | |||
do { | |||
DBusMessageIter iter; | |||
dbus_message_iter_init(message, &iter); | |||
// starts with uint32 for return/exit code | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32); | |||
uint32_t ret = 1; | |||
dbus_message_iter_get_basic(&iter, &ret); | |||
if (ret != 0) | |||
break; | |||
// next must be array | |||
dbus_message_iter_next(&iter); | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY); | |||
// open dict array | |||
DBusMessageIter dictArray; | |||
dbus_message_iter_recurse(&iter, &dictArray); | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY); | |||
// open containing dict | |||
DBusMessageIter dict; | |||
dbus_message_iter_recurse(&dictArray, &dict); | |||
// start with the string "uris" | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING); | |||
const char* key = nullptr; | |||
dbus_message_iter_get_basic(&dict, &key); | |||
DISTRHO_SAFE_ASSERT_BREAK(key != nullptr && std::strcmp(key, "uris") == 0); | |||
// then comes variant | |||
dbus_message_iter_next(&dict); | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT); | |||
DBusMessageIter variant; | |||
dbus_message_iter_recurse(&dict, &variant); | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY); | |||
// open variant array (variant type is string) | |||
DBusMessageIter variantArray; | |||
dbus_message_iter_recurse(&variant, &variantArray); | |||
DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING); | |||
const char* value = nullptr; | |||
dbus_message_iter_get_basic(&variantArray, &value); | |||
// and finally we have our dear value, just make sure it is local | |||
DISTRHO_SAFE_ASSERT_BREAK(value != nullptr); | |||
if (const char* const localvalue = std::strstr(value, "file:///")) | |||
handle->selectedFile = strdup(localvalue + 7); | |||
} while(false); | |||
if (handle->selectedFile == nullptr) | |||
handle->selectedFile = kSelectedFileCancelled; | |||
} | |||
} | |||
} | |||
#endif | |||
#ifdef HAVE_X11 | |||
Display* const x11display = handle->x11display; | |||
if (x11display == nullptr) | |||
return false; | |||
XEvent event; | |||
while (XPending(x11display) > 0) | |||
{ | |||
XNextEvent(x11display, &event); | |||
if (x_fib_handle_events(x11display, &event) == 0) | |||
continue; | |||
if (x_fib_status() > 0) | |||
handle->selectedFile = x_fib_filename(); | |||
else | |||
handle->selectedFile = kSelectedFileCancelled; | |||
x_fib_close(x11display); | |||
XCloseDisplay(x11display); | |||
handle->x11display = nullptr; | |||
break; | |||
} | |||
#endif | |||
return handle->selectedFile != nullptr; | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// close sofd file dialog | |||
void fileBrowserClose(const FileBrowserHandle handle) | |||
{ | |||
#ifdef HAVE_X11 | |||
if (Display* const x11display = handle->x11display) | |||
x_fib_close(x11display); | |||
#endif | |||
delete handle; | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// get path chosen via sofd file dialog | |||
const char* fileBrowserGetPath(const FileBrowserHandle handle) | |||
{ | |||
return handle->selectedFile != kSelectedFileCancelled ? handle->selectedFile : nullptr; | |||
} | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE | |||
} | |||
#endif | |||
END_NAMESPACE_DISTRHO |
@@ -0,0 +1,134 @@ | |||
/* | |||
* DISTRHO Plugin Framework (DPF) | |||
* Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> | |||
* | |||
* Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
* or without fee is hereby granted, provided that the above copyright notice and this | |||
* permission notice appear in all copies. | |||
* | |||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
* TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
*/ | |||
#ifndef DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED | |||
#define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED | |||
#include "../DistrhoUtils.hpp" | |||
START_NAMESPACE_DISTRHO | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
// File Browser Dialog stuff | |||
struct FileBrowserData; | |||
typedef FileBrowserData* FileBrowserHandle; | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
/** | |||
File browser options, for customizing the file browser dialog.@n | |||
By default the file browser dialog will be work as "open file" in the current working directory. | |||
*/ | |||
struct FileBrowserOptions { | |||
/** Whether we are saving, opening files otherwise (default) */ | |||
bool saving; | |||
/** Start directory, uses current working directory if null */ | |||
const char* startDir; | |||
/** File browser dialog window title, uses "FileBrowser" if null */ | |||
const char* title; | |||
// TODO file filter | |||
/** | |||
File browser button state. | |||
This allows to customize the behaviour of the file browse dialog buttons. | |||
Note these are merely hints, not all systems support them. | |||
*/ | |||
enum ButtonState { | |||
kButtonInvisible, | |||
kButtonVisibleUnchecked, | |||
kButtonVisibleChecked, | |||
}; | |||
/** | |||
File browser buttons. | |||
*/ | |||
struct Buttons { | |||
/** Whether to list all files vs only those with matching file extension */ | |||
ButtonState listAllFiles; | |||
/** Whether to show hidden files */ | |||
ButtonState showHidden; | |||
/** Whether to show list of places (bookmarks) */ | |||
ButtonState showPlaces; | |||
/** Constructor for default values */ | |||
Buttons() | |||
: listAllFiles(kButtonVisibleChecked), | |||
showHidden(kButtonVisibleUnchecked), | |||
showPlaces(kButtonVisibleChecked) {} | |||
} buttons; | |||
/** Constructor for default values */ | |||
FileBrowserOptions() | |||
: saving(false), | |||
startDir(nullptr), | |||
title(nullptr), | |||
buttons() {} | |||
}; | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE | |||
namespace DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE { | |||
#endif | |||
/** | |||
Create a new file browser dialog. | |||
@p isEmbed: Whether the window this dialog belongs to is an embed/child window (needed to close dialog on Windows) | |||
@p windowId: The native window id to attach this dialog to as transient parent (X11 Window, HWND or NSView*) | |||
@p scaleFactor: Scale factor to use (only used on X11) | |||
@p options: Extra options, optional | |||
By default the file browser dialog will be work as "open file" in the current working directory. | |||
*/ | |||
FileBrowserHandle fileBrowserCreate(bool isEmbed, | |||
uintptr_t windowId, | |||
double scaleFactor, | |||
const FileBrowserOptions& options = FileBrowserOptions()); | |||
/** | |||
Idle the file browser dialog handle.@n | |||
Returns true if dialog was closed (with or without a file selection), | |||
in which case the handle must not be used afterwards. | |||
You can then call fileBrowserGetPath to know the selected file (or null if cancelled). | |||
*/ | |||
bool fileBrowserIdle(const FileBrowserHandle handle); | |||
/** | |||
Close the file browser dialog, handle must not be used afterwards. | |||
*/ | |||
void fileBrowserClose(const FileBrowserHandle handle); | |||
/** | |||
Get the path chosen by the user or null.@n | |||
Should only be called after fileBrowserIdle returns true. | |||
*/ | |||
const char* fileBrowserGetPath(const FileBrowserHandle handle); | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
#ifdef DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE | |||
} | |||
#endif | |||
// -------------------------------------------------------------------------------------------------------------------- | |||
END_NAMESPACE_DISTRHO | |||
#endif // DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED |
@@ -207,6 +207,7 @@ typedef unsigned char uchar; | |||
typedef unsigned short int ushort; | |||
typedef unsigned int uint; | |||
typedef unsigned long int ulong; | |||
typedef unsigned long long int ulonglong; | |||
/* Deprecated macros */ | |||
#define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName) | |||
@@ -15,6 +15,29 @@ | |||
*/ | |||
#include "src/DistrhoPluginChecks.h" | |||
#include "src/DistrhoDefines.h" | |||
#if !defined(DGL_FILE_BROWSER_DISABLED) && !defined(DISTRHO_OS_MAC) | |||
# define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION | |||
# define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION) | |||
# define DISTRHO_FILE_BROWSER_DIALOG_EXTRA_NAMESPACE Plugin | |||
# define x_fib_add_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_add_recent) | |||
# define x_fib_cfg_buttons DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_cfg_buttons) | |||
# define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_cfg_filter_callback) | |||
# define x_fib_close DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_close) | |||
# define x_fib_configure DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_configure) | |||
# define x_fib_filename DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_filename) | |||
# define x_fib_free_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_free_recent) | |||
# define x_fib_handle_events DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_handle_events) | |||
# define x_fib_load_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_load_recent) | |||
# define x_fib_recent_at DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_at) | |||
# define x_fib_recent_count DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_count) | |||
# define x_fib_recent_file DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_recent_file) | |||
# define x_fib_save_recent DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_save_recent) | |||
# define x_fib_show DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_show) | |||
# define x_fib_status DISTRHO_PUGL_NAMESPACE_MACRO(Plugin, x_fib_status) | |||
# include "../extra/FileBrowserDialog.cpp" | |||
#endif | |||
#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
# if defined(DISTRHO_OS_WINDOWS) | |||
@@ -255,6 +278,19 @@ void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity) | |||
} | |||
#endif | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
bool UI::openFileBrowser(const FileBrowserOptions& options) | |||
{ | |||
# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
// TODO | |||
return false; | |||
(void)options; | |||
# else | |||
return getWindow().openFileBrowser(options); | |||
# endif | |||
} | |||
#endif | |||
#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS | |||
/* ------------------------------------------------------------------------------------------------------------ | |||
* Direct DSP access */ | |||
@@ -311,13 +347,13 @@ void UI::uiReshape(uint, uint) | |||
// NOTE this must be the same as Window::onReshape | |||
pData->fallbackOnResize(); | |||
} | |||
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
# ifndef DGL_FILE_BROWSER_DISABLED | |||
#ifndef DGL_FILE_BROWSER_DISABLED | |||
void UI::uiFileBrowserSelected(const char*) | |||
{ | |||
} | |||
# endif | |||
#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI | |||
#endif | |||
/* ------------------------------------------------------------------------------------------------------------ | |||
* UI Resize Handling, internal */ | |||
@@ -433,7 +433,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) | |||
snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key); | |||
title[sizeof(title)-1u] = '\0'; | |||
DGL_NAMESPACE::Window::FileBrowserOptions opts; | |||
FileBrowserOptions opts; | |||
opts.title = title; | |||
return window->openFileBrowser(opts); | |||
#endif | |||
@@ -125,6 +125,7 @@ protected: | |||
repaint(); | |||
FileBrowserOptions opts; | |||
// opts.saving = true; | |||
opts.title = "Look at me"; | |||
if (! openFileBrowser(opts)) | |||
{ | |||