Mercurial > hg > pub > prymula > com
diff DPF-Prymula-audioplugins/dpf/distrho/extra/sofd/libsofd.c @ 3:84e66ea83026
DPF-Prymula-audioplugins-0.231015-2
author | prymula <prymula76@outlook.com> |
---|---|
date | Mon, 16 Oct 2023 21:53:34 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DPF-Prymula-audioplugins/dpf/distrho/extra/sofd/libsofd.c Mon Oct 16 21:53:34 2023 +0200 @@ -0,0 +1,2482 @@ +/* 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. + */ + +/* Test and example: + * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11 + * + * public API documentation and example code at the bottom of this file + * + * This small lib may one day include openGL rendering and + * wayland window support, but not today. Today we celebrate + * 30 years of X11. + */ + +#ifdef SOFD_TEST +#define HAVE_X11 +#include "libsofd.h" +#endif + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <libgen.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnarrowing" +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnarrowing" +#endif + +// shared 'recently used' implementation +// sadly, xbel does not qualify as simple. +// hence we use a simple format alike the +// gtk-bookmark list (one file per line) + +#define MAX_RECENT_ENTRIES 24 +#define MAX_RECENT_AGE (15552000) // 180 days (in sec) + +typedef struct { + char path[1024]; + time_t atime; +} FibRecentFile; + +static FibRecentFile *_recentlist = NULL; +static unsigned int _recentcnt = 0; +static uint8_t _recentlock = 0; + +static int fib_isxdigit (const char x) { + if ( + (x >= '0' && x <= '9') + || + (x >= 'a' && x <= 'f') + || + (x >= 'A' && x <= 'F') + ) return 1; + return 0; +} + +static void decode_3986 (char *str) { + int len = strlen (str); + int idx = 0; + while (idx + 2 < len) { + char *in = &str[idx]; + if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) { + char hexstr[3]; + hexstr[0] = in[1]; + hexstr[1] = in[2]; + hexstr[2] = 0; + long hex = strtol (hexstr, NULL, 16); + *in = hex; + memmove (&str[idx+1], &str[idx + 3], len - idx - 2); + len -= 2; + } + ++idx; + } +} + +static char *encode_3986 (const char *str) { + size_t alloc, newlen; + char *ns = NULL; + unsigned char in; + size_t i = 0; + size_t length; + + if (!str) return strdup (""); + + alloc = strlen (str) + 1; + newlen = alloc; + + ns = (char*) malloc (alloc); + + length = alloc; + while (--length) { + in = *str; + + switch (in) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '~': case '.': case '-': + case '/': case ',': // XXX not in RFC3986 + ns[i++] = in; + break; + default: + newlen += 2; /* this'll become a %XX */ + if (newlen > alloc) { + alloc *= 2; + ns = (char*) realloc (ns, alloc); + } + snprintf (&ns[i], 4, "%%%02X", in); + i += 3; + break; + } + ++str; + } + ns[i] = 0; + return ns; +} + +void x_fib_free_recent () { + free (_recentlist); + _recentlist = NULL; + _recentcnt = 0; +} + +static int cmp_recent (const void *p1, const void *p2) { + FibRecentFile *a = (FibRecentFile*) p1; + FibRecentFile *b = (FibRecentFile*) p2; + if (a->atime == b->atime) return 0; + return a->atime < b->atime; +} + +int x_fib_add_recent (const char *path, time_t atime) { + unsigned int i; + struct stat fs; + if (_recentlock) { return -1; } + if (access (path, R_OK)) { + return -1; + } + if (stat (path, &fs)) { + return -1; + } + if (!S_ISREG (fs.st_mode)) { + return -1; + } + if (atime == 0) atime = time (NULL); + if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) { + return -1; + } + + for (i = 0; i < _recentcnt; ++i) { + if (!strcmp (_recentlist[i].path, path)) { + if (_recentlist[i].atime < atime) { + _recentlist[i].atime = atime; + } + qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); + return _recentcnt; + } + } + _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile)); + _recentlist[_recentcnt].atime = atime; + strcpy (_recentlist[_recentcnt].path, path); + qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent); + + if (_recentcnt >= MAX_RECENT_ENTRIES) { + return (_recentcnt); + } + return (++_recentcnt); +} + +#ifdef PATHSEP +#undef PATHSEP +#endif + +#ifdef PLATFORM_WINDOWS +#define DIRSEP '\\' +#else +#define DIRSEP '/' +#endif + +static void mkpath(const char *dir) { + char tmp[1024]; + char *p; + size_t len; + + snprintf (tmp, sizeof(tmp), "%s", dir); + len = strlen(tmp); + if (tmp[len - 1] == '/') + tmp[len - 1] = 0; + for (p = tmp + 1; *p; ++p) + if(*p == DIRSEP) { + *p = 0; +#ifdef PLATFORM_WINDOWS + mkdir(tmp); +#else + mkdir(tmp, 0755); +#endif + *p = DIRSEP; + } +#ifdef PLATFORM_WINDOWS + mkdir(tmp); +#else + mkdir(tmp, 0755); +#endif +} + +int x_fib_save_recent (const char *fn) { + if (_recentlock) { return -1; } + if (!fn) { return -1; } + if (_recentcnt < 1 || !_recentlist) { return -1; } + unsigned int i; + char *dn = strdup (fn); + mkpath (dirname (dn)); + free (dn); + + FILE *rf = fopen (fn, "w"); + if (!rf) return -1; + + qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); + for (i = 0; i < _recentcnt; ++i) { + char *n = encode_3986 (_recentlist[i].path); + fprintf (rf, "%s %lu\n", n, _recentlist[i].atime); + free (n); + } + fclose (rf); + return 0; +} + +int x_fib_load_recent (const char *fn) { + char tmp[1024]; + if (_recentlock) { return -1; } + if (!fn) { return -1; } + x_fib_free_recent (); + if (access (fn, R_OK)) { + return -1; + } + FILE *rf = fopen (fn, "r"); + if (!rf) return -1; + while (fgets (tmp, sizeof(tmp), rf) + && strlen (tmp) > 1 + && strlen (tmp) < sizeof(tmp)) + { + char *s; + tmp[strlen (tmp) - 1] = '\0'; // strip newline + if (!(s = strchr (tmp, ' '))) { // find name <> atime sep + continue; + } + *s = '\0'; + time_t t = atol (++s); + decode_3986 (tmp); + x_fib_add_recent (tmp, t); + } + fclose (rf); + return 0; +} + +unsigned int x_fib_recent_count () { + return _recentcnt; +} + +const char *x_fib_recent_at (unsigned int i) { + if (i >= _recentcnt) + return NULL; + return _recentlist[i].path; +} + +#ifdef PLATFORM_WINDOWS +#define PATHSEP "\\" +#else +#define PATHSEP "/" +#endif + +const char *x_fib_recent_file(const char *appname) { + static char recent_file[1024]; + assert(!strchr(appname, '/')); + const char *xdg = getenv("XDG_DATA_HOME"); + if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) { + sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname); + return recent_file; + } +#ifdef PLATFORM_WINDOWS + const char * homedrive = getenv("HOMEDRIVE"); + const char * homepath = getenv("HOMEPATH"); + if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) { + sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname); + return recent_file; + } +#elif defined PLATFORM_OSX + const char *home = getenv("HOME"); + if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) { + sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname); + return recent_file; + } +#else + const char *home = getenv("HOME"); + if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) { + sprintf(recent_file, "%s/.local/share/%s/recent", home, appname); + return recent_file; + } +#endif + return NULL; +} + +#ifdef HAVE_X11 +#include <dirent.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> +#include <X11/Xos.h> + +#if defined(__linux__) || defined(__linux) +#define HAVE_MNTENT +#include <mntent.h> +#endif + +#ifndef MIN +#define MIN(A,B) ( (A) < (B) ? (A) : (B) ) +#endif + +#ifndef MAX +#define MAX(A,B) ( (A) < (B) ? (B) : (A) ) +#endif + +static Window _fib_win = 0; +static GC _fib_gc = 0; +static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5; +static Font _fibfont = 0; +static Pixmap _pixbuffer = None; + +static int _fib_width = 100; +static int _fib_height = 100; +static int _btn_w = 0; +static int _btn_span = 0; +static double _scalefactor = 1; + +static int _fib_font_height = 0; +static int _fib_dir_indent = 0; +static int _fib_spc_norm = 0; +static int _fib_font_ascent = 0; +static int _fib_font_vsep = 0; +static int _fib_font_size_width = 0; +static int _fib_font_time_width = 0; +static int _fib_place_width = 0; + +static int _scrl_f = 0; +static int _scrl_y0 = -1; +static int _scrl_y1 = -1; +static int _scrl_my = -1; +static int _scrl_mf = -1; +static int _view_p = -1; + +static int _fsel = -1; +static int _hov_b = -1; +static int _hov_f = -1; +static int _hov_p = -1; +static int _hov_h = -1; +static int _hov_l = -1; +static int _hov_s = -1; +static int _sort = 0; +static int _columns = 0; +static int _fib_filter_fn = 1; +static int _fib_hidden_fn = 0; +static int _fib_show_places = 0; + +static uint8_t _fib_mapped = 0; +static uint8_t _fib_resized = 0; +static unsigned long _dblclk = 0; + +static int _status = -2; +static char _rv_open[1024] = ""; + +static char _fib_cfg_custom_places[1024] = ""; +static char _fib_cfg_custom_font[256] = ""; +static char _fib_cfg_title[128] = "xjadeo - Open Video File"; + +typedef struct { + char name[256]; + int x0; + int xw; +} FibPathButton; + +typedef struct { + char name[256]; + char strtime[32]; + char strsize[32]; + int ssizew; + off_t size; + time_t mtime; + uint8_t flags; // 2: selected, 4: isdir 8: recent-entry + FibRecentFile *rfp; +} FibFileEntry; + +typedef struct { + char text[24]; + uint8_t flags; // 2: selected, 4: toggle, 8 disable + int x0; + int tw; + int xw; + void (*callback)(Display*); +} FibButton; + +typedef struct { + char name[256]; + char path[1024]; + uint8_t flags; // 1: hover, 2: selected, 4:add sep +} FibPlace; + +static char _cur_path[1024] = ""; +static FibFileEntry *_dirlist = NULL; +static FibPathButton *_pathbtn = NULL; +static FibPlace *_placelist = NULL; +static int _dircount = 0; +static int _pathparts = 0; +static int _placecnt = 0; + +static FibButton _btn_ok; +static FibButton _btn_cancel; +static FibButton _btn_filter; +static FibButton _btn_places; +static FibButton _btn_hidden; +static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok}; + +static int (*_fib_filter_function)(const char *filename); + +/* hardcoded layout */ +#define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list +#define PSEP 4 // px; horiz space beween paths +#define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column +#define LISTTOP 2.7 //em; top of the file-browser list +#define LISTBOT 4.75 //em; bottom of the file-browers list +#define BTNBTMMARGIN 0.75 //em; height/margin of the button row +#define BTNPADDING 2 // px - only used for open/cancel buttons +#define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3) +#define SCROLLBOXH 10 //px; arrow box top+bottom +#define PLACESW _fib_place_width //px; +#define PLACESWMAX (15 *_fib_spc_norm) //px; +#define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent); +#define FAREAMRGB 3 //px; base L+R margin +#define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width) +#define FAREAMRGL (_fib_show_places ? PLACESW / _scalefactor + FAREAMRGB : FAREAMRGB) //px; left margin of file-area +#define TEXTSEP 4 //px; +#define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP +#define SORTBTNOFF -10 //px; + +#ifndef DBLCLKTME +#define DBLCLKTME 200 //msec; double click time +#endif + +#define DRAW_OUTLINE +#define DOUBLE_BUFFER +#define LIST_ENTRY_HOVER + +static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) { + XCharStruct text_structure; + int font_direction, font_ascent, font_descent; + XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc)); + + if (!fontinfo) { return -1; } + XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure); + if (w) *w = XTextWidth (fontinfo, txt, strlen (txt)); + if (h) *h = text_structure.ascent + text_structure.descent; + if (a) *a = text_structure.ascent; + if (d) *d = text_structure.descent; +#ifndef DISTRHO_OS_HAIKU // FIXME + XFreeFontInfo (NULL, fontinfo, 1); +#endif + return 0; +} + +static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) { +#ifdef DRAW_OUTLINE + XSetForeground (dpy, gc, _c_gray5.pixel); + XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h); + XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h); + XDrawLine (dpy, d, gc, x + 1, y, x + w, y); + XDrawLine (dpy, d, gc, x, y + 1, x, y + h); +#else + const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); + XSetForeground (dpy, _fib_gc, blackColor); + XDrawRectangle (dpy, d, gc, x, y, w, h); +#endif +} + +static void fib_expose (Display *dpy, Window realwin) { + int i; + XID win; + if (!_fib_mapped) return; + + if (_fib_resized +#ifdef DOUBLE_BUFFER + || !_pixbuffer +#endif + ) + { +#ifdef DOUBLE_BUFFER + unsigned int w = 0, h = 0; + if (_pixbuffer != None) { + Window ignored_w; + int ignored_i; + unsigned int ignored_u; + XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u); + if (_fib_width != (int)w || _fib_height != (int)h) { + XFreePixmap (dpy, _pixbuffer); + _pixbuffer = None; + } + } + if (_pixbuffer == None) { + XWindowAttributes wa; + XGetWindowAttributes (dpy, realwin, &wa); + _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth); + } +#endif + if (_pixbuffer != None) { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height); + } else { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height); + } + _fib_resized = 0; + } + + if (_pixbuffer == None) { + win = realwin; + } else { + win = _pixbuffer; + } + + // Top Row: dirs and up navigation + + int ppw = 0; + int ppx = FAREAMRGB * _scalefactor; + + for (i = _pathparts - 1; i >= 0; --i) { + ppw += _pathbtn[i].xw + PSEP * _scalefactor; + if (ppw >= _fib_width - PSEP * _scalefactor - _pathbtn[0].xw - FAREAMRGB * _scalefactor) break; // XXX, first change is from "/" to "<", NOOP + } + ++i; + // border-less "<" parent/up, IFF space is limited + if (i > 0) { + if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1); + ppx += _pathbtn[0].xw + PSEP * _scalefactor; + if (i == _pathparts) --i; + } + + _view_p = i; + + while (i < _pathparts) { + if (i == _hov_p) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + XFillRectangle (dpy, win, _fib_gc, + ppx + 1, PATHBTNTOP - _fib_font_ascent, + _pathbtn[i].xw - 1, _fib_font_height); + VDrawRectangle (dpy, win, _fib_gc, + ppx, PATHBTNTOP - _fib_font_ascent, + _pathbtn[i].xw, _fib_font_height); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP, + _pathbtn[i].name, strlen (_pathbtn[i].name)); + _pathbtn[i].x0 = ppx; // current position + ppx += _pathbtn[i].xw + PSEP * _scalefactor; + ++i; + } + + // middle, scroll list of file names + const int ltop = LISTTOP * _fib_font_vsep; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int fsel_height = 4 * _scalefactor + llen * _fib_font_vsep; + const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0); + const int t_x = FAREATEXTL * _scalefactor; + int t_s = FAREATEXTL * _scalefactor + fsel_width; + int t_t = FAREATEXTL * _scalefactor + fsel_width; + + // check which colums can be visible + // depending on available width of window. + _columns = 0; + if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) { + _columns |= 2; + t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * _scalefactor; + } + if (fsel_width > FILECOLUMN + _fib_font_size_width) { + _columns |= 1; + t_t = t_s - _fib_font_size_width - TEXTSEP * _scalefactor; + } + + int fstop = _scrl_f; // first entry in scroll position + const int ttop = ltop - _fib_font_height + _fib_font_ascent; + + if (fstop > 0 && fstop + llen > _dircount) { + fstop = MAX (0, _dircount - llen); + _scrl_f = fstop; + } + + // list header + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep); + + // draw background of file list + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop, fsel_width, fsel_height); + +#ifdef DRAW_OUTLINE + VDrawRectangle (dpy, win, _fib_gc, + FAREAMRGL * _scalefactor, + ltop - _fib_font_vsep - 1, + _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor, + fsel_height + _fib_font_vsep + 1); +#endif + + switch (_hov_h) { + case 1: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, + t_x + _fib_dir_indent - (TEXTSEP - 1) * _scalefactor, + ltop - _fib_font_vsep, + t_t - t_x - _fib_dir_indent - 1 * _scalefactor, + _fib_font_vsep); + break; + case 2: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, + t_t - (TEXTSEP - 1) * _scalefactor, + ltop - _fib_font_vsep, + _fib_font_size_width + (TEXTSEP - 1) * _scalefactor, + _fib_font_vsep); + break; + case 3: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, + t_s - (TEXTSEP - 1) * _scalefactor, + ltop - _fib_font_vsep, + TEXTSEP * 2 * _scalefactor + _fib_font_time_width - 1 * _scalefactor, + _fib_font_vsep); + break; + default: + break; + } + + // column headings and sort order + int arp = MAX (2 * _scalefactor, _fib_font_height / 5); // arrow scale + const int trioff = _fib_font_height - _fib_font_ascent - arp + 1 * _scalefactor; + XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1 * _scalefactor}, {-arp - arp, 0}, {arp, arp + arp + 1 * _scalefactor}}; + if (_sort & 1) { + ptri[0].y = ttop -arp - arp - 1 * _scalefactor; + ptri[1].y *= -1; + ptri[3].y *= -1; + } + switch (_sort) { + case 0: + case 1: + ptri[0].x = t_t + (SORTBTNOFF + 2) * _scalefactor - arp; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + break; + case 2: + case 3: + if (_columns & 1) { + ptri[0].x = t_s + (SORTBTNOFF + 2) * _scalefactor - arp; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + } + break; + case 4: + case 5: + if (_columns & 2) { + ptri[0].x = FAREATEXTL * _scalefactor + fsel_width + (SORTBTNOFF + 2) * _scalefactor - arp; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + } + break; + } + +#if 0 // bottom header bottom border + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); + XDrawLine (dpy, win, _fib_gc, + FAREAMRGL + 1, ltop, + FAREAMRGL + fsel_width, ltop); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); +#endif + + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, + t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4); + + if (_columns & 1) { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + t_t - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, + t_t - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4); + } + + if (_columns & 2) { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + t_s - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, + t_s - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + if (_pathparts > 0) + XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13); + else + XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9); + } + + // scrollbar sep + if (llen < _dircount) { + const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor; + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + sx0 - 1, ltop - _fib_font_vsep, +#ifdef DRAW_OUTLINE + sx0 - 1, ltop + fsel_height +#else + sx0 - 1, ltop - 1 +#endif + ); + } + + // clip area for file-name + XRectangle clp = {(FAREAMRGL + 1) * _scalefactor, ltop, + t_t - (FAREAMRGL + TEXTSEP * 2 + 1) * _scalefactor, fsel_height}; + + // list files in view + for (i = 0; i < llen; ++i) { + const int j = i + fstop; + if (j >= _dircount) break; + + const int t_y = ltop + (i+1) * _fib_font_vsep - 4; + + if (_dirlist[j].flags & 2) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XFillRectangle (dpy, win, _fib_gc, + FAREAMRGL * _scalefactor, t_y - _fib_font_ascent, fsel_width, _fib_font_height); + } + /* + if (_hov_f == j && !(_dirlist[j].flags & 2)) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } + */ + if (_dirlist[j].flags & 4) { + XSetForeground (dpy, _fib_gc, (_dirlist[j].flags & 2) ? _c_gray3.pixel : _c_gray5.pixel); + XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1); + } + XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, + t_x + _fib_dir_indent, t_y, + _dirlist[j].name, strlen (_dirlist[j].name)); + XSetClipMask (dpy, _fib_gc, None); + + if (_columns & 1) // right-aligned 'size' + XDrawString (dpy, win, _fib_gc, + t_s - (TEXTSEP + 2) * _scalefactor - _dirlist[j].ssizew, t_y, + _dirlist[j].strsize, strlen (_dirlist[j].strsize)); + if (_columns & 2) + XDrawString (dpy, win, _fib_gc, + t_s, t_y, + _dirlist[j].strtime, strlen (_dirlist[j].strtime)); + } + + // scrollbar + if (llen < _dircount) { + float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor) / (float) _dircount; + sl = MAX ((8. * _scalefactor / llen), sl); // 8px min height of scroller + const int sy1 = llen * sl; + const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor - sy1) / (float)(_dircount - llen); + const int sy0 = fstop * mx; + const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor; + const int stop = ltop - _fib_font_vsep; + + _scrl_y0 = stop + SCROLLBOXH * _scalefactor + sy0; + _scrl_y1 = _scrl_y0 + sy1; + + assert (fstop + llen <= _dircount); + // scroll-bar background + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW * _scalefactor, fsel_height + _fib_font_vsep); + + // scroller + if (_hov_s == 0 || _hov_s == 1 || _hov_s == 2) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XFillRectangle (dpy, win, _fib_gc, sx0 + 1 * _scalefactor, stop + SCROLLBOXH * _scalefactor + sy0, (SCROLLBARW - 2) * _scalefactor, sy1); + + int scrw = (SCROLLBARW -3) / 2 * _scalefactor; + // arrows top and bottom + if (_hov_s == 1) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XPoint ptst[4] = { {sx0 + 1 * _scalefactor, stop + 8 * _scalefactor}, {scrw, -7 * _scalefactor}, {scrw, 7 * _scalefactor}, {-2 * scrw, 0}}; + XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious); + + if (_hov_s == 2) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XPoint ptsb[4] = { {sx0 + 1 * _scalefactor, ltop + fsel_height - 9 * _scalefactor}, {2*scrw, 0}, {-scrw, 7 * _scalefactor}, {-scrw, -7 * _scalefactor}}; + XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious); + } else { + _scrl_y0 = _scrl_y1 = -1; + } + + if (_fib_show_places) { + assert (_placecnt > 0); + + // heading + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep); + + // body + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop, PLACESW - TEXTSEP * _scalefactor, fsel_height); + +#ifdef DRAW_OUTLINE + VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP * _scalefactor, fsel_height + _fib_font_vsep + 1); +#endif + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, (FAREAMRGB + TEXTSEP) * _scalefactor, ttop, "Places", 6); + + XRectangle pclip = {(FAREAMRGB + 1) * _scalefactor, ltop, PLACESW - (TEXTSEP + 1) * _scalefactor, fsel_height}; + XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted); + const int plx = (FAREAMRGB + TEXTSEP) * _scalefactor; + for (i = 0; i < llen && i < _placecnt; ++i) { + const int ply = ltop + (i+1) * _fib_font_vsep - 4 * _scalefactor; + if (i == _hov_l) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop + i * _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep); + } + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, + plx, ply, + _placelist[i].name, strlen (_placelist[i].name)); + if (_placelist[i].flags & 4) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + const int plly = ply - _fib_font_ascent + _fib_font_height + 1 * _scalefactor; + const int pllx0 = FAREAMRGB * _scalefactor; + const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * _scalefactor; + XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); + } + } + XSetClipMask (dpy, _fib_gc, None); + + if (_placecnt > llen) { + const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent; + const int pllx0 = FAREAMRGB * _scalefactor + (PLACESW - TEXTSEP * _scalefactor) * .75; + const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * 2 * _scalefactor; + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineOnOffDash, CapButt, JoinMiter); + XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); + XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineSolid, CapButt, JoinMiter); + } + } + + // Bottom Buttons + const int numb = sizeof(_btns) / sizeof(FibButton*); + int xtra = _fib_width - _btn_span; + const int cbox = _fib_font_ascent - 2 * _scalefactor; + const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING * _scalefactor; + const int cblw = cbox > 20 * _scalefactor + ? 5 * _scalefactor + : (cbox > 9 * _scalefactor ? 3 : 1) * _scalefactor; + + int bx = FAREAMRGB * _scalefactor; + for (i = 0; i < numb; ++i) { + if (_btns[i]->flags & 8) { continue; } + if (_btns[i]->flags & 4) { + // checkbutton + const int cby0 = bbase - cbox + (1 + BTNPADDING) * _scalefactor; + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XDrawRectangle (dpy, win, _fib_gc, + bx, cby0 - 1, cbox + 1, cbox + 1); + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, BTNPADDING * _scalefactor + bx + _fib_font_ascent, bbase + (BTNPADDING + 1) * _scalefactor, + _btns[i]->text, strlen (_btns[i]->text)); + + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + /* if (_btns[i]->flags & 2) { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + } else */ { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + } + XFillRectangle (dpy, win, _fib_gc, + bx+1, cby0, cbox, cbox); + + if (_btns[i]->flags & 2) { + XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawLine (dpy, win, _fib_gc, + bx + 2, cby0 + 1, + bx + cbox - 1, cby0 + cbox - 2); + XDrawLine (dpy, win, _fib_gc, + bx + cbox - 1, cby0 + 1, + bx + 2, cby0 + cbox - 2); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + } + } else { + if (xtra > 0) { + bx += xtra; + xtra = 0; + } + // pushbutton + + uint8_t can_hover = 1; // special case + if (_btns[i] == &_btn_ok) { + if (_fsel < 0 || _fsel >= _dircount) { + can_hover = 0; + } + } + + if (can_hover && i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + XFillRectangle (dpy, win, _fib_gc, + bx + 1, bbase - _fib_font_ascent, + _btn_w - 1, _fib_font_height + BTNPADDING * 2 * _scalefactor); + VDrawRectangle (dpy, win, _fib_gc, + bx, bbase - _fib_font_ascent, + _btn_w, _fib_font_height + BTNPADDING * 2 * _scalefactor); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING * _scalefactor, + _btns[i]->text, strlen (_btns[i]->text)); + } + _btns[i]->x0 = bx; + bx += _btns[i]->xw + DSEP * _scalefactor; + } + + if (_pixbuffer != None) { + XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0); + } + XFlush (dpy); +} + +static void fib_reset () { + _hov_p = _hov_f = _hov_h = _hov_l = -1; + _scrl_f = 0; + _fib_resized = 1; +} + +static int cmp_n_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + return strcmp (a->name, b->name); +} + +static int cmp_n_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + return strcmp (b->name, a->name); +} + +static int cmp_t_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->mtime == b->mtime) return 0; + return a->mtime > b->mtime ? -1 : 1; +} + +static int cmp_t_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->mtime == b->mtime) return 0; + return a->mtime > b->mtime ? 1 : -1; +} + +static int cmp_s_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->size == b->size) return 0; + return a->size > b->size ? -1 : 1; +} + +static int cmp_s_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->size == b->size) return 0; + return a->size > b->size ? 1 : -1; +} + +static void fmt_size (Display *dpy, FibFileEntry *f) { + if (f->size > 10995116277760) { + sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f); + } + if (f->size > 1099511627776) { + sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f); + } + else if (f->size > 10737418240) { + sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f); + } + else if (f->size > 1073741824) { + sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f); + } + else if (f->size > 10485760) { + sprintf (f->strsize, "%.0f MB", f->size / 1048576.f); + } + else if (f->size > 1048576) { + sprintf (f->strsize, "%.1f MB", f->size / 1048576.f); + } + else if (f->size > 10240) { + sprintf (f->strsize, "%.0f KB", f->size / 1024.f); + } + else if (f->size >= 1000) { + sprintf (f->strsize, "%.1f KB", f->size / 1024.f); + } + else { + sprintf (f->strsize, "%.0f B", f->size / 1.f); + } + int sw = 0; + query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL); + if (sw > _fib_font_size_width) { + _fib_font_size_width = sw; + } + f->ssizew = sw; +} + +static void fmt_time (Display *dpy, FibFileEntry *f) { + struct tm *tmp; + tmp = localtime (&f->mtime); + if (!tmp) { + return; + } + strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp); + + int tw = 0; + query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL); + if (tw > _fib_font_time_width) { + _fib_font_time_width = tw; + } +} + +static void fib_resort (const char * sel) { + if (_dircount < 1) { return; } + int (*sortfn)(const void *p1, const void *p2); + switch (_sort) { + case 1: sortfn = &cmp_n_down; break; + case 2: sortfn = &cmp_s_down; break; + case 3: sortfn = &cmp_s_up; break; + case 4: sortfn = &cmp_t_down; break; + case 5: sortfn = &cmp_t_up; break; + default: + sortfn = &cmp_n_up; + break; + } + qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn); + int i; + for (i = 0; i < _dircount && sel; ++i) { + if (!strcmp (_dirlist[i].name, sel)) { + _fsel = i; + break; + } + } +} + +static void fib_select (Display *dpy, int item) { + if (_fsel >= 0) { + _dirlist[_fsel].flags &= ~2; + } + _fsel = item; + if (_fsel >= 0 && _fsel < _dircount) { + _dirlist[_fsel].flags |= 2; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (_fsel < _scrl_f) { + _scrl_f = _fsel; + } + else if (_fsel >= _scrl_f + llen) { + _scrl_f = 1 + _fsel - llen; + } + } else { + _fsel = -1; + } + + fib_expose (dpy, _fib_win); +} + +static inline int fib_filter (const char *name) { + if (_fib_filter_function) { + return _fib_filter_function (name); + } else { + return 1; + } +} + +static void fib_pre_opendir (Display *dpy) { + if (_dirlist) free (_dirlist); + if (_pathbtn) free (_pathbtn); + _dirlist = NULL; + _pathbtn = NULL; + _dircount = 0; + _pathparts = 0; + query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL); + fib_reset (); + _fsel = -1; +} + +static void fib_post_opendir (Display *dpy, const char *sel) { + if (_dircount > 0) + _fsel = 0; // select first + else + _fsel = -1; + fib_resort (sel); + + if (_dircount > 0 && _fsel >= 0) { + fib_select (dpy, _fsel); + } else { + fib_expose (dpy, _fib_win); + } +} + +static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) { + char tp[1024]; + struct stat fs; + if (!_fib_hidden_fn && name[0] == '.') return -1; + if (!strcmp (name, ".")) return -1; + if (!strcmp (name, "..")) return -1; + strcpy (tp, path); + strcat (tp, name); + if (access (tp, R_OK)) { + return -1; + } + if (stat (tp, &fs)) { + return -1; + } + assert (i < _dircount); // could happen if dir changes while we're reading. + if (i >= _dircount) return -1; + if (S_ISDIR (fs.st_mode)) { + _dirlist[i].flags |= 4; + } + else if (S_ISREG (fs.st_mode)) { + if (!fib_filter (name)) return -1; + } +#if 0 // only needed with lstat() + else if (S_ISLNK (fs.st_mode)) { + if (!fib_filter (name)) return -1; + } +#endif + else { + return -1; + } + strcpy (_dirlist[i].name, name); + _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime; + _dirlist[i].size = fs.st_size; + if (!(_dirlist[i].flags & 4)) + fmt_size (dpy, &_dirlist[i]); + fmt_time (dpy, &_dirlist[i]); + return 0; +} + +static int fib_openrecent (Display *dpy, const char *sel) { + int i; + unsigned int j; + assert (_recentcnt > 0); + fib_pre_opendir (dpy); + query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL); + _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry)); + _dircount = _recentcnt; + for (j = 0, i = 0; j < _recentcnt; ++j) { + char base[1024]; + char *s = strrchr (_recentlist[j].path, '/'); + if (!s || !*++s) continue; + size_t len = (s - _recentlist[j].path); + strncpy (base, _recentlist[j].path, len); + base[len] = '\0'; + if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) { + _dirlist[i].rfp = &_recentlist[j]; + _dirlist[i].flags |= 8; + ++i; + } + } + _dircount = i; + fib_post_opendir (dpy, sel); + return _dircount; +} + +static int fib_opendir (Display *dpy, const char* path, const char *sel) { + char *t0, *t1; + int i; + + assert (path); + + if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this + strcpy (_cur_path, ""); + return fib_openrecent (dpy, sel); + } + + assert (strlen (path) < sizeof(_cur_path) -1); + assert (strlen (path) > 0); + assert (strstr (path, "//") == NULL); + assert (path[0] == '/'); + + fib_pre_opendir (dpy); + + query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL); + DIR *dir = opendir (path); + if (!dir) { + strcpy (_cur_path, "/"); + } else { + int i; + struct dirent *de; + if (path != _cur_path) + strcpy (_cur_path, path); + + if (_cur_path[strlen (_cur_path) -1] != '/') + strcat (_cur_path, "/"); + + while ((de = readdir (dir))) { + if (!_fib_hidden_fn && de->d_name[0] == '.') continue; + ++_dircount; + } + + if (_dircount > 0) + _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry)); + + rewinddir (dir); + + i = 0; + while ((de = readdir (dir))) { + if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0)) + ++i; + } + _dircount = i; + closedir (dir); + } + + t0 = _cur_path; + while (*t0 && (t0 = strchr (t0, '/'))) { + ++_pathparts; + ++t0; + } + assert (_pathparts > 0); + _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton)); + + t1 = _cur_path; + i = 0; + while (*t1 && (t0 = strchr (t1, '/'))) { + if (i == 0) { + strcpy (_pathbtn[i].name, "/"); + } else { + *t0 = 0; + strcpy (_pathbtn[i].name, t1); + } + query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL); + _pathbtn[i].xw += BTNPADDING + BTNPADDING; + *t0 = '/'; + t1 = t0 + 1; + ++i; + } + fib_post_opendir (dpy, sel); + return _dircount; +} + +static int fib_open (Display *dpy, int item) { + char tp[1024]; + if (_dirlist[item].flags & 8) { + assert (_dirlist[item].rfp); + strcpy (_rv_open, _dirlist[item].rfp->path); + _status = 1; + return 0; + } + strcpy (tp, _cur_path); + strcat (tp, _dirlist[item].name); + if (_dirlist[item].flags & 4) { + fib_opendir (dpy, tp, NULL); + return 0; + } else { + _status = 1; + strcpy (_rv_open, tp); + } + return 0; +} + +static void cb_cancel (Display *dpy) { + _status = -1; + + // unused + return; (void)dpy; +} + +static void cb_open (Display *dpy) { + if (_fsel >= 0 && _fsel < _dircount) { + fib_open (dpy, _fsel); + } +} + +static void sync_button_states () { + if (_fib_show_places) + _btn_places.flags |= 2; + else + _btn_places.flags &= ~2; + if (_fib_filter_fn) // inverse -> show all + _btn_filter.flags &= ~2; + else + _btn_filter.flags |= 2; + if (_fib_hidden_fn) + _btn_hidden.flags |= 2; + else + _btn_hidden.flags &= ~2; +} + +static void cb_places (Display *dpy) { + _fib_show_places = ! _fib_show_places; + if (_placecnt < 1) + _fib_show_places = 0; + sync_button_states (); + _fib_resized = 1; + fib_expose (dpy, _fib_win); +} + +static void cb_filter (Display *dpy) { + _fib_filter_fn = ! _fib_filter_fn; + sync_button_states (); + char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; + fib_opendir (dpy, _cur_path, sel); + free (sel); +} + +static void cb_hidden (Display *dpy) { + _fib_hidden_fn = ! _fib_hidden_fn; + sync_button_states (); + char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; + fib_opendir (dpy, _cur_path, sel); + free (sel); +} + +static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) { + const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING * _scalefactor; + const int bbot = btop + _fib_font_height + BTNPADDING * 2 * _scalefactor; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int ltop = LISTTOP * _fib_font_vsep; + const int fbot = ltop + 4 * _scalefactor + llen * _fib_font_vsep; + const int ptop = PATHBTNTOP - _fib_font_ascent; + assert (it); + + // paths at top + if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) { + int i = _view_p; + *it = -1; + if (i > 0) { // special case '<' + if (x > FAREAMRGB * _scalefactor && x <= FAREAMRGB * _scalefactor + _pathbtn[0].xw) { + *it = _view_p - 1; + i = _pathparts; + } + } + while (i < _pathparts) { + if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) { + *it = i; + break; + } + ++i; + } + assert (*it < _pathparts); + if (*it >= 0) return 1; + else return 0; + } + + // buttons at bottom + if (y > btop && y < bbot) { + size_t i; + *it = -1; + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + const int bx = _btns[i]->x0; + if (_btns[i]->flags & 8) { continue; } + if (x > bx && x < bx + _btns[i]->xw) { + *it = i; + } + } + if (*it >= 0) return 3; + else return 0; + } + + // main file area + if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL * _scalefactor && x < _fib_width - FAREAMRGR * _scalefactor) { + // scrollbar + if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) * _scalefactor && x <= _fib_width - FAREAMRGR * _scalefactor) { + if (y >= _scrl_y0 && y < _scrl_y1) { + *it = 0; + } else if (y >= _scrl_y1) { + *it = 2; + } else { + *it = 1; + } + return 4; + } + // file-list + else if (y >= ltop) { + const int item = (y - ltop) / _fib_font_vsep + _scrl_f; + *it = -1; + if (item >= 0 && item < _dircount) { + *it = item; + } + if (*it >= 0) return 2; + else return 0; + } + else { + *it = -1; + const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0); + const int t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * 2 * _scalefactor; + const int t_t = FAREAMRGL * _scalefactor + fsel_width - TEXTSEP * _scalefactor - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP * 2 * _scalefactor) : 0); + if (x >= fsel_width + FAREAMRGL * _scalefactor) ; + else if ((_columns & 2) && x >= t_s) *it = 3; + else if ((_columns & 1) && x >= t_t) *it = 2; + else if (x >= FAREATEXTL * _scalefactor + _fib_dir_indent - TEXTSEP * _scalefactor) *it = 1; + if (*it >= 0) return 5; + else return 0; + } + } + + // places list + if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB * _scalefactor && x < (FAREAMRGL - FAREAMRGB) * _scalefactor) { + const int item = (y - ltop) / _fib_font_vsep; + *it = -1; + if (item >= 0 && item < _placecnt) { + *it = item; + } + if (*it >= 0) return 6; + else return 0; + } + + return 0; + + // unused + (void)dpy; +} + +static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) { + int hov_p = -1; + int hov_b = -1; + int hov_h = -1; + int hov_s = -1; +#ifdef LIST_ENTRY_HOVER + int hov_f = -1; + int hov_l = -1; +#endif + + switch (type) { + case 1: hov_p = item; break; + case 3: hov_b = item; break; + case 4: hov_s = item; break; + case 5: hov_h = item; break; +#ifdef LIST_ENTRY_HOVER + case 6: hov_l = item; break; + case 2: hov_f = item; break; +#endif + default: break; + } +#ifdef LIST_ENTRY_HOVER + if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; } + if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; } +#endif + if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; } + if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; } + if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; } + if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; } + + if (need_expose) { + fib_expose (dpy, _fib_win); + } +} + +static void fib_motion (Display *dpy, int x, int y) { + int it = -1; + + if (_scrl_my >= 0) { + const int sdiff = y - _scrl_my; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int fsel_height = 4 + llen * _fib_font_vsep; + const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; + + int news = _scrl_mf + sdiff / sl; + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f) { + _scrl_f = news; + fib_expose (dpy, _fib_win); + } + return; + } + + const int type = fib_widget_at_pos (dpy, x, y, &it); + fib_update_hover (dpy, 0, type, it); +} + +static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) { + int it; + switch (fib_widget_at_pos (dpy, x, y, &it)) { + case 4: // scrollbar + if (btn == 1) { + _dblclk = 0; + if (it == 0) { + _scrl_my = y; + _scrl_mf = _scrl_f; + } else { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 2) llen = 2; + int news = _scrl_f; + if (it == 1) { + news -= llen - 1; + } else { + news += llen - 1; + } + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f && _scrl_y0 >= 0) { + assert (news >=0); + _scrl_f = news; + fib_update_hover (dpy, 1, 4, it); + } + } + } + break; + case 2: // file-list + if (btn == 4 || btn == 5) { + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + int news = _scrl_f + ((btn == 4) ? - 1 : 1); + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f && _scrl_y0 >= 0) { + assert (news >=0); + _scrl_f = news; + fib_update_hover (dpy, 1, 0, 0); + } + _dblclk = 0; + } + else if (btn == 1 && it >= 0 && it < _dircount) { + if (_fsel == it) { + if (time - _dblclk < DBLCLKTME) { + fib_open (dpy, it); + _dblclk = 0; + } + _dblclk = time; + } else { + fib_select (dpy, it); + _dblclk = time; + } + /* + if (_fsel >= 0) { + if (!(_dirlist[_fsel].flags & 4)); + } + */ + } + break; + case 1: // paths + assert (_fsel < _dircount); + assert (it >= 0 && it < _pathparts); + { + int i = 0; + char path[1024] = "/"; + while (++i <= it) { + strcat (path, _pathbtn[i].name); + strcat (path, "/"); + } + char *sel = NULL; + if (i < _pathparts) + sel = strdup (_pathbtn[i].name); + else if (i == _pathparts && _fsel >= 0) + sel = strdup (_dirlist[_fsel].name); + fib_opendir (dpy, path, sel); + free (sel); + } + break; + case 3: // btn + if (btn == 1 && _btns[it]->callback) { + _btns[it]->callback (dpy); + } + break; + case 5: // sort + if (btn == 1) { + switch (it) { + case 1: if (_sort == 0) _sort = 1; else _sort = 0; break; + case 2: if (_sort == 2) _sort = 3; else _sort = 2; break; + case 3: if (_sort == 4) _sort = 5; else _sort = 4; break; + } + if (_fsel >= 0) { + assert (_dirlist && _dircount >= _fsel); + _dirlist[_fsel].flags &= ~2; + char *sel = strdup (_dirlist[_fsel].name); + fib_resort (sel); + free (sel); + } else { + fib_resort (NULL); + _fsel = -1; + } + fib_reset (); + _hov_h = it; + fib_select (dpy, _fsel); + } + break; + case 6: + if (btn == 1 && it >= 0 && it < _placecnt) { + fib_opendir (dpy, _placelist[it].path, NULL); + } + break; + default: + break; + } +} + +static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) { + _scrl_my = -1; + + // unused + return; (void)dpy; (void)x; (void)y; (void)btn; (void)time; +} + +static void add_place_raw (Display *dpy, const char *name, const char *path) { + _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace)); + strcpy (_placelist[_placecnt].path, path); + strcpy (_placelist[_placecnt].name, name); + _placelist[_placecnt].flags = 0; + + int sw = -1; + query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL); + if (sw > _fib_place_width) { + _fib_place_width = sw; + } + ++_placecnt; +} + +static int add_place_places (Display *dpy, const char *name, const char *url) { + char const * path; + struct stat fs; + int i; + if (!url || strlen (url) < 1) return -1; + if (!name || strlen (name) < 1) return -1; + if (url[0] == '/') { + path = url; + } + else if (!strncmp (url, "file:///", 8)) { + path = &url[7]; + } + else { + return -1; + } + + if (access (path, R_OK)) { + return -1; + } + if (stat (path, &fs)) { + return -1; + } + if (!S_ISDIR (fs.st_mode)) { + return -1; + } + + for (i = 0; i < _placecnt; ++i) { + if (!strcmp (path, _placelist[i].path)) { + return -1; + } + } + add_place_raw (dpy, name, path); + return 0; +} + +static int parse_gtk_bookmarks (Display *dpy, const char *fn) { + char tmp[1024]; + if (access (fn, R_OK)) { + return -1; + } + FILE *bm = fopen (fn, "r"); + if (!bm) return -1; + int found = 0; + while (fgets (tmp, sizeof(tmp), bm) + && strlen (tmp) > 1 + && strlen (tmp) < sizeof(tmp)) + { + char *s, *n; + tmp[strlen (tmp) - 1] = '\0'; // strip newline + if ((s = strchr (tmp, ' '))) { + *s = '\0'; + n = strdup (++s); + decode_3986 (tmp); + if (!add_place_places (dpy, n, tmp)) { + ++found; + } + free (n); + } else if ((s = strrchr (tmp, '/'))) { + n = strdup (++s); + decode_3986 (tmp); + if (!add_place_places (dpy, n, tmp)) { + ++found; + } + free (n); + } + } + fclose (bm); + return found; +} + +#ifdef HAVE_MNTENT +static const char *ignore_mountpoints[] = { + "/bin", "/boot", "/dev", "/etc", + "/lib", "/live", "/mnt", "/opt", + "/root", "/sbin", "/srv", "/tmp", + "/usr", "/var", "/proc", "/sbin", + "/net", "/sys" +}; + +static const char *ignore_fs[] = { + "auto", "autofs", + "debugfs", "devfs", + "devpts", "ecryptfs", + "fusectl", "kernfs", + "linprocfs", "proc", + "ptyfs", "rootfs", + "selinuxfs", "sysfs", + "tmpfs", "usbfs", + "nfsd", "rpc_pipefs", +}; + +static const char *ignore_devices[] = { + "binfmt_", "devpts", + "gvfs", "none", + "nfsd", "sunrpc", + "/dev/loop", "/dev/vn" +}; + +static int check_mount (const char *mountpoint, const char *fs, const char *device) { + size_t i; + if (!mountpoint || !fs || !device) return -1; + //printf("%s %s %s\n", mountpoint, fs, device); + for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) { + if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) { + return 1; + } + } + if (!strncmp (mountpoint, "/home", 5)) { + return 1; + } + for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) { + if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) { + return 1; + } + } + for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) { + if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) { + return 1; + } + } + return 0; +} + +static int read_mtab (Display *dpy, const char *mtab) { + FILE *mt = fopen (mtab, "r"); + if (!mt) return -1; + int found = 0; + struct mntent *mntent; + while ((mntent = getmntent (mt)) != NULL) { + char *s; + if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname)) + continue; + + if ((s = strrchr (mntent->mnt_dir, '/'))) { + ++s; + } else { + s = mntent->mnt_dir; + } + if (!add_place_places (dpy, s, mntent->mnt_dir)) { + ++found; + } + } + fclose (mt); + return found; +} +#endif + +static void populate_places (Display *dpy) { + char tmp[1024]; + int spacer = -1; + if (_placecnt > 0) return; + _fib_place_width = 0; + + if (_recentcnt > 0) { + add_place_raw (dpy, "Recently Used", ""); + _placelist[0].flags |= 4; + } + + add_place_places (dpy, "Home", getenv ("HOME")); + + if (getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/Desktop"); + add_place_places (dpy, "Desktop", tmp); + } + + add_place_places (dpy, "Filesystem", "/"); + + if (_placecnt > 0) spacer = _placecnt -1; + + if (strlen (_fib_cfg_custom_places) > 0) { + parse_gtk_bookmarks (dpy, _fib_cfg_custom_places); + } + +#ifdef HAVE_MNTENT + if (read_mtab (dpy, "/proc/mounts") < 1) { + read_mtab (dpy, "/etc/mtab"); + } +#endif + + int parsed_bookmarks = 0; + if (!parsed_bookmarks && getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/.gtk-bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) { + strcpy (tmp, getenv ("XDG_CONFIG_HOME")); + strcat (tmp, "/gtk-3.0/bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (!parsed_bookmarks && getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/.config/gtk-3.0/bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (_fib_place_width > 0) { + _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX); + } + if (spacer > 0 && spacer < _placecnt -1) { + _placelist[ spacer ].flags |= 4; + } +} + +static uint8_t font_err = 0; +static int x_error_handler (Display *d, XErrorEvent *e) { + font_err = 1; + return 0; + + // unused + (void)d; (void)e; +} + +int x_fib_show (Display *dpy, Window parent, int x, int y, double scalefactor) { + if (_fib_win) { + XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime); + return -1; + } + + _status = 0; + _rv_open[0] = '\0'; + + Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); + _c_gray1.flags = DoRed | DoGreen | DoBlue; + _c_gray0.red = _c_gray0.green = _c_gray0.blue = 0x5000; // 95% hover prelight + _c_gray1.red = _c_gray1.green = _c_gray1.blue = 0x1100; // 93% window bg, scrollbar-fg + _c_gray2.red = _c_gray2.green = _c_gray2.blue = 0x1c00; // 83% button & list bg + _c_gray3.red = _c_gray3.green = _c_gray3.blue = 0x0a00; // 75% heading + scrollbar-bg + _c_gray4.red = _c_gray4.green = _c_gray4.blue = 0xd600; // 40% prelight text, sep lines + _c_gray5.red = _c_gray5.green = _c_gray5.blue = 0x3000; // 20% 3D border + + if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1; + + XSetWindowAttributes attr; + memset (&attr, 0, sizeof(XSetWindowAttributes)); + attr.border_pixel = _c_gray2.pixel; + + attr.event_mask = ExposureMask | KeyPressMask + | ButtonPressMask | ButtonReleaseMask + | ConfigureNotify | StructureNotifyMask + | PointerMotionMask | LeaveWindowMask; + + _fib_win = XCreateWindow ( + dpy, DefaultRootWindow (dpy), + x, y, _fib_width * scalefactor, _fib_height * scalefactor, + 1, CopyFromParent, InputOutput, CopyFromParent, + CWEventMask | CWBorderPixel, &attr); + + _scalefactor = scalefactor; + + if (!_fib_win) { return 1; } + + if (parent) + XSetTransientForHint (dpy, _fib_win, parent); + + XStoreName (dpy, _fib_win, "Select File"); + + Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True); + XSetWMProtocols (dpy, _fib_win, &wmDelete, 1); + + _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + const char dl[1] = {1}; + XSetDashes (dpy, _fib_gc, 0, dl, 1); + + int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler); + +#define _XTESTFONT(FN) \ + { \ + font_err = 0; \ + _fibfont = XLoadFont (dpy, FN); \ + XSetFont (dpy, _fib_gc, _fibfont); \ + XSync (dpy, False); \ + } + + font_err = 1; + if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT")); + if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font); + if (scalefactor >= 2.5) { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-18-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-20-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"); + } else if (scalefactor >= 2) { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-16-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-16-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-16-*-*-*-*-*-*-*"); + } else if (scalefactor >= 1.5) { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-14-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-14-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-15-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"); + } else { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*"); + } + if (font_err) _fibfont = None; + XSync (dpy, False); + XSetErrorHandler (handler); + + if (_fib_font_height == 0) { // 1st time only + query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL); + query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL); + if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) { + XFreeGC (dpy, _fib_gc); + XDestroyWindow (dpy, _fib_win); + _fib_win = 0; + return -1; + } + _fib_font_height += 3 * scalefactor; + _fib_font_ascent += 2 * scalefactor; + _fib_font_vsep = _fib_font_height + 2 * scalefactor; + } + + populate_places (dpy); + + strcpy (_btn_ok.text, "Open"); + strcpy (_btn_cancel.text, "Cancel"); + strcpy (_btn_filter.text, "List All Files"); + strcpy (_btn_places.text, "Show Places"); + strcpy (_btn_hidden.text, "Show Hidden"); + + _btn_ok.callback = &cb_open; + _btn_cancel.callback = &cb_cancel; + _btn_filter.callback = &cb_filter; + _btn_places.callback = &cb_places; + _btn_hidden.callback = &cb_hidden; + _btn_filter.flags |= 4; + _btn_places.flags |= 4; + _btn_hidden.flags |= 4; + + if (!_fib_filter_function) { + _btn_filter.flags |= 8; + } + + size_t i; + int btncnt = 0; + _btn_w = 0; + _btn_span = 0; + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + if (_btns[i]->flags & 8) { continue; } + query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL); + if (_btns[i]->flags & 4) { + _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor; + } else { + ++btncnt; + if (_btns[i]->tw > _btn_w) + _btn_w = _btns[i]->tw; + } + } + + _btn_w += (BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP) * scalefactor; + _btn_span += _btn_w * btncnt + DSEP * scalefactor * (i - 1) + (FAREAMRGR + FAREAMRGB) * scalefactor; + + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + if (_btns[i]->flags & 8) { continue; } + if (_btns[i]->flags & 4) { + _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor; + } else { + _btns[i]->xw = _btn_w; + } + } + + sync_button_states () ; + + _fib_height = _fib_font_vsep * 15.8 * (1.0 + (scalefactor - 1.0) / 2.0); + _fib_width = MAX (_btn_span, 480 * scalefactor); + + XResizeWindow (dpy, _fib_win, _fib_width, _fib_height); + + XTextProperty x_wname, x_iname; + XSizeHints hints; + XWMHints wmhints; + + hints.flags = PSize | PMinSize; + hints.min_width = _btn_span; + hints.min_height = 8 * _fib_font_vsep; + + char *w_name = & _fib_cfg_title[0]; + + wmhints.input = True; + wmhints.flags = InputHint; + if (XStringListToTextProperty (&w_name, 1, &x_wname) && + XStringListToTextProperty (&w_name, 1, &x_iname)) + { + XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL); + XFree (x_wname.value); + XFree (x_iname.value); + } + + XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel); + + _fib_mapped = 0; + XMapRaised (dpy, _fib_win); + + if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) { + fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL); + } + +#if 0 + XGrabPointer (dpy, _fib_win, True, + ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); + //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime); +#endif + _recentlock = 1; + return 0; +} + +void x_fib_close (Display *dpy) { + if (!_fib_win) return; + XFreeGC (dpy, _fib_gc); + XDestroyWindow (dpy, _fib_win); + _fib_win = 0; + free (_dirlist); + _dirlist = NULL; + free (_pathbtn); + _pathbtn = NULL; + if (_fibfont != None) XUnloadFont (dpy, _fibfont); + _fibfont = None; + free (_placelist); + _placelist = NULL; + _dircount = 0; + _pathparts = 0; + _placecnt = 0; + if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer); + _pixbuffer = None; + Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); + XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0); + _recentlock = 0; +} + +int x_fib_handle_events (Display *dpy, XEvent *event) { + if (!_fib_win) return 0; + if (_status) return 0; + if (event->xany.window != _fib_win) { + return 0; + } + + switch (event->type) { + case MapNotify: + _fib_mapped = 1; + break; + case UnmapNotify: + _fib_mapped = 0; + break; + case LeaveNotify: + fib_update_hover (dpy, 1, 0, 0); + break; + case ClientMessage: + if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) { + _status = -1; + } + break; + case ConfigureNotify: + if ( + (event->xconfigure.width > 1 && event->xconfigure.height > 1) + && + (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height) + ) + { + _fib_width = event->xconfigure.width; + _fib_height = event->xconfigure.height; + _fib_resized = 1; + } + break; + case Expose: + if (event->xexpose.count == 0) { + fib_expose (dpy, event->xany.window); + } + break; + case MotionNotify: + fib_motion (dpy, event->xmotion.x, event->xmotion.y); + if (event->xmotion.is_hint == NotifyHint) { + XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL); + } + break; + case ButtonPress: + fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); + break; + case ButtonRelease: + fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); + break; + case KeyRelease: + break; + case KeyPress: + { + KeySym key; + char buf[100]; + static XComposeStatus stat; + XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat); + switch (key) { + case XK_Escape: + _status = -1; + break; + case XK_Up: + if (_fsel > 0) { + fib_select (dpy, _fsel - 1); + } + break; + case XK_Down: + if (_fsel < _dircount -1) { + fib_select ( dpy, _fsel + 1); + } + break; + case XK_Page_Up: + if (_fsel > 0) { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 1) llen = 1; else --llen; + int fs = MAX (0, _fsel - llen); + fib_select ( dpy, fs); + } + break; + case XK_Page_Down: + if (_fsel < _dircount) { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 1) llen = 1; else --llen; + int fs = MIN (_dircount - 1, _fsel + llen); + fib_select ( dpy, fs); + } + break; + case XK_Left: + if (_pathparts > 1) { + int i = 0; + char path[1024] = "/"; + while (++i < _pathparts - 1) { + strcat (path, _pathbtn[i].name); + strcat (path, "/"); + } + char *sel = strdup (_pathbtn[_pathparts-1].name); + fib_opendir (dpy, path, sel); + free (sel); + } + break; + case XK_Right: + if (_fsel >= 0 && _fsel < _dircount) { + if (_dirlist[_fsel].flags & 4) { + cb_open (dpy); + } + } + break; + case XK_Return: + cb_open (dpy); + break; + default: + if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) { + int i; + for (i = 0; i < _dircount; ++i) { + int j = (_fsel + i + 1) % _dircount; + char kcmp = _dirlist[j].name[0]; + if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20; + if (kcmp == (char)key) { + fib_select ( dpy, j); + break; + } + } + } + break; + } + } + break; + } + + if (_status) { + x_fib_close (dpy); + } + return _status; +} + +int x_fib_status () { + return _status; +} + +int x_fib_configure (int k, const char *v) { + if (_fib_win) { return -1; } + switch (k) { + case 0: + if (strlen (v) >= sizeof(_cur_path) -1) return -2; + if (strlen (v) < 1) return -2; + if (v[0] != '/') return -2; + if (strstr (v, "//")) return -2; + strncpy (_cur_path, v, sizeof(_cur_path)); + break; + case 1: + if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2; + strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title)); + break; + case 2: + if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2; + strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font)); + break; + case 3: + if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2; + strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places)); + break; + default: + return -2; + } + return 0; +} + +int x_fib_cfg_buttons (int k, int v) { + if (_fib_win) { return -1; } + switch (k) { + case 1: + if (v < 0) { + _btn_hidden.flags |= 8; + } else { + _btn_hidden.flags &= ~8; + } + if (v == 1) { + _btn_hidden.flags |= 2; + _fib_hidden_fn = 1; + } else if (v == 0) { + _btn_hidden.flags &= 2; + _fib_hidden_fn = 0; + } + break; + case 2: + if (v < 0) { + _btn_places.flags |= 8; + } else { + _btn_places.flags &= ~8; + } + if (v == 1) { + _btn_places.flags |= 2; + _fib_show_places = 1; + } else if (v == 0) { + _btn_places.flags &= ~2; + _fib_show_places = 0; + } + break; + case 3: + // NB. filter button is automatically hidden + // IFF the filter-function is NULL. + if (v < 0) { + _btn_filter.flags |= 8; + } else { + _btn_filter.flags &= ~8; + } + if (v == 1) { + _btn_filter.flags &= ~2; // inverse - 'show all' = !filter + _fib_filter_fn = 1; + } else if (v == 0) { + _btn_filter.flags |= 2; + _fib_filter_fn = 0; + } + break; + default: + return -2; + } + return 0; +} + +int x_fib_cfg_filter_callback (int (*cb)(const char*)) { + if (_fib_win) { return -1; } + _fib_filter_function = cb; + return 0; +} + +char *x_fib_filename () { + if (_status > 0 && !_fib_win) + return strdup (_rv_open); + else + return NULL; +} +#endif // HAVE_X11 + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#endif + +/* example usage */ +#ifdef SOFD_TEST + +static int fib_filter_movie_filename (const char *name) { + if (!_fib_filter_fn) return 1; + const int l3 = strlen (name) - 3; + const int l4 = l3 - 1; + const int l5 = l4 - 1; + const int l6 = l5 - 1; + const int l9 = l6 - 3; + if ( + (l4 > 0 && ( + !strcasecmp (&name[l4], ".avi") + || !strcasecmp (&name[l4], ".mov") + || !strcasecmp (&name[l4], ".ogg") + || !strcasecmp (&name[l4], ".ogv") + || !strcasecmp (&name[l4], ".mpg") + || !strcasecmp (&name[l4], ".mov") + || !strcasecmp (&name[l4], ".mp4") + || !strcasecmp (&name[l4], ".mkv") + || !strcasecmp (&name[l4], ".vob") + || !strcasecmp (&name[l4], ".asf") + || !strcasecmp (&name[l4], ".avs") + || !strcasecmp (&name[l4], ".dts") + || !strcasecmp (&name[l4], ".flv") + || !strcasecmp (&name[l4], ".m4v") + )) || + (l5 > 0 && ( + !strcasecmp (&name[l5], ".h264") + || !strcasecmp (&name[l5], ".webm") + )) || + (l6 > 0 && ( + !strcasecmp (&name[l6], ".dirac") + )) || + (l9 > 0 && ( + !strcasecmp (&name[l9], ".matroska") + )) || + (l3 > 0 && ( + !strcasecmp (&name[l3], ".dv") + || !strcasecmp (&name[l3], ".ts") + )) + ) + { + return 1; + } + return 0; +} + +int main (int argc, char **argv) { + Display* dpy = XOpenDisplay (0); + if (!dpy) return -1; + + x_fib_cfg_filter_callback (fib_filter_movie_filename); + x_fib_configure (1, "Open Movie File"); + x_fib_load_recent ("/tmp/sofd.recent"); + x_fib_show (dpy, 0, 300, 300); + + while (1) { + XEvent event; + while (XPending (dpy) > 0) { + XNextEvent (dpy, &event); + if (x_fib_handle_events (dpy, &event)) { + if (x_fib_status () > 0) { + char *fn = x_fib_filename (); + printf ("OPEN '%s'\n", fn); + x_fib_add_recent (fn, time (NULL)); + free (fn); + } + } + } + if (x_fib_status ()) { + break; + } + usleep (80000); + } + x_fib_close (dpy); + + x_fib_save_recent ("/tmp/sofd.recent"); + + x_fib_free_recent (); + XCloseDisplay (dpy); + return 0; +} +#endif