| @@ -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); | |||