@@ -25,10 +25,48 @@ START_NAMESPACE_DGL | |||
class App; | |||
class Widget; | |||
class StandaloneWindow; | |||
class Window | |||
{ | |||
public: | |||
/** | |||
File browser options. | |||
*/ | |||
struct FileBrowserOptions { | |||
const char* startDir; | |||
const char* title; | |||
uint width; | |||
uint height; | |||
/** | |||
File browser buttons. | |||
0 means hidden. | |||
1 means visible and unchecked. | |||
2 means visible and checked. | |||
*/ | |||
struct Buttons { | |||
uint listAllFiles; | |||
uint showHidden; | |||
uint showPlaces; | |||
/** Constuctor for default values */ | |||
Buttons() | |||
: listAllFiles(2), | |||
showHidden(1), | |||
showPlaces(1) {} | |||
} buttons; | |||
/** Constuctor for default values */ | |||
FileBrowserOptions() | |||
: startDir(nullptr), | |||
title(nullptr), | |||
width(0), | |||
height(0), | |||
buttons() {} | |||
}; | |||
explicit Window(App& app); | |||
explicit Window(App& app, Window& parent); | |||
explicit Window(App& app, intptr_t parentId); | |||
@@ -42,6 +80,8 @@ public: | |||
void focus(); | |||
void repaint() noexcept; | |||
bool openFileBrowser(const FileBrowserOptions& options); | |||
bool isVisible() const noexcept; | |||
void setVisible(bool yesNo); | |||
@@ -54,6 +94,7 @@ public: | |||
void setSize(uint width, uint height); | |||
void setSize(Size<uint> size); | |||
const char* getTitle() const noexcept; | |||
void setTitle(const char* title); | |||
void setTransientWinId(uintptr_t winId); | |||
@@ -70,6 +111,8 @@ protected: | |||
virtual void onReshape(uint width, uint height); | |||
virtual void onClose(); | |||
virtual void fileBrowserSelected(const char* filename); | |||
private: | |||
struct PrivateData; | |||
PrivateData* const pData; | |||
@@ -20,6 +20,7 @@ | |||
#include "AppPrivateData.hpp" | |||
#include "../Widget.hpp" | |||
#include "../Window.hpp" | |||
#include "../../distrho/extra/d_string.hpp" | |||
#include "pugl/pugl.h" | |||
@@ -69,6 +70,7 @@ struct Window::PrivateData { | |||
fUsingEmbed(false), | |||
fWidth(1), | |||
fHeight(1), | |||
fTitle(nullptr), | |||
fWidgets(), | |||
fModal(), | |||
#if defined(DISTRHO_OS_WINDOWS) | |||
@@ -97,6 +99,7 @@ struct Window::PrivateData { | |||
fUsingEmbed(false), | |||
fWidth(1), | |||
fHeight(1), | |||
fTitle(nullptr), | |||
fWidgets(), | |||
fModal(parent.pData), | |||
#if defined(DISTRHO_OS_WINDOWS) | |||
@@ -135,6 +138,7 @@ struct Window::PrivateData { | |||
fUsingEmbed(parentId != 0), | |||
fWidth(1), | |||
fHeight(1), | |||
fTitle(nullptr), | |||
fWidgets(), | |||
fModal(), | |||
#if defined(DISTRHO_OS_WINDOWS) | |||
@@ -143,7 +147,7 @@ struct Window::PrivateData { | |||
xDisplay(nullptr), | |||
xWindow(0), | |||
#elif defined(DISTRHO_OS_MAC) | |||
fNeedsIdle(false), | |||
fNeedsIdle(parentId == 0), | |||
mView(nullptr), | |||
mWindow(nullptr), | |||
#endif | |||
@@ -190,6 +194,7 @@ struct Window::PrivateData { | |||
puglSetSpecialFunc(fView, onSpecialCallback); | |||
puglSetReshapeFunc(fView, onReshapeCallback); | |||
puglSetCloseFunc(fView, onCloseCallback); | |||
puglSetFileSelectedFunc(fView, fileBrowserSelectedCallback); | |||
puglCreateWindow(fView, nullptr); | |||
@@ -535,10 +540,22 @@ struct Window::PrivateData { | |||
// ------------------------------------------------------------------- | |||
const char* getTitle() const noexcept | |||
{ | |||
static const char* const kFallback = ""; | |||
return fTitle != nullptr ? fTitle : kFallback; | |||
} | |||
void setTitle(const char* const title) | |||
{ | |||
DBGp("Window setTitle \"%s\"\n", title); | |||
if (fTitle != nullptr) | |||
std::free(fTitle); | |||
fTitle = strdup(title); | |||
#if defined(DISTRHO_OS_WINDOWS) | |||
SetWindowTextA(hwnd, title); | |||
#elif defined(DISTRHO_OS_MAC) | |||
@@ -840,6 +857,7 @@ struct Window::PrivateData { | |||
bool fUsingEmbed; | |||
uint fWidth; | |||
uint fHeight; | |||
char* fTitle; | |||
std::list<Widget*> fWidgets; | |||
struct Modal { | |||
@@ -922,6 +940,11 @@ struct Window::PrivateData { | |||
handlePtr->onPuglClose(); | |||
} | |||
static void fileBrowserSelectedCallback(PuglView* view, const char* filename) | |||
{ | |||
handlePtr->fSelf->fileBrowserSelected(filename); | |||
} | |||
#undef handlePtr | |||
DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) | |||
@@ -977,6 +1000,78 @@ void Window::repaint() noexcept | |||
puglPostRedisplay(pData->fView); | |||
} | |||
// static int fib_filter_filename_filter(const char* const name) | |||
// { | |||
// return 1; | |||
// (void)name; | |||
// } | |||
bool Window::openFileBrowser(const FileBrowserOptions& options) | |||
{ | |||
#ifdef SOFD_HAVE_X11 | |||
using DISTRHO_NAMESPACE::d_string; | |||
// -------------------------------------------------------------------------- | |||
// configure start dir | |||
// TODO: get abspath if needed | |||
// TODO: cross-platform | |||
d_string startDir(options.startDir); | |||
if (startDir.isEmpty()) | |||
{ | |||
if (char* const dir_name = get_current_dir_name()) | |||
{ | |||
startDir = dir_name; | |||
std::free(dir_name); | |||
} | |||
} | |||
DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false); | |||
if (! startDir.endsWith('/')) | |||
startDir += "/"; | |||
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false); | |||
// -------------------------------------------------------------------------- | |||
// configure title | |||
d_string title(options.title); | |||
if (title.isEmpty()) | |||
{ | |||
title = pData->getTitle(); | |||
if (title.isEmpty()) | |||
title = "FileBrowser"; | |||
} | |||
DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false); | |||
// -------------------------------------------------------------------------- | |||
// configure filters | |||
x_fib_cfg_filter_callback(nullptr); //fib_filter_filename_filter); | |||
// -------------------------------------------------------------------------- | |||
// configure buttons | |||
x_fib_cfg_buttons(3, options.buttons.listAllFiles-1); | |||
x_fib_cfg_buttons(1, options.buttons.showHidden-1); | |||
x_fib_cfg_buttons(2, options.buttons.showPlaces-1); | |||
// -------------------------------------------------------------------------- | |||
// show | |||
return (x_fib_show(pData->xDisplay, pData->xWindow, /*options.width*/0, /*options.height*/0) == 0); | |||
#else | |||
// not implemented | |||
return false; | |||
#endif | |||
} | |||
bool Window::isVisible() const noexcept | |||
{ | |||
return pData->fVisible; | |||
@@ -1022,6 +1117,11 @@ void Window::setSize(Size<uint> size) | |||
pData->setSize(size.getWidth(), size.getHeight()); | |||
} | |||
const char* Window::getTitle() const noexcept | |||
{ | |||
return pData->getTitle(); | |||
} | |||
void Window::setTitle(const char* title) | |||
{ | |||
pData->setTitle(title); | |||
@@ -1101,6 +1201,10 @@ void Window::onClose() | |||
{ | |||
} | |||
void Window::fileBrowserSelected(const char*) | |||
{ | |||
} | |||
// ----------------------------------------------------------------------- | |||
END_NAMESPACE_DGL | |||
@@ -219,6 +219,14 @@ typedef void (*PuglScrollFunc)(PuglView* view, int x, int y, float dx, float dy) | |||
*/ | |||
typedef void (*PuglSpecialFunc)(PuglView* view, bool press, PuglKey key); | |||
/** | |||
A function called when a filename is selected via file-browser. | |||
@param view The view the event occured in. | |||
@param filename The selected file name or NULL if the dialog was canceled. | |||
*/ | |||
typedef void (*PuglFileSelectedFunc)(PuglView* view, const char* filename); | |||
/** | |||
Create a Pugl context. | |||
@@ -352,6 +360,12 @@ puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc); | |||
PUGL_API void | |||
puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc); | |||
/** | |||
Set the function to call on file-browser selections. | |||
*/ | |||
PUGL_API void | |||
puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc); | |||
/** | |||
Return the native window handle. | |||
*/ | |||
@@ -51,6 +51,7 @@ struct PuglViewImpl { | |||
PuglReshapeFunc reshapeFunc; | |||
PuglScrollFunc scrollFunc; | |||
PuglSpecialFunc specialFunc; | |||
PuglFileSelectedFunc fileSelectedFunc; | |||
PuglInternals* impl; | |||
PuglNativeWindow parent; | |||
@@ -200,3 +201,9 @@ puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc) | |||
{ | |||
view->specialFunc = specialFunc; | |||
} | |||
void | |||
puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc) | |||
{ | |||
view->fileSelectedFunc = fileSelectedFunc; | |||
} |
@@ -35,6 +35,7 @@ | |||
backing:(NSBackingStoreType)bufferingType | |||
defer:(BOOL)flag; | |||
- (void) setPuglview:(PuglView*)view; | |||
- (BOOL) canBecomeKeyWindow; | |||
- (BOOL) windowShouldClose:(id)sender; | |||
@end | |||
@@ -66,6 +67,11 @@ | |||
[self setContentSize:NSMakeSize(view->width, view->height)]; | |||
} | |||
- (BOOL)canBecomeKeyWindow | |||
{ | |||
return YES; | |||
} | |||
- (BOOL)windowShouldClose:(id)sender | |||
{ | |||
if (puglview->closeFunc) | |||
@@ -81,6 +87,7 @@ | |||
static void | |||
puglDisplay(PuglView* view) | |||
{ | |||
view->redisplay = false; | |||
if (view->displayFunc) { | |||
view->displayFunc(view); | |||
} | |||
@@ -91,15 +98,17 @@ puglDisplay(PuglView* view) | |||
@public | |||
PuglView* puglview; | |||
NSTrackingArea* trackingArea; | |||
bool doubleBuffered; | |||
} | |||
- (BOOL) acceptsFirstMouse:(NSEvent*)e; | |||
- (BOOL) acceptsFirstResponder; | |||
- (BOOL) isFlipped; | |||
- (BOOL) isOpaque; | |||
- (BOOL) preservesContentInLiveResize; | |||
- (id) initWithFrame:(NSRect)frame; | |||
- (void) reshape; | |||
- (void) drawRect:(NSRect)rect; | |||
- (void) drawRect:(NSRect)r; | |||
- (void) cursorUpdate:(NSEvent*)e; | |||
- (void) updateTrackingAreas; | |||
- (void) viewWillMoveToWindow:(NSWindow*)newWindow; | |||
@@ -130,6 +139,11 @@ puglDisplay(PuglView* view) | |||
(void)e; | |||
} | |||
- (BOOL) acceptsFirstResponder | |||
{ | |||
return YES; | |||
} | |||
- (BOOL) isFlipped | |||
{ | |||
return YES; | |||
@@ -147,16 +161,16 @@ puglDisplay(PuglView* view) | |||
- (id) initWithFrame:(NSRect)frame | |||
{ | |||
puglview = nil; | |||
trackingArea = nil; | |||
puglview = nil; | |||
trackingArea = nil; | |||
doubleBuffered = true; | |||
NSOpenGLPixelFormatAttribute pixelAttribs[16] = { | |||
NSOpenGLPixelFormatAttribute pixelAttribs[] = { | |||
NSOpenGLPFAColorSize, 24, | |||
NSOpenGLPFAAlphaSize, 8, | |||
NSOpenGLPFADepthSize, 16, | |||
NSOpenGLPFADoubleBuffer, | |||
NSOpenGLPFAAccelerated, | |||
NSOpenGLPFAColorSize, | |||
8, | |||
NSOpenGLPFADepthSize, | |||
8, | |||
0 | |||
}; | |||
@@ -166,12 +180,20 @@ puglDisplay(PuglView* view) | |||
if (pixelFormat) { | |||
self = [super initWithFrame:frame pixelFormat:pixelFormat]; | |||
[pixelFormat release]; | |||
printf("Is doubleBuffered? TRUE\n"); | |||
} else { | |||
self = [super initWithFrame:frame]; | |||
doubleBuffered = false; | |||
printf("Is doubleBuffered? FALSE\n"); | |||
} | |||
if (self) { | |||
[[self openGLContext] makeCurrentContext]; | |||
if (self) { | |||
NSOpenGLContext* context = [self openGLContext]; | |||
[context makeCurrentContext]; | |||
GLint swapInterval = 1; | |||
[context setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; | |||
[self reshape]; | |||
} | |||
@@ -204,13 +226,19 @@ puglDisplay(PuglView* view) | |||
puglview->height = height; | |||
} | |||
- (void) drawRect:(NSRect)rect | |||
- (void) drawRect:(NSRect)r | |||
{ | |||
puglDisplay(puglview); | |||
glFlush(); | |||
glSwapAPPLE(); | |||
[super drawRect:rect]; | |||
if (doubleBuffered) { | |||
[[self openGLContext] flushBuffer]; | |||
} else { | |||
glFlush(); | |||
//glSwapAPPLE(); | |||
} | |||
// unused | |||
return; (void)r; | |||
} | |||
- (void) cursorUpdate:(NSEvent*)e | |||
@@ -421,6 +449,7 @@ puglCreateWindow(PuglView* view, const char* title) | |||
} | |||
if (view->parent) { | |||
[impl->glview retain]; | |||
NSView* pview = (NSView*)view->parent; | |||
[pview addSubview:impl->glview]; | |||
return 0; | |||
@@ -497,9 +526,10 @@ puglDestroy(PuglView* view) | |||
PuglStatus | |||
puglProcessEvents(PuglView* view) | |||
{ | |||
[view->impl->glview setNeedsDisplay:YES]; | |||
view->redisplay = false; | |||
return PUGL_SUCCESS; | |||
// unused | |||
(void)view; | |||
} | |||
void | |||
@@ -32,6 +32,10 @@ | |||
#include "pugl_internal.h" | |||
#define SOFD_HAVE_X11 | |||
#include "../sofd/libsofd.h" | |||
#include "../sofd/libsofd.c" | |||
struct PuglInternalsImpl { | |||
Display* display; | |||
int screen; | |||
@@ -96,7 +100,7 @@ puglCreateWindow(PuglView* view, const char* title) | |||
{ | |||
PuglInternals* impl = view->impl; | |||
impl->display = XOpenDisplay(0); | |||
impl->display = XOpenDisplay(NULL); | |||
impl->screen = DefaultScreen(impl->display); | |||
impl->doubleBuffered = True; | |||
@@ -322,6 +326,26 @@ puglProcessEvents(PuglView* view) | |||
XEvent event; | |||
while (XPending(view->impl->display) > 0) { | |||
XNextEvent(view->impl->display, &event); | |||
if (x_fib_handle_events(view->impl->display, &event)) { | |||
const int status = x_fib_status(); | |||
if (status > 0) { | |||
char* const filename = x_fib_filename(); | |||
x_fib_close(view->impl->display); | |||
if (view->fileSelectedFunc) { | |||
view->fileSelectedFunc(view, filename); | |||
} | |||
free(filename); | |||
} else if (status < 0) { | |||
x_fib_close(view->impl->display); | |||
if (view->fileSelectedFunc) { | |||
view->fileSelectedFunc(view, NULL); | |||
} | |||
} | |||
break; | |||
} | |||
switch (event.type) { | |||
case MapNotify: | |||
puglReshape(view, view->width, view->height); | |||
@@ -0,0 +1,175 @@ | |||
/* libSOFD - Simple Open File Dialog [for X11 without toolkit] | |||
* | |||
* Copyright (C) 2014 Robin Gareus <robin@gareus.org> | |||
* | |||
* Permission is hereby granted, free of charge, to any person obtaining a copy | |||
* of this software and associated documentation files (the "Software"), to deal | |||
* in the Software without restriction, including without limitation the rights | |||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
* copies of the Software, and to permit persons to whom the Software is | |||
* furnished to do so, subject to the following conditions: | |||
* | |||
* The above copyright notice and this permission notice shall be included in | |||
* all copies or substantial portions of the Software. | |||
* | |||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
* THE SOFTWARE. | |||
*/ | |||
#ifdef SOFD_HAVE_X11 | |||
#include <X11/Xlib.h> | |||
/////////////////////////////////////////////////////////////////////////////// | |||
/* public API */ | |||
typedef struct FibInternalsImpl FibInternals; | |||
/** open a file select dialog | |||
* @param dpy X Display connection | |||
* @param parent (optional) if not NULL, become transient for given window | |||
* @param x if >0 set explict initial width of the window | |||
* @param y if >0 set explict initial height of the window | |||
* @return 0 on success | |||
*/ | |||
int x_fib_show (Display *dpy, Window parent, int x, int y); | |||
/** force close the dialog. | |||
* This is normally not needed, the dialog closes itself | |||
* when a file is selected or the user cancels selection. | |||
* @param dpy X Display connection | |||
*/ | |||
void x_fib_close (Display *dpy); | |||
/** non-blocking X11 event handler. | |||
* It is safe to run this function even if the dialog is | |||
* closed or was not initialized. | |||
* | |||
* @param dpy X Display connection | |||
* @param event the XEvent to process | |||
* @return status | |||
* 0: the event was not for this window, or file-dialog still | |||
* active, or the dialog window is not displayed. | |||
* >0: file was selected, dialog closed | |||
* <0: file selection was cancelled. | |||
*/ | |||
int x_fib_handle_events (Display *dpy, XEvent *event); | |||
/** last status of the dialog | |||
* @return >0: file was selected, <0: canceled or inactive. 0: active | |||
*/ | |||
int x_fib_status (); | |||
/** query the selected filename | |||
* @return NULL if none set, or allocated string to be free()ed by the called | |||
*/ | |||
char *x_fib_filename (); | |||
/** customize/configure the dialog before calling \ref x_fib_show | |||
* changes only have any effect if the dialog is not visible. | |||
* @param k key to change | |||
* 0: set current dir to display (must end with slash) | |||
* 1: set title of dialog window | |||
* 2: specify a custom X11 font to use | |||
* 3: specify a custom 'places' file to include | |||
* (following gtk-bookmark convention) | |||
* @param v value | |||
* @return 0 on success. | |||
*/ | |||
int x_fib_configure (int k, const char *v); | |||
/** customize/configure the dialog before calling \ref x_fib_show | |||
* changes only have any effect if the dialog is not visible. | |||
* | |||
* @param k button to change: | |||
* 1: show hidden files | |||
* 2: show places | |||
* 3: show filter/list all (automatically hidden if there is no | |||
* filter function) | |||
* @param v <0 to hide the button >=0 show button, | |||
* 0: set button-state to not-checked | |||
* 1: set button-state to checked | |||
* >1: retain current state | |||
* @return 0 on success. | |||
*/ | |||
int x_fib_cfg_buttons (int k, int v); | |||
/** set custom callback to filter file-names. | |||
* NULL will disable the filter and hide the 'show all' button. | |||
* changes only have any effect if the dialog is not visible. | |||
* | |||
* @param cb callback function to check file | |||
* the callback function is called with the file name (basename only) | |||
* and is expected to return 1 if the file passes the filter | |||
* and 0 if the file should not be listed by default. | |||
* @return 0 on success. | |||
*/ | |||
int x_fib_cfg_filter_callback (int (*cb)(const char*)); | |||
#endif /* END X11 specific functions */ | |||
/* 'recently used' API. x-platform | |||
* NOTE: all functions use a static cache and are not reentrant. | |||
* It is expected that none of these functions are called in | |||
* parallel from different threads. | |||
*/ | |||
/** release static resources of 'recently used files' | |||
*/ | |||
void x_fib_free_recent (); | |||
/** add an entry to the recently used list | |||
* | |||
* The dialog does not add files automatically on open, | |||
* if the application succeeds to open a selected file, | |||
* this function should be called. | |||
* | |||
* @param path complete path to file | |||
* @param atime time of last use, 0: NOW | |||
* @return -1 on error, number of current entries otherwise | |||
*/ | |||
int x_fib_add_recent (const char *path, time_t atime); | |||
/** get a platform specific path to a good location for | |||
* saving the recently used file list. | |||
* (follows XDG_DATA_HOME on Unix, and CSIDL_LOCAL_APPDATA spec) | |||
* | |||
* @param application-name to use to include in file | |||
* @return pointer to static path or NULL | |||
*/ | |||
const char *x_fib_recent_file(const char *appname); | |||
/** save the current list of recently used files to the given filename | |||
* (the format is one file per line, filename URL encoded and space separated | |||
* with last-used timestamp) | |||
* | |||
* This function tries to creates the containing directory if it does | |||
* not exist. | |||
* | |||
* @param fn file to save the list to | |||
* @return 0: on success | |||
*/ | |||
int x_fib_save_recent (const char *fn); | |||
/** load a recently used file list. | |||
* | |||
* @param fn file to load the list from | |||
* @return 0: on success | |||
*/ | |||
int x_fib_load_recent (const char *fn); | |||
/** get number of entries in the current list | |||
* @return number of entries in the recently used list | |||
*/ | |||
unsigned int x_fib_recent_count (); | |||
/** get recently used entry at given position | |||
* | |||
* @param i entry to query | |||
* @return pointer to static string | |||
*/ | |||
const char *x_fib_recent_at (unsigned int i); |