DISTRHO Plugin Framework
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2483 lines
69KB

  1. /* libSOFD - Simple Open File Dialog [for X11 without toolkit]
  2. *
  3. * Copyright (C) 2014 Robin Gareus <robin@gareus.org>
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy
  6. * of this software and associated documentation files (the "Software"), to deal
  7. * in the Software without restriction, including without limitation the rights
  8. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. * copies of the Software, and to permit persons to whom the Software is
  10. * furnished to do so, subject to the following conditions:
  11. *
  12. * The above copyright notice and this permission notice shall be included in
  13. * all copies or substantial portions of the Software.
  14. *
  15. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. * THE SOFTWARE.
  22. */
  23. /* Test and example:
  24. * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11
  25. *
  26. * public API documentation and example code at the bottom of this file
  27. *
  28. * This small lib may one day include openGL rendering and
  29. * wayland window support, but not today. Today we celebrate
  30. * 30 years of X11.
  31. */
  32. #ifdef SOFD_TEST
  33. #define HAVE_X11
  34. #include "libsofd.h"
  35. #endif
  36. #include <stdio.h>
  37. #include <stdint.h>
  38. #include <string.h>
  39. #include <stdlib.h>
  40. #include <unistd.h>
  41. #include <libgen.h>
  42. #include <time.h>
  43. #include <sys/types.h>
  44. #include <sys/stat.h>
  45. #include <assert.h>
  46. #if defined(__clang__)
  47. # pragma clang diagnostic push
  48. # pragma clang diagnostic ignored "-Wnarrowing"
  49. #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
  50. # pragma GCC diagnostic push
  51. # pragma GCC diagnostic ignored "-Wnarrowing"
  52. #endif
  53. // shared 'recently used' implementation
  54. // sadly, xbel does not qualify as simple.
  55. // hence we use a simple format alike the
  56. // gtk-bookmark list (one file per line)
  57. #define MAX_RECENT_ENTRIES 24
  58. #define MAX_RECENT_AGE (15552000) // 180 days (in sec)
  59. typedef struct {
  60. char path[1024];
  61. time_t atime;
  62. } FibRecentFile;
  63. static FibRecentFile *_recentlist = NULL;
  64. static unsigned int _recentcnt = 0;
  65. static uint8_t _recentlock = 0;
  66. static int fib_isxdigit (const char x) {
  67. if (
  68. (x >= '0' && x <= '9')
  69. ||
  70. (x >= 'a' && x <= 'f')
  71. ||
  72. (x >= 'A' && x <= 'F')
  73. ) return 1;
  74. return 0;
  75. }
  76. static void decode_3986 (char *str) {
  77. int len = strlen (str);
  78. int idx = 0;
  79. while (idx + 2 < len) {
  80. char *in = &str[idx];
  81. if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) {
  82. char hexstr[3];
  83. hexstr[0] = in[1];
  84. hexstr[1] = in[2];
  85. hexstr[2] = 0;
  86. long hex = strtol (hexstr, NULL, 16);
  87. *in = hex;
  88. memmove (&str[idx+1], &str[idx + 3], len - idx - 2);
  89. len -= 2;
  90. }
  91. ++idx;
  92. }
  93. }
  94. static char *encode_3986 (const char *str) {
  95. size_t alloc, newlen;
  96. char *ns = NULL;
  97. unsigned char in;
  98. size_t i = 0;
  99. size_t length;
  100. if (!str) return strdup ("");
  101. alloc = strlen (str) + 1;
  102. newlen = alloc;
  103. ns = (char*) malloc (alloc);
  104. length = alloc;
  105. while (--length) {
  106. in = *str;
  107. switch (in) {
  108. case '0': case '1': case '2': case '3': case '4':
  109. case '5': case '6': case '7': case '8': case '9':
  110. case 'a': case 'b': case 'c': case 'd': case 'e':
  111. case 'f': case 'g': case 'h': case 'i': case 'j':
  112. case 'k': case 'l': case 'm': case 'n': case 'o':
  113. case 'p': case 'q': case 'r': case 's': case 't':
  114. case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
  115. case 'A': case 'B': case 'C': case 'D': case 'E':
  116. case 'F': case 'G': case 'H': case 'I': case 'J':
  117. case 'K': case 'L': case 'M': case 'N': case 'O':
  118. case 'P': case 'Q': case 'R': case 'S': case 'T':
  119. case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
  120. case '_': case '~': case '.': case '-':
  121. case '/': case ',': // XXX not in RFC3986
  122. ns[i++] = in;
  123. break;
  124. default:
  125. newlen += 2; /* this'll become a %XX */
  126. if (newlen > alloc) {
  127. alloc *= 2;
  128. ns = (char*) realloc (ns, alloc);
  129. }
  130. snprintf (&ns[i], 4, "%%%02X", in);
  131. i += 3;
  132. break;
  133. }
  134. ++str;
  135. }
  136. ns[i] = 0;
  137. return ns;
  138. }
  139. void x_fib_free_recent () {
  140. free (_recentlist);
  141. _recentlist = NULL;
  142. _recentcnt = 0;
  143. }
  144. static int cmp_recent (const void *p1, const void *p2) {
  145. FibRecentFile *a = (FibRecentFile*) p1;
  146. FibRecentFile *b = (FibRecentFile*) p2;
  147. if (a->atime == b->atime) return 0;
  148. return a->atime < b->atime;
  149. }
  150. int x_fib_add_recent (const char *path, time_t atime) {
  151. unsigned int i;
  152. struct stat fs;
  153. if (_recentlock) { return -1; }
  154. if (access (path, R_OK)) {
  155. return -1;
  156. }
  157. if (stat (path, &fs)) {
  158. return -1;
  159. }
  160. if (!S_ISREG (fs.st_mode)) {
  161. return -1;
  162. }
  163. if (atime == 0) atime = time (NULL);
  164. if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) {
  165. return -1;
  166. }
  167. for (i = 0; i < _recentcnt; ++i) {
  168. if (!strcmp (_recentlist[i].path, path)) {
  169. if (_recentlist[i].atime < atime) {
  170. _recentlist[i].atime = atime;
  171. }
  172. qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent);
  173. return _recentcnt;
  174. }
  175. }
  176. _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile));
  177. _recentlist[_recentcnt].atime = atime;
  178. strcpy (_recentlist[_recentcnt].path, path);
  179. qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent);
  180. if (_recentcnt >= MAX_RECENT_ENTRIES) {
  181. return (_recentcnt);
  182. }
  183. return (++_recentcnt);
  184. }
  185. #ifdef PATHSEP
  186. #undef PATHSEP
  187. #endif
  188. #ifdef PLATFORM_WINDOWS
  189. #define DIRSEP '\\'
  190. #else
  191. #define DIRSEP '/'
  192. #endif
  193. static void mkpath(const char *dir) {
  194. char tmp[1024];
  195. char *p;
  196. size_t len;
  197. snprintf (tmp, sizeof(tmp), "%s", dir);
  198. len = strlen(tmp);
  199. if (tmp[len - 1] == '/')
  200. tmp[len - 1] = 0;
  201. for (p = tmp + 1; *p; ++p)
  202. if(*p == DIRSEP) {
  203. *p = 0;
  204. #ifdef PLATFORM_WINDOWS
  205. mkdir(tmp);
  206. #else
  207. mkdir(tmp, 0755);
  208. #endif
  209. *p = DIRSEP;
  210. }
  211. #ifdef PLATFORM_WINDOWS
  212. mkdir(tmp);
  213. #else
  214. mkdir(tmp, 0755);
  215. #endif
  216. }
  217. int x_fib_save_recent (const char *fn) {
  218. if (_recentlock) { return -1; }
  219. if (!fn) { return -1; }
  220. if (_recentcnt < 1 || !_recentlist) { return -1; }
  221. unsigned int i;
  222. char *dn = strdup (fn);
  223. mkpath (dirname (dn));
  224. free (dn);
  225. FILE *rf = fopen (fn, "w");
  226. if (!rf) return -1;
  227. qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent);
  228. for (i = 0; i < _recentcnt; ++i) {
  229. char *n = encode_3986 (_recentlist[i].path);
  230. fprintf (rf, "%s %lu\n", n, _recentlist[i].atime);
  231. free (n);
  232. }
  233. fclose (rf);
  234. return 0;
  235. }
  236. int x_fib_load_recent (const char *fn) {
  237. char tmp[1024];
  238. if (_recentlock) { return -1; }
  239. if (!fn) { return -1; }
  240. x_fib_free_recent ();
  241. if (access (fn, R_OK)) {
  242. return -1;
  243. }
  244. FILE *rf = fopen (fn, "r");
  245. if (!rf) return -1;
  246. while (fgets (tmp, sizeof(tmp), rf)
  247. && strlen (tmp) > 1
  248. && strlen (tmp) < sizeof(tmp))
  249. {
  250. char *s;
  251. tmp[strlen (tmp) - 1] = '\0'; // strip newline
  252. if (!(s = strchr (tmp, ' '))) { // find name <> atime sep
  253. continue;
  254. }
  255. *s = '\0';
  256. time_t t = atol (++s);
  257. decode_3986 (tmp);
  258. x_fib_add_recent (tmp, t);
  259. }
  260. fclose (rf);
  261. return 0;
  262. }
  263. unsigned int x_fib_recent_count () {
  264. return _recentcnt;
  265. }
  266. const char *x_fib_recent_at (unsigned int i) {
  267. if (i >= _recentcnt)
  268. return NULL;
  269. return _recentlist[i].path;
  270. }
  271. #ifdef PLATFORM_WINDOWS
  272. #define PATHSEP "\\"
  273. #else
  274. #define PATHSEP "/"
  275. #endif
  276. const char *x_fib_recent_file(const char *appname) {
  277. static char recent_file[1024];
  278. assert(!strchr(appname, '/'));
  279. const char *xdg = getenv("XDG_DATA_HOME");
  280. if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) {
  281. sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname);
  282. return recent_file;
  283. }
  284. #ifdef PLATFORM_WINDOWS
  285. const char * homedrive = getenv("HOMEDRIVE");
  286. const char * homepath = getenv("HOMEPATH");
  287. if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) {
  288. sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname);
  289. return recent_file;
  290. }
  291. #elif defined PLATFORM_OSX
  292. const char *home = getenv("HOME");
  293. if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) {
  294. sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname);
  295. return recent_file;
  296. }
  297. #else
  298. const char *home = getenv("HOME");
  299. if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) {
  300. sprintf(recent_file, "%s/.local/share/%s/recent", home, appname);
  301. return recent_file;
  302. }
  303. #endif
  304. return NULL;
  305. }
  306. #ifdef HAVE_X11
  307. #include <dirent.h>
  308. #include <X11/Xlib.h>
  309. #include <X11/Xatom.h>
  310. #include <X11/Xutil.h>
  311. #include <X11/keysym.h>
  312. #include <X11/Xos.h>
  313. #if defined(__linux__) || defined(__linux)
  314. #define HAVE_MNTENT
  315. #include <mntent.h>
  316. #endif
  317. #ifndef MIN
  318. #define MIN(A,B) ( (A) < (B) ? (A) : (B) )
  319. #endif
  320. #ifndef MAX
  321. #define MAX(A,B) ( (A) < (B) ? (B) : (A) )
  322. #endif
  323. static Window _fib_win = 0;
  324. static GC _fib_gc = 0;
  325. static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5;
  326. static Font _fibfont = 0;
  327. static Pixmap _pixbuffer = None;
  328. static int _fib_width = 100;
  329. static int _fib_height = 100;
  330. static int _btn_w = 0;
  331. static int _btn_span = 0;
  332. static double _scalefactor = 1;
  333. static int _fib_font_height = 0;
  334. static int _fib_dir_indent = 0;
  335. static int _fib_spc_norm = 0;
  336. static int _fib_font_ascent = 0;
  337. static int _fib_font_vsep = 0;
  338. static int _fib_font_size_width = 0;
  339. static int _fib_font_time_width = 0;
  340. static int _fib_place_width = 0;
  341. static int _scrl_f = 0;
  342. static int _scrl_y0 = -1;
  343. static int _scrl_y1 = -1;
  344. static int _scrl_my = -1;
  345. static int _scrl_mf = -1;
  346. static int _view_p = -1;
  347. static int _fsel = -1;
  348. static int _hov_b = -1;
  349. static int _hov_f = -1;
  350. static int _hov_p = -1;
  351. static int _hov_h = -1;
  352. static int _hov_l = -1;
  353. static int _hov_s = -1;
  354. static int _sort = 0;
  355. static int _columns = 0;
  356. static int _fib_filter_fn = 1;
  357. static int _fib_hidden_fn = 0;
  358. static int _fib_show_places = 0;
  359. static uint8_t _fib_mapped = 0;
  360. static uint8_t _fib_resized = 0;
  361. static unsigned long _dblclk = 0;
  362. static int _status = -2;
  363. static char _rv_open[1024] = "";
  364. static char _fib_cfg_custom_places[1024] = "";
  365. static char _fib_cfg_custom_font[256] = "";
  366. static char _fib_cfg_title[128] = "xjadeo - Open Video File";
  367. typedef struct {
  368. char name[256];
  369. int x0;
  370. int xw;
  371. } FibPathButton;
  372. typedef struct {
  373. char name[256];
  374. char strtime[32];
  375. char strsize[32];
  376. int ssizew;
  377. off_t size;
  378. time_t mtime;
  379. uint8_t flags; // 2: selected, 4: isdir 8: recent-entry
  380. FibRecentFile *rfp;
  381. } FibFileEntry;
  382. typedef struct {
  383. char text[24];
  384. uint8_t flags; // 2: selected, 4: toggle, 8 disable
  385. int x0;
  386. int tw;
  387. int xw;
  388. void (*callback)(Display*);
  389. } FibButton;
  390. typedef struct {
  391. char name[256];
  392. char path[1024];
  393. uint8_t flags; // 1: hover, 2: selected, 4:add sep
  394. } FibPlace;
  395. static char _cur_path[1024] = "";
  396. static FibFileEntry *_dirlist = NULL;
  397. static FibPathButton *_pathbtn = NULL;
  398. static FibPlace *_placelist = NULL;
  399. static int _dircount = 0;
  400. static int _pathparts = 0;
  401. static int _placecnt = 0;
  402. static FibButton _btn_ok;
  403. static FibButton _btn_cancel;
  404. static FibButton _btn_filter;
  405. static FibButton _btn_places;
  406. static FibButton _btn_hidden;
  407. static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok};
  408. static int (*_fib_filter_function)(const char *filename);
  409. /* hardcoded layout */
  410. #define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list
  411. #define PSEP 4 // px; horiz space beween paths
  412. #define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column
  413. #define LISTTOP 2.7 //em; top of the file-browser list
  414. #define LISTBOT 4.75 //em; bottom of the file-browers list
  415. #define BTNBTMMARGIN 0.75 //em; height/margin of the button row
  416. #define BTNPADDING 2 // px - only used for open/cancel buttons
  417. #define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3)
  418. #define SCROLLBOXH 10 //px; arrow box top+bottom
  419. #define PLACESW _fib_place_width //px;
  420. #define PLACESWMAX (15 *_fib_spc_norm) //px;
  421. #define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent);
  422. #define FAREAMRGB 3 //px; base L+R margin
  423. #define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width)
  424. #define FAREAMRGL (_fib_show_places ? PLACESW / _scalefactor + FAREAMRGB : FAREAMRGB) //px; left margin of file-area
  425. #define TEXTSEP 4 //px;
  426. #define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP
  427. #define SORTBTNOFF -10 //px;
  428. #ifndef DBLCLKTME
  429. #define DBLCLKTME 200 //msec; double click time
  430. #endif
  431. #define DRAW_OUTLINE
  432. #define DOUBLE_BUFFER
  433. #define LIST_ENTRY_HOVER
  434. static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) {
  435. XCharStruct text_structure;
  436. int font_direction, font_ascent, font_descent;
  437. XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc));
  438. if (!fontinfo) { return -1; }
  439. XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure);
  440. if (w) *w = XTextWidth (fontinfo, txt, strlen (txt));
  441. if (h) *h = text_structure.ascent + text_structure.descent;
  442. if (a) *a = text_structure.ascent;
  443. if (d) *d = text_structure.descent;
  444. #ifndef DISTRHO_OS_HAIKU // FIXME
  445. XFreeFontInfo (NULL, fontinfo, 1);
  446. #endif
  447. return 0;
  448. }
  449. static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) {
  450. #ifdef DRAW_OUTLINE
  451. XSetForeground (dpy, gc, _c_gray5.pixel);
  452. XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h);
  453. XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h);
  454. XDrawLine (dpy, d, gc, x + 1, y, x + w, y);
  455. XDrawLine (dpy, d, gc, x, y + 1, x, y + h);
  456. #else
  457. const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy));
  458. XSetForeground (dpy, _fib_gc, blackColor);
  459. XDrawRectangle (dpy, d, gc, x, y, w, h);
  460. #endif
  461. }
  462. static void fib_expose (Display *dpy, Window realwin) {
  463. int i;
  464. XID win;
  465. if (!_fib_mapped) return;
  466. if (_fib_resized
  467. #ifdef DOUBLE_BUFFER
  468. || !_pixbuffer
  469. #endif
  470. )
  471. {
  472. #ifdef DOUBLE_BUFFER
  473. unsigned int w = 0, h = 0;
  474. if (_pixbuffer != None) {
  475. Window ignored_w;
  476. int ignored_i;
  477. unsigned int ignored_u;
  478. XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u);
  479. if (_fib_width != (int)w || _fib_height != (int)h) {
  480. XFreePixmap (dpy, _pixbuffer);
  481. _pixbuffer = None;
  482. }
  483. }
  484. if (_pixbuffer == None) {
  485. XWindowAttributes wa;
  486. XGetWindowAttributes (dpy, realwin, &wa);
  487. _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth);
  488. }
  489. #endif
  490. if (_pixbuffer != None) {
  491. XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
  492. XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height);
  493. } else {
  494. XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
  495. XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height);
  496. }
  497. _fib_resized = 0;
  498. }
  499. if (_pixbuffer == None) {
  500. win = realwin;
  501. } else {
  502. win = _pixbuffer;
  503. }
  504. // Top Row: dirs and up navigation
  505. int ppw = 0;
  506. int ppx = FAREAMRGB * _scalefactor;
  507. for (i = _pathparts - 1; i >= 0; --i) {
  508. ppw += _pathbtn[i].xw + PSEP * _scalefactor;
  509. if (ppw >= _fib_width - PSEP * _scalefactor - _pathbtn[0].xw - FAREAMRGB * _scalefactor) break; // XXX, first change is from "/" to "<", NOOP
  510. }
  511. ++i;
  512. // border-less "<" parent/up, IFF space is limited
  513. if (i > 0) {
  514. if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) {
  515. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  516. } else {
  517. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  518. }
  519. XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1);
  520. ppx += _pathbtn[0].xw + PSEP * _scalefactor;
  521. if (i == _pathparts) --i;
  522. }
  523. _view_p = i;
  524. while (i < _pathparts) {
  525. if (i == _hov_p) {
  526. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  527. } else {
  528. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  529. }
  530. XFillRectangle (dpy, win, _fib_gc,
  531. ppx + 1, PATHBTNTOP - _fib_font_ascent,
  532. _pathbtn[i].xw - 1, _fib_font_height);
  533. VDrawRectangle (dpy, win, _fib_gc,
  534. ppx, PATHBTNTOP - _fib_font_ascent,
  535. _pathbtn[i].xw, _fib_font_height);
  536. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  537. XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP,
  538. _pathbtn[i].name, strlen (_pathbtn[i].name));
  539. _pathbtn[i].x0 = ppx; // current position
  540. ppx += _pathbtn[i].xw + PSEP * _scalefactor;
  541. ++i;
  542. }
  543. // middle, scroll list of file names
  544. const int ltop = LISTTOP * _fib_font_vsep;
  545. const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  546. const int fsel_height = 4 * _scalefactor + llen * _fib_font_vsep;
  547. const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0);
  548. const int t_x = FAREATEXTL * _scalefactor;
  549. int t_s = FAREATEXTL * _scalefactor + fsel_width;
  550. int t_t = FAREATEXTL * _scalefactor + fsel_width;
  551. // check which colums can be visible
  552. // depending on available width of window.
  553. _columns = 0;
  554. if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) {
  555. _columns |= 2;
  556. t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * _scalefactor;
  557. }
  558. if (fsel_width > FILECOLUMN + _fib_font_size_width) {
  559. _columns |= 1;
  560. t_t = t_s - _fib_font_size_width - TEXTSEP * _scalefactor;
  561. }
  562. int fstop = _scrl_f; // first entry in scroll position
  563. const int ttop = ltop - _fib_font_height + _fib_font_ascent;
  564. if (fstop > 0 && fstop + llen > _dircount) {
  565. fstop = MAX (0, _dircount - llen);
  566. _scrl_f = fstop;
  567. }
  568. // list header
  569. XSetForeground (dpy, _fib_gc, _c_gray3.pixel);
  570. XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep);
  571. // draw background of file list
  572. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  573. XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop, fsel_width, fsel_height);
  574. #ifdef DRAW_OUTLINE
  575. VDrawRectangle (dpy, win, _fib_gc,
  576. FAREAMRGL * _scalefactor,
  577. ltop - _fib_font_vsep - 1,
  578. _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor,
  579. fsel_height + _fib_font_vsep + 1);
  580. #endif
  581. switch (_hov_h) {
  582. case 1:
  583. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  584. XFillRectangle (dpy, win, _fib_gc,
  585. t_x + _fib_dir_indent - (TEXTSEP - 1) * _scalefactor,
  586. ltop - _fib_font_vsep,
  587. t_t - t_x - _fib_dir_indent - 1 * _scalefactor,
  588. _fib_font_vsep);
  589. break;
  590. case 2:
  591. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  592. XFillRectangle (dpy, win, _fib_gc,
  593. t_t - (TEXTSEP - 1) * _scalefactor,
  594. ltop - _fib_font_vsep,
  595. _fib_font_size_width + (TEXTSEP - 1) * _scalefactor,
  596. _fib_font_vsep);
  597. break;
  598. case 3:
  599. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  600. XFillRectangle (dpy, win, _fib_gc,
  601. t_s - (TEXTSEP - 1) * _scalefactor,
  602. ltop - _fib_font_vsep,
  603. TEXTSEP * 2 * _scalefactor + _fib_font_time_width - 1 * _scalefactor,
  604. _fib_font_vsep);
  605. break;
  606. default:
  607. break;
  608. }
  609. // column headings and sort order
  610. int arp = MAX (2 * _scalefactor, _fib_font_height / 5); // arrow scale
  611. const int trioff = _fib_font_height - _fib_font_ascent - arp + 1 * _scalefactor;
  612. XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1 * _scalefactor}, {-arp - arp, 0}, {arp, arp + arp + 1 * _scalefactor}};
  613. if (_sort & 1) {
  614. ptri[0].y = ttop -arp - arp - 1 * _scalefactor;
  615. ptri[1].y *= -1;
  616. ptri[3].y *= -1;
  617. }
  618. switch (_sort) {
  619. case 0:
  620. case 1:
  621. ptri[0].x = t_t + (SORTBTNOFF + 2) * _scalefactor - arp;
  622. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  623. XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious);
  624. XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious);
  625. break;
  626. case 2:
  627. case 3:
  628. if (_columns & 1) {
  629. ptri[0].x = t_s + (SORTBTNOFF + 2) * _scalefactor - arp;
  630. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  631. XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious);
  632. XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious);
  633. }
  634. break;
  635. case 4:
  636. case 5:
  637. if (_columns & 2) {
  638. ptri[0].x = FAREATEXTL * _scalefactor + fsel_width + (SORTBTNOFF + 2) * _scalefactor - arp;
  639. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  640. XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious);
  641. XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious);
  642. }
  643. break;
  644. }
  645. #if 0 // bottom header bottom border
  646. XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
  647. XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter);
  648. XDrawLine (dpy, win, _fib_gc,
  649. FAREAMRGL + 1, ltop,
  650. FAREAMRGL + fsel_width, ltop);
  651. XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter);
  652. #endif
  653. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  654. XDrawLine (dpy, win, _fib_gc,
  655. t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor,
  656. t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor);
  657. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  658. XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4);
  659. if (_columns & 1) {
  660. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  661. XDrawLine (dpy, win, _fib_gc,
  662. t_t - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor,
  663. t_t - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor);
  664. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  665. XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4);
  666. }
  667. if (_columns & 2) {
  668. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  669. XDrawLine (dpy, win, _fib_gc,
  670. t_s - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor,
  671. t_s - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor);
  672. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  673. if (_pathparts > 0)
  674. XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13);
  675. else
  676. XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9);
  677. }
  678. // scrollbar sep
  679. if (llen < _dircount) {
  680. const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor;
  681. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  682. XDrawLine (dpy, win, _fib_gc,
  683. sx0 - 1, ltop - _fib_font_vsep,
  684. #ifdef DRAW_OUTLINE
  685. sx0 - 1, ltop + fsel_height
  686. #else
  687. sx0 - 1, ltop - 1
  688. #endif
  689. );
  690. }
  691. // clip area for file-name
  692. XRectangle clp = {(FAREAMRGL + 1) * _scalefactor, ltop,
  693. t_t - (FAREAMRGL + TEXTSEP * 2 + 1) * _scalefactor, fsel_height};
  694. // list files in view
  695. for (i = 0; i < llen; ++i) {
  696. const int j = i + fstop;
  697. if (j >= _dircount) break;
  698. const int t_y = ltop + (i+1) * _fib_font_vsep - 4;
  699. if (_dirlist[j].flags & 2) {
  700. XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
  701. XFillRectangle (dpy, win, _fib_gc,
  702. FAREAMRGL * _scalefactor, t_y - _fib_font_ascent, fsel_width, _fib_font_height);
  703. }
  704. /*
  705. if (_hov_f == j && !(_dirlist[j].flags & 2)) {
  706. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  707. }
  708. */
  709. if (_dirlist[j].flags & 4) {
  710. XSetForeground (dpy, _fib_gc, (_dirlist[j].flags & 2) ? _c_gray3.pixel : _c_gray5.pixel);
  711. XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1);
  712. }
  713. XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted);
  714. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  715. XDrawString (dpy, win, _fib_gc,
  716. t_x + _fib_dir_indent, t_y,
  717. _dirlist[j].name, strlen (_dirlist[j].name));
  718. XSetClipMask (dpy, _fib_gc, None);
  719. if (_columns & 1) // right-aligned 'size'
  720. XDrawString (dpy, win, _fib_gc,
  721. t_s - (TEXTSEP + 2) * _scalefactor - _dirlist[j].ssizew, t_y,
  722. _dirlist[j].strsize, strlen (_dirlist[j].strsize));
  723. if (_columns & 2)
  724. XDrawString (dpy, win, _fib_gc,
  725. t_s, t_y,
  726. _dirlist[j].strtime, strlen (_dirlist[j].strtime));
  727. }
  728. // scrollbar
  729. if (llen < _dircount) {
  730. float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor) / (float) _dircount;
  731. sl = MAX ((8. * _scalefactor / llen), sl); // 8px min height of scroller
  732. const int sy1 = llen * sl;
  733. const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor - sy1) / (float)(_dircount - llen);
  734. const int sy0 = fstop * mx;
  735. const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor;
  736. const int stop = ltop - _fib_font_vsep;
  737. _scrl_y0 = stop + SCROLLBOXH * _scalefactor + sy0;
  738. _scrl_y1 = _scrl_y0 + sy1;
  739. assert (fstop + llen <= _dircount);
  740. // scroll-bar background
  741. XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
  742. XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW * _scalefactor, fsel_height + _fib_font_vsep);
  743. // scroller
  744. if (_hov_s == 0 || _hov_s == 1 || _hov_s == 2) {
  745. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  746. } else {
  747. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  748. }
  749. XFillRectangle (dpy, win, _fib_gc, sx0 + 1 * _scalefactor, stop + SCROLLBOXH * _scalefactor + sy0, (SCROLLBARW - 2) * _scalefactor, sy1);
  750. int scrw = (SCROLLBARW -3) / 2 * _scalefactor;
  751. // arrows top and bottom
  752. if (_hov_s == 1) {
  753. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  754. } else {
  755. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  756. }
  757. XPoint ptst[4] = { {sx0 + 1 * _scalefactor, stop + 8 * _scalefactor}, {scrw, -7 * _scalefactor}, {scrw, 7 * _scalefactor}, {-2 * scrw, 0}};
  758. XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious);
  759. XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious);
  760. if (_hov_s == 2) {
  761. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  762. } else {
  763. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  764. }
  765. XPoint ptsb[4] = { {sx0 + 1 * _scalefactor, ltop + fsel_height - 9 * _scalefactor}, {2*scrw, 0}, {-scrw, 7 * _scalefactor}, {-scrw, -7 * _scalefactor}};
  766. XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious);
  767. XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious);
  768. } else {
  769. _scrl_y0 = _scrl_y1 = -1;
  770. }
  771. if (_fib_show_places) {
  772. assert (_placecnt > 0);
  773. // heading
  774. XSetForeground (dpy, _fib_gc, _c_gray3.pixel);
  775. XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep);
  776. // body
  777. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  778. XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop, PLACESW - TEXTSEP * _scalefactor, fsel_height);
  779. #ifdef DRAW_OUTLINE
  780. VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP * _scalefactor, fsel_height + _fib_font_vsep + 1);
  781. #endif
  782. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  783. XDrawString (dpy, win, _fib_gc, (FAREAMRGB + TEXTSEP) * _scalefactor, ttop, "Places", 6);
  784. XRectangle pclip = {(FAREAMRGB + 1) * _scalefactor, ltop, PLACESW - (TEXTSEP + 1) * _scalefactor, fsel_height};
  785. XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted);
  786. const int plx = (FAREAMRGB + TEXTSEP) * _scalefactor;
  787. for (i = 0; i < llen && i < _placecnt; ++i) {
  788. const int ply = ltop + (i+1) * _fib_font_vsep - 4 * _scalefactor;
  789. if (i == _hov_l) {
  790. XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
  791. XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop + i * _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep);
  792. }
  793. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  794. XDrawString (dpy, win, _fib_gc,
  795. plx, ply,
  796. _placelist[i].name, strlen (_placelist[i].name));
  797. if (_placelist[i].flags & 4) {
  798. XSetForeground (dpy, _fib_gc, _c_gray5.pixel);
  799. const int plly = ply - _fib_font_ascent + _fib_font_height + 1 * _scalefactor;
  800. const int pllx0 = FAREAMRGB * _scalefactor;
  801. const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * _scalefactor;
  802. XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly);
  803. }
  804. }
  805. XSetClipMask (dpy, _fib_gc, None);
  806. if (_placecnt > llen) {
  807. const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent;
  808. const int pllx0 = FAREAMRGB * _scalefactor + (PLACESW - TEXTSEP * _scalefactor) * .75;
  809. const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * 2 * _scalefactor;
  810. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  811. XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineOnOffDash, CapButt, JoinMiter);
  812. XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly);
  813. XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineSolid, CapButt, JoinMiter);
  814. }
  815. }
  816. // Bottom Buttons
  817. const int numb = sizeof(_btns) / sizeof(FibButton*);
  818. int xtra = _fib_width - _btn_span;
  819. const int cbox = _fib_font_ascent - 2 * _scalefactor;
  820. const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING * _scalefactor;
  821. const int cblw = cbox > 20 * _scalefactor
  822. ? 5 * _scalefactor
  823. : (cbox > 9 * _scalefactor ? 3 : 1) * _scalefactor;
  824. int bx = FAREAMRGB * _scalefactor;
  825. for (i = 0; i < numb; ++i) {
  826. if (_btns[i]->flags & 8) { continue; }
  827. if (_btns[i]->flags & 4) {
  828. // checkbutton
  829. const int cby0 = bbase - cbox + (1 + BTNPADDING) * _scalefactor;
  830. if (i == _hov_b) {
  831. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  832. } else {
  833. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  834. }
  835. XDrawRectangle (dpy, win, _fib_gc,
  836. bx, cby0 - 1, cbox + 1, cbox + 1);
  837. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  838. XDrawString (dpy, win, _fib_gc, BTNPADDING * _scalefactor + bx + _fib_font_ascent, bbase + (BTNPADDING + 1) * _scalefactor,
  839. _btns[i]->text, strlen (_btns[i]->text));
  840. if (i == _hov_b) {
  841. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  842. } else {
  843. /* if (_btns[i]->flags & 2) {
  844. XSetForeground (dpy, _fib_gc, _c_gray1.pixel);
  845. } else */ {
  846. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  847. }
  848. }
  849. XFillRectangle (dpy, win, _fib_gc,
  850. bx+1, cby0, cbox, cbox);
  851. if (_btns[i]->flags & 2) {
  852. XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter);
  853. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  854. XDrawLine (dpy, win, _fib_gc,
  855. bx + 2, cby0 + 1,
  856. bx + cbox - 1, cby0 + cbox - 2);
  857. XDrawLine (dpy, win, _fib_gc,
  858. bx + cbox - 1, cby0 + 1,
  859. bx + 2, cby0 + cbox - 2);
  860. XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter);
  861. }
  862. } else {
  863. if (xtra > 0) {
  864. bx += xtra;
  865. xtra = 0;
  866. }
  867. // pushbutton
  868. uint8_t can_hover = 1; // special case
  869. if (_btns[i] == &_btn_ok) {
  870. if (_fsel < 0 || _fsel >= _dircount) {
  871. can_hover = 0;
  872. }
  873. }
  874. if (can_hover && i == _hov_b) {
  875. XSetForeground (dpy, _fib_gc, _c_gray0.pixel);
  876. } else {
  877. XSetForeground (dpy, _fib_gc, _c_gray2.pixel);
  878. }
  879. XFillRectangle (dpy, win, _fib_gc,
  880. bx + 1, bbase - _fib_font_ascent,
  881. _btn_w - 1, _fib_font_height + BTNPADDING * 2 * _scalefactor);
  882. VDrawRectangle (dpy, win, _fib_gc,
  883. bx, bbase - _fib_font_ascent,
  884. _btn_w, _fib_font_height + BTNPADDING * 2 * _scalefactor);
  885. XSetForeground (dpy, _fib_gc, _c_gray4.pixel);
  886. XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING * _scalefactor,
  887. _btns[i]->text, strlen (_btns[i]->text));
  888. }
  889. _btns[i]->x0 = bx;
  890. bx += _btns[i]->xw + DSEP * _scalefactor;
  891. }
  892. if (_pixbuffer != None) {
  893. XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0);
  894. }
  895. XFlush (dpy);
  896. }
  897. static void fib_reset () {
  898. _hov_p = _hov_f = _hov_h = _hov_l = -1;
  899. _scrl_f = 0;
  900. _fib_resized = 1;
  901. }
  902. static int cmp_n_up (const void *p1, const void *p2) {
  903. FibFileEntry *a = (FibFileEntry*) p1;
  904. FibFileEntry *b = (FibFileEntry*) p2;
  905. if ((a->flags & 4) && !(b->flags & 4)) return -1;
  906. if (!(a->flags & 4) && (b->flags & 4)) return 1;
  907. return strcmp (a->name, b->name);
  908. }
  909. static int cmp_n_down (const void *p1, const void *p2) {
  910. FibFileEntry *a = (FibFileEntry*) p1;
  911. FibFileEntry *b = (FibFileEntry*) p2;
  912. if ((a->flags & 4) && !(b->flags & 4)) return -1;
  913. if (!(a->flags & 4) && (b->flags & 4)) return 1;
  914. return strcmp (b->name, a->name);
  915. }
  916. static int cmp_t_up (const void *p1, const void *p2) {
  917. FibFileEntry *a = (FibFileEntry*) p1;
  918. FibFileEntry *b = (FibFileEntry*) p2;
  919. if ((a->flags & 4) && !(b->flags & 4)) return -1;
  920. if (!(a->flags & 4) && (b->flags & 4)) return 1;
  921. if (a->mtime == b->mtime) return 0;
  922. return a->mtime > b->mtime ? -1 : 1;
  923. }
  924. static int cmp_t_down (const void *p1, const void *p2) {
  925. FibFileEntry *a = (FibFileEntry*) p1;
  926. FibFileEntry *b = (FibFileEntry*) p2;
  927. if ((a->flags & 4) && !(b->flags & 4)) return -1;
  928. if (!(a->flags & 4) && (b->flags & 4)) return 1;
  929. if (a->mtime == b->mtime) return 0;
  930. return a->mtime > b->mtime ? 1 : -1;
  931. }
  932. static int cmp_s_up (const void *p1, const void *p2) {
  933. FibFileEntry *a = (FibFileEntry*) p1;
  934. FibFileEntry *b = (FibFileEntry*) p2;
  935. if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order
  936. if ((a->flags & 4) && !(b->flags & 4)) return -1;
  937. if (!(a->flags & 4) && (b->flags & 4)) return 1;
  938. if (a->size == b->size) return 0;
  939. return a->size > b->size ? -1 : 1;
  940. }
  941. static int cmp_s_down (const void *p1, const void *p2) {
  942. FibFileEntry *a = (FibFileEntry*) p1;
  943. FibFileEntry *b = (FibFileEntry*) p2;
  944. if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order
  945. if ((a->flags & 4) && !(b->flags & 4)) return -1;
  946. if (!(a->flags & 4) && (b->flags & 4)) return 1;
  947. if (a->size == b->size) return 0;
  948. return a->size > b->size ? 1 : -1;
  949. }
  950. static void fmt_size (Display *dpy, FibFileEntry *f) {
  951. if (f->size > 10995116277760) {
  952. sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f);
  953. }
  954. if (f->size > 1099511627776) {
  955. sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f);
  956. }
  957. else if (f->size > 10737418240) {
  958. sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f);
  959. }
  960. else if (f->size > 1073741824) {
  961. sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f);
  962. }
  963. else if (f->size > 10485760) {
  964. sprintf (f->strsize, "%.0f MB", f->size / 1048576.f);
  965. }
  966. else if (f->size > 1048576) {
  967. sprintf (f->strsize, "%.1f MB", f->size / 1048576.f);
  968. }
  969. else if (f->size > 10240) {
  970. sprintf (f->strsize, "%.0f KB", f->size / 1024.f);
  971. }
  972. else if (f->size >= 1000) {
  973. sprintf (f->strsize, "%.1f KB", f->size / 1024.f);
  974. }
  975. else {
  976. sprintf (f->strsize, "%.0f B", f->size / 1.f);
  977. }
  978. int sw = 0;
  979. query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL);
  980. if (sw > _fib_font_size_width) {
  981. _fib_font_size_width = sw;
  982. }
  983. f->ssizew = sw;
  984. }
  985. static void fmt_time (Display *dpy, FibFileEntry *f) {
  986. struct tm *tmp;
  987. tmp = localtime (&f->mtime);
  988. if (!tmp) {
  989. return;
  990. }
  991. strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp);
  992. int tw = 0;
  993. query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL);
  994. if (tw > _fib_font_time_width) {
  995. _fib_font_time_width = tw;
  996. }
  997. }
  998. static void fib_resort (const char * sel) {
  999. if (_dircount < 1) { return; }
  1000. int (*sortfn)(const void *p1, const void *p2);
  1001. switch (_sort) {
  1002. case 1: sortfn = &cmp_n_down; break;
  1003. case 2: sortfn = &cmp_s_down; break;
  1004. case 3: sortfn = &cmp_s_up; break;
  1005. case 4: sortfn = &cmp_t_down; break;
  1006. case 5: sortfn = &cmp_t_up; break;
  1007. default:
  1008. sortfn = &cmp_n_up;
  1009. break;
  1010. }
  1011. qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn);
  1012. int i;
  1013. for (i = 0; i < _dircount && sel; ++i) {
  1014. if (!strcmp (_dirlist[i].name, sel)) {
  1015. _fsel = i;
  1016. break;
  1017. }
  1018. }
  1019. }
  1020. static void fib_select (Display *dpy, int item) {
  1021. if (_fsel >= 0) {
  1022. _dirlist[_fsel].flags &= ~2;
  1023. }
  1024. _fsel = item;
  1025. if (_fsel >= 0 && _fsel < _dircount) {
  1026. _dirlist[_fsel].flags |= 2;
  1027. const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  1028. if (_fsel < _scrl_f) {
  1029. _scrl_f = _fsel;
  1030. }
  1031. else if (_fsel >= _scrl_f + llen) {
  1032. _scrl_f = 1 + _fsel - llen;
  1033. }
  1034. } else {
  1035. _fsel = -1;
  1036. }
  1037. fib_expose (dpy, _fib_win);
  1038. }
  1039. static inline int fib_filter (const char *name) {
  1040. if (_fib_filter_function) {
  1041. return _fib_filter_function (name);
  1042. } else {
  1043. return 1;
  1044. }
  1045. }
  1046. static void fib_pre_opendir (Display *dpy) {
  1047. if (_dirlist) free (_dirlist);
  1048. if (_pathbtn) free (_pathbtn);
  1049. _dirlist = NULL;
  1050. _pathbtn = NULL;
  1051. _dircount = 0;
  1052. _pathparts = 0;
  1053. query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL);
  1054. fib_reset ();
  1055. _fsel = -1;
  1056. }
  1057. static void fib_post_opendir (Display *dpy, const char *sel) {
  1058. if (_dircount > 0)
  1059. _fsel = 0; // select first
  1060. else
  1061. _fsel = -1;
  1062. fib_resort (sel);
  1063. if (_dircount > 0 && _fsel >= 0) {
  1064. fib_select (dpy, _fsel);
  1065. } else {
  1066. fib_expose (dpy, _fib_win);
  1067. }
  1068. }
  1069. static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) {
  1070. char tp[1024];
  1071. struct stat fs;
  1072. if (!_fib_hidden_fn && name[0] == '.') return -1;
  1073. if (!strcmp (name, ".")) return -1;
  1074. if (!strcmp (name, "..")) return -1;
  1075. strcpy (tp, path);
  1076. strcat (tp, name);
  1077. if (access (tp, R_OK)) {
  1078. return -1;
  1079. }
  1080. if (stat (tp, &fs)) {
  1081. return -1;
  1082. }
  1083. assert (i < _dircount); // could happen if dir changes while we're reading.
  1084. if (i >= _dircount) return -1;
  1085. if (S_ISDIR (fs.st_mode)) {
  1086. _dirlist[i].flags |= 4;
  1087. }
  1088. else if (S_ISREG (fs.st_mode)) {
  1089. if (!fib_filter (name)) return -1;
  1090. }
  1091. #if 0 // only needed with lstat()
  1092. else if (S_ISLNK (fs.st_mode)) {
  1093. if (!fib_filter (name)) return -1;
  1094. }
  1095. #endif
  1096. else {
  1097. return -1;
  1098. }
  1099. strcpy (_dirlist[i].name, name);
  1100. _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime;
  1101. _dirlist[i].size = fs.st_size;
  1102. if (!(_dirlist[i].flags & 4))
  1103. fmt_size (dpy, &_dirlist[i]);
  1104. fmt_time (dpy, &_dirlist[i]);
  1105. return 0;
  1106. }
  1107. static int fib_openrecent (Display *dpy, const char *sel) {
  1108. int i;
  1109. unsigned int j;
  1110. assert (_recentcnt > 0);
  1111. fib_pre_opendir (dpy);
  1112. query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL);
  1113. _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry));
  1114. _dircount = _recentcnt;
  1115. for (j = 0, i = 0; j < _recentcnt; ++j) {
  1116. char base[1024];
  1117. char *s = strrchr (_recentlist[j].path, '/');
  1118. if (!s || !*++s) continue;
  1119. size_t len = (s - _recentlist[j].path);
  1120. strncpy (base, _recentlist[j].path, len);
  1121. base[len] = '\0';
  1122. if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) {
  1123. _dirlist[i].rfp = &_recentlist[j];
  1124. _dirlist[i].flags |= 8;
  1125. ++i;
  1126. }
  1127. }
  1128. _dircount = i;
  1129. fib_post_opendir (dpy, sel);
  1130. return _dircount;
  1131. }
  1132. static int fib_opendir (Display *dpy, const char* path, const char *sel) {
  1133. char *t0, *t1;
  1134. int i;
  1135. assert (path);
  1136. if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this
  1137. strcpy (_cur_path, "");
  1138. return fib_openrecent (dpy, sel);
  1139. }
  1140. assert (strlen (path) < sizeof(_cur_path) -1);
  1141. assert (strlen (path) > 0);
  1142. assert (strstr (path, "//") == NULL);
  1143. assert (path[0] == '/');
  1144. fib_pre_opendir (dpy);
  1145. query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL);
  1146. DIR *dir = opendir (path);
  1147. if (!dir) {
  1148. strcpy (_cur_path, "/");
  1149. } else {
  1150. int i;
  1151. struct dirent *de;
  1152. if (path != _cur_path)
  1153. strcpy (_cur_path, path);
  1154. if (_cur_path[strlen (_cur_path) -1] != '/')
  1155. strcat (_cur_path, "/");
  1156. while ((de = readdir (dir))) {
  1157. if (!_fib_hidden_fn && de->d_name[0] == '.') continue;
  1158. ++_dircount;
  1159. }
  1160. if (_dircount > 0)
  1161. _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry));
  1162. rewinddir (dir);
  1163. i = 0;
  1164. while ((de = readdir (dir))) {
  1165. if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0))
  1166. ++i;
  1167. }
  1168. _dircount = i;
  1169. closedir (dir);
  1170. }
  1171. t0 = _cur_path;
  1172. while (*t0 && (t0 = strchr (t0, '/'))) {
  1173. ++_pathparts;
  1174. ++t0;
  1175. }
  1176. assert (_pathparts > 0);
  1177. _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton));
  1178. t1 = _cur_path;
  1179. i = 0;
  1180. while (*t1 && (t0 = strchr (t1, '/'))) {
  1181. if (i == 0) {
  1182. strcpy (_pathbtn[i].name, "/");
  1183. } else {
  1184. *t0 = 0;
  1185. strcpy (_pathbtn[i].name, t1);
  1186. }
  1187. query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL);
  1188. _pathbtn[i].xw += BTNPADDING + BTNPADDING;
  1189. *t0 = '/';
  1190. t1 = t0 + 1;
  1191. ++i;
  1192. }
  1193. fib_post_opendir (dpy, sel);
  1194. return _dircount;
  1195. }
  1196. static int fib_open (Display *dpy, int item) {
  1197. char tp[1024];
  1198. if (_dirlist[item].flags & 8) {
  1199. assert (_dirlist[item].rfp);
  1200. strcpy (_rv_open, _dirlist[item].rfp->path);
  1201. _status = 1;
  1202. return 0;
  1203. }
  1204. strcpy (tp, _cur_path);
  1205. strcat (tp, _dirlist[item].name);
  1206. if (_dirlist[item].flags & 4) {
  1207. fib_opendir (dpy, tp, NULL);
  1208. return 0;
  1209. } else {
  1210. _status = 1;
  1211. strcpy (_rv_open, tp);
  1212. }
  1213. return 0;
  1214. }
  1215. static void cb_cancel (Display *dpy) {
  1216. _status = -1;
  1217. // unused
  1218. return; (void)dpy;
  1219. }
  1220. static void cb_open (Display *dpy) {
  1221. if (_fsel >= 0 && _fsel < _dircount) {
  1222. fib_open (dpy, _fsel);
  1223. }
  1224. }
  1225. static void sync_button_states () {
  1226. if (_fib_show_places)
  1227. _btn_places.flags |= 2;
  1228. else
  1229. _btn_places.flags &= ~2;
  1230. if (_fib_filter_fn) // inverse -> show all
  1231. _btn_filter.flags &= ~2;
  1232. else
  1233. _btn_filter.flags |= 2;
  1234. if (_fib_hidden_fn)
  1235. _btn_hidden.flags |= 2;
  1236. else
  1237. _btn_hidden.flags &= ~2;
  1238. }
  1239. static void cb_places (Display *dpy) {
  1240. _fib_show_places = ! _fib_show_places;
  1241. if (_placecnt < 1)
  1242. _fib_show_places = 0;
  1243. sync_button_states ();
  1244. _fib_resized = 1;
  1245. fib_expose (dpy, _fib_win);
  1246. }
  1247. static void cb_filter (Display *dpy) {
  1248. _fib_filter_fn = ! _fib_filter_fn;
  1249. sync_button_states ();
  1250. char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL;
  1251. fib_opendir (dpy, _cur_path, sel);
  1252. free (sel);
  1253. }
  1254. static void cb_hidden (Display *dpy) {
  1255. _fib_hidden_fn = ! _fib_hidden_fn;
  1256. sync_button_states ();
  1257. char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL;
  1258. fib_opendir (dpy, _cur_path, sel);
  1259. free (sel);
  1260. }
  1261. static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) {
  1262. const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING * _scalefactor;
  1263. const int bbot = btop + _fib_font_height + BTNPADDING * 2 * _scalefactor;
  1264. const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  1265. const int ltop = LISTTOP * _fib_font_vsep;
  1266. const int fbot = ltop + 4 * _scalefactor + llen * _fib_font_vsep;
  1267. const int ptop = PATHBTNTOP - _fib_font_ascent;
  1268. assert (it);
  1269. // paths at top
  1270. if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) {
  1271. int i = _view_p;
  1272. *it = -1;
  1273. if (i > 0) { // special case '<'
  1274. if (x > FAREAMRGB * _scalefactor && x <= FAREAMRGB * _scalefactor + _pathbtn[0].xw) {
  1275. *it = _view_p - 1;
  1276. i = _pathparts;
  1277. }
  1278. }
  1279. while (i < _pathparts) {
  1280. if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) {
  1281. *it = i;
  1282. break;
  1283. }
  1284. ++i;
  1285. }
  1286. assert (*it < _pathparts);
  1287. if (*it >= 0) return 1;
  1288. else return 0;
  1289. }
  1290. // buttons at bottom
  1291. if (y > btop && y < bbot) {
  1292. size_t i;
  1293. *it = -1;
  1294. for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) {
  1295. const int bx = _btns[i]->x0;
  1296. if (_btns[i]->flags & 8) { continue; }
  1297. if (x > bx && x < bx + _btns[i]->xw) {
  1298. *it = i;
  1299. }
  1300. }
  1301. if (*it >= 0) return 3;
  1302. else return 0;
  1303. }
  1304. // main file area
  1305. if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL * _scalefactor && x < _fib_width - FAREAMRGR * _scalefactor) {
  1306. // scrollbar
  1307. if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) * _scalefactor && x <= _fib_width - FAREAMRGR * _scalefactor) {
  1308. if (y >= _scrl_y0 && y < _scrl_y1) {
  1309. *it = 0;
  1310. } else if (y >= _scrl_y1) {
  1311. *it = 2;
  1312. } else {
  1313. *it = 1;
  1314. }
  1315. return 4;
  1316. }
  1317. // file-list
  1318. else if (y >= ltop) {
  1319. const int item = (y - ltop) / _fib_font_vsep + _scrl_f;
  1320. *it = -1;
  1321. if (item >= 0 && item < _dircount) {
  1322. *it = item;
  1323. }
  1324. if (*it >= 0) return 2;
  1325. else return 0;
  1326. }
  1327. else {
  1328. *it = -1;
  1329. const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0);
  1330. const int t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * 2 * _scalefactor;
  1331. const int t_t = FAREAMRGL * _scalefactor + fsel_width - TEXTSEP * _scalefactor - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP * 2 * _scalefactor) : 0);
  1332. if (x >= fsel_width + FAREAMRGL * _scalefactor) ;
  1333. else if ((_columns & 2) && x >= t_s) *it = 3;
  1334. else if ((_columns & 1) && x >= t_t) *it = 2;
  1335. else if (x >= FAREATEXTL * _scalefactor + _fib_dir_indent - TEXTSEP * _scalefactor) *it = 1;
  1336. if (*it >= 0) return 5;
  1337. else return 0;
  1338. }
  1339. }
  1340. // places list
  1341. if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB * _scalefactor && x < (FAREAMRGL - FAREAMRGB) * _scalefactor) {
  1342. const int item = (y - ltop) / _fib_font_vsep;
  1343. *it = -1;
  1344. if (item >= 0 && item < _placecnt) {
  1345. *it = item;
  1346. }
  1347. if (*it >= 0) return 6;
  1348. else return 0;
  1349. }
  1350. return 0;
  1351. // unused
  1352. (void)dpy;
  1353. }
  1354. static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) {
  1355. int hov_p = -1;
  1356. int hov_b = -1;
  1357. int hov_h = -1;
  1358. int hov_s = -1;
  1359. #ifdef LIST_ENTRY_HOVER
  1360. int hov_f = -1;
  1361. int hov_l = -1;
  1362. #endif
  1363. switch (type) {
  1364. case 1: hov_p = item; break;
  1365. case 3: hov_b = item; break;
  1366. case 4: hov_s = item; break;
  1367. case 5: hov_h = item; break;
  1368. #ifdef LIST_ENTRY_HOVER
  1369. case 6: hov_l = item; break;
  1370. case 2: hov_f = item; break;
  1371. #endif
  1372. default: break;
  1373. }
  1374. #ifdef LIST_ENTRY_HOVER
  1375. if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; }
  1376. if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; }
  1377. #endif
  1378. if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; }
  1379. if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; }
  1380. if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; }
  1381. if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; }
  1382. if (need_expose) {
  1383. fib_expose (dpy, _fib_win);
  1384. }
  1385. }
  1386. static void fib_motion (Display *dpy, int x, int y) {
  1387. int it = -1;
  1388. if (_scrl_my >= 0) {
  1389. const int sdiff = y - _scrl_my;
  1390. const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  1391. const int fsel_height = 4 + llen * _fib_font_vsep;
  1392. const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount;
  1393. int news = _scrl_mf + sdiff / sl;
  1394. if (news < 0) news = 0;
  1395. if (news >= (_dircount - llen)) news = _dircount - llen;
  1396. if (news != _scrl_f) {
  1397. _scrl_f = news;
  1398. fib_expose (dpy, _fib_win);
  1399. }
  1400. return;
  1401. }
  1402. const int type = fib_widget_at_pos (dpy, x, y, &it);
  1403. fib_update_hover (dpy, 0, type, it);
  1404. }
  1405. static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) {
  1406. int it;
  1407. switch (fib_widget_at_pos (dpy, x, y, &it)) {
  1408. case 4: // scrollbar
  1409. if (btn == 1) {
  1410. _dblclk = 0;
  1411. if (it == 0) {
  1412. _scrl_my = y;
  1413. _scrl_mf = _scrl_f;
  1414. } else {
  1415. int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  1416. if (llen < 2) llen = 2;
  1417. int news = _scrl_f;
  1418. if (it == 1) {
  1419. news -= llen - 1;
  1420. } else {
  1421. news += llen - 1;
  1422. }
  1423. if (news < 0) news = 0;
  1424. if (news >= (_dircount - llen)) news = _dircount - llen;
  1425. if (news != _scrl_f && _scrl_y0 >= 0) {
  1426. assert (news >=0);
  1427. _scrl_f = news;
  1428. fib_update_hover (dpy, 1, 4, it);
  1429. }
  1430. }
  1431. }
  1432. break;
  1433. case 2: // file-list
  1434. if (btn == 4 || btn == 5) {
  1435. const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  1436. int news = _scrl_f + ((btn == 4) ? - 1 : 1);
  1437. if (news < 0) news = 0;
  1438. if (news >= (_dircount - llen)) news = _dircount - llen;
  1439. if (news != _scrl_f && _scrl_y0 >= 0) {
  1440. assert (news >=0);
  1441. _scrl_f = news;
  1442. fib_update_hover (dpy, 1, 0, 0);
  1443. }
  1444. _dblclk = 0;
  1445. }
  1446. else if (btn == 1 && it >= 0 && it < _dircount) {
  1447. if (_fsel == it) {
  1448. if (time - _dblclk < DBLCLKTME) {
  1449. fib_open (dpy, it);
  1450. _dblclk = 0;
  1451. }
  1452. _dblclk = time;
  1453. } else {
  1454. fib_select (dpy, it);
  1455. _dblclk = time;
  1456. }
  1457. /*
  1458. if (_fsel >= 0) {
  1459. if (!(_dirlist[_fsel].flags & 4));
  1460. }
  1461. */
  1462. }
  1463. break;
  1464. case 1: // paths
  1465. assert (_fsel < _dircount);
  1466. assert (it >= 0 && it < _pathparts);
  1467. {
  1468. int i = 0;
  1469. char path[1024] = "/";
  1470. while (++i <= it) {
  1471. strcat (path, _pathbtn[i].name);
  1472. strcat (path, "/");
  1473. }
  1474. char *sel = NULL;
  1475. if (i < _pathparts)
  1476. sel = strdup (_pathbtn[i].name);
  1477. else if (i == _pathparts && _fsel >= 0)
  1478. sel = strdup (_dirlist[_fsel].name);
  1479. fib_opendir (dpy, path, sel);
  1480. free (sel);
  1481. }
  1482. break;
  1483. case 3: // btn
  1484. if (btn == 1 && _btns[it]->callback) {
  1485. _btns[it]->callback (dpy);
  1486. }
  1487. break;
  1488. case 5: // sort
  1489. if (btn == 1) {
  1490. switch (it) {
  1491. case 1: if (_sort == 0) _sort = 1; else _sort = 0; break;
  1492. case 2: if (_sort == 2) _sort = 3; else _sort = 2; break;
  1493. case 3: if (_sort == 4) _sort = 5; else _sort = 4; break;
  1494. }
  1495. if (_fsel >= 0) {
  1496. assert (_dirlist && _dircount >= _fsel);
  1497. _dirlist[_fsel].flags &= ~2;
  1498. char *sel = strdup (_dirlist[_fsel].name);
  1499. fib_resort (sel);
  1500. free (sel);
  1501. } else {
  1502. fib_resort (NULL);
  1503. _fsel = -1;
  1504. }
  1505. fib_reset ();
  1506. _hov_h = it;
  1507. fib_select (dpy, _fsel);
  1508. }
  1509. break;
  1510. case 6:
  1511. if (btn == 1 && it >= 0 && it < _placecnt) {
  1512. fib_opendir (dpy, _placelist[it].path, NULL);
  1513. }
  1514. break;
  1515. default:
  1516. break;
  1517. }
  1518. }
  1519. static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) {
  1520. _scrl_my = -1;
  1521. // unused
  1522. return; (void)dpy; (void)x; (void)y; (void)btn; (void)time;
  1523. }
  1524. static void add_place_raw (Display *dpy, const char *name, const char *path) {
  1525. _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace));
  1526. strcpy (_placelist[_placecnt].path, path);
  1527. strcpy (_placelist[_placecnt].name, name);
  1528. _placelist[_placecnt].flags = 0;
  1529. int sw = -1;
  1530. query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL);
  1531. if (sw > _fib_place_width) {
  1532. _fib_place_width = sw;
  1533. }
  1534. ++_placecnt;
  1535. }
  1536. static int add_place_places (Display *dpy, const char *name, const char *url) {
  1537. char const * path;
  1538. struct stat fs;
  1539. int i;
  1540. if (!url || strlen (url) < 1) return -1;
  1541. if (!name || strlen (name) < 1) return -1;
  1542. if (url[0] == '/') {
  1543. path = url;
  1544. }
  1545. else if (!strncmp (url, "file:///", 8)) {
  1546. path = &url[7];
  1547. }
  1548. else {
  1549. return -1;
  1550. }
  1551. if (access (path, R_OK)) {
  1552. return -1;
  1553. }
  1554. if (stat (path, &fs)) {
  1555. return -1;
  1556. }
  1557. if (!S_ISDIR (fs.st_mode)) {
  1558. return -1;
  1559. }
  1560. for (i = 0; i < _placecnt; ++i) {
  1561. if (!strcmp (path, _placelist[i].path)) {
  1562. return -1;
  1563. }
  1564. }
  1565. add_place_raw (dpy, name, path);
  1566. return 0;
  1567. }
  1568. static int parse_gtk_bookmarks (Display *dpy, const char *fn) {
  1569. char tmp[1024];
  1570. if (access (fn, R_OK)) {
  1571. return -1;
  1572. }
  1573. FILE *bm = fopen (fn, "r");
  1574. if (!bm) return -1;
  1575. int found = 0;
  1576. while (fgets (tmp, sizeof(tmp), bm)
  1577. && strlen (tmp) > 1
  1578. && strlen (tmp) < sizeof(tmp))
  1579. {
  1580. char *s, *n;
  1581. tmp[strlen (tmp) - 1] = '\0'; // strip newline
  1582. if ((s = strchr (tmp, ' '))) {
  1583. *s = '\0';
  1584. n = strdup (++s);
  1585. decode_3986 (tmp);
  1586. if (!add_place_places (dpy, n, tmp)) {
  1587. ++found;
  1588. }
  1589. free (n);
  1590. } else if ((s = strrchr (tmp, '/'))) {
  1591. n = strdup (++s);
  1592. decode_3986 (tmp);
  1593. if (!add_place_places (dpy, n, tmp)) {
  1594. ++found;
  1595. }
  1596. free (n);
  1597. }
  1598. }
  1599. fclose (bm);
  1600. return found;
  1601. }
  1602. #ifdef HAVE_MNTENT
  1603. static const char *ignore_mountpoints[] = {
  1604. "/bin", "/boot", "/dev", "/etc",
  1605. "/lib", "/live", "/mnt", "/opt",
  1606. "/root", "/sbin", "/srv", "/tmp",
  1607. "/usr", "/var", "/proc", "/sbin",
  1608. "/net", "/sys"
  1609. };
  1610. static const char *ignore_fs[] = {
  1611. "auto", "autofs",
  1612. "debugfs", "devfs",
  1613. "devpts", "ecryptfs",
  1614. "fusectl", "kernfs",
  1615. "linprocfs", "proc",
  1616. "ptyfs", "rootfs",
  1617. "selinuxfs", "sysfs",
  1618. "tmpfs", "usbfs",
  1619. "nfsd", "rpc_pipefs",
  1620. };
  1621. static const char *ignore_devices[] = {
  1622. "binfmt_", "devpts",
  1623. "gvfs", "none",
  1624. "nfsd", "sunrpc",
  1625. "/dev/loop", "/dev/vn"
  1626. };
  1627. static int check_mount (const char *mountpoint, const char *fs, const char *device) {
  1628. size_t i;
  1629. if (!mountpoint || !fs || !device) return -1;
  1630. //printf("%s %s %s\n", mountpoint, fs, device);
  1631. for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) {
  1632. if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) {
  1633. return 1;
  1634. }
  1635. }
  1636. if (!strncmp (mountpoint, "/home", 5)) {
  1637. return 1;
  1638. }
  1639. for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) {
  1640. if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) {
  1641. return 1;
  1642. }
  1643. }
  1644. for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) {
  1645. if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) {
  1646. return 1;
  1647. }
  1648. }
  1649. return 0;
  1650. }
  1651. static int read_mtab (Display *dpy, const char *mtab) {
  1652. FILE *mt = fopen (mtab, "r");
  1653. if (!mt) return -1;
  1654. int found = 0;
  1655. struct mntent *mntent;
  1656. while ((mntent = getmntent (mt)) != NULL) {
  1657. char *s;
  1658. if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname))
  1659. continue;
  1660. if ((s = strrchr (mntent->mnt_dir, '/'))) {
  1661. ++s;
  1662. } else {
  1663. s = mntent->mnt_dir;
  1664. }
  1665. if (!add_place_places (dpy, s, mntent->mnt_dir)) {
  1666. ++found;
  1667. }
  1668. }
  1669. fclose (mt);
  1670. return found;
  1671. }
  1672. #endif
  1673. static void populate_places (Display *dpy) {
  1674. char tmp[1024];
  1675. int spacer = -1;
  1676. if (_placecnt > 0) return;
  1677. _fib_place_width = 0;
  1678. if (_recentcnt > 0) {
  1679. add_place_raw (dpy, "Recently Used", "");
  1680. _placelist[0].flags |= 4;
  1681. }
  1682. add_place_places (dpy, "Home", getenv ("HOME"));
  1683. if (getenv ("HOME")) {
  1684. strcpy (tmp, getenv ("HOME"));
  1685. strcat (tmp, "/Desktop");
  1686. add_place_places (dpy, "Desktop", tmp);
  1687. }
  1688. add_place_places (dpy, "Filesystem", "/");
  1689. if (_placecnt > 0) spacer = _placecnt -1;
  1690. if (strlen (_fib_cfg_custom_places) > 0) {
  1691. parse_gtk_bookmarks (dpy, _fib_cfg_custom_places);
  1692. }
  1693. #ifdef HAVE_MNTENT
  1694. if (read_mtab (dpy, "/proc/mounts") < 1) {
  1695. read_mtab (dpy, "/etc/mtab");
  1696. }
  1697. #endif
  1698. int parsed_bookmarks = 0;
  1699. if (!parsed_bookmarks && getenv ("HOME")) {
  1700. strcpy (tmp, getenv ("HOME"));
  1701. strcat (tmp, "/.gtk-bookmarks");
  1702. if (parse_gtk_bookmarks (dpy, tmp) > 0) {
  1703. parsed_bookmarks = 1;
  1704. }
  1705. }
  1706. if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) {
  1707. strcpy (tmp, getenv ("XDG_CONFIG_HOME"));
  1708. strcat (tmp, "/gtk-3.0/bookmarks");
  1709. if (parse_gtk_bookmarks (dpy, tmp) > 0) {
  1710. parsed_bookmarks = 1;
  1711. }
  1712. }
  1713. if (!parsed_bookmarks && getenv ("HOME")) {
  1714. strcpy (tmp, getenv ("HOME"));
  1715. strcat (tmp, "/.config/gtk-3.0/bookmarks");
  1716. if (parse_gtk_bookmarks (dpy, tmp) > 0) {
  1717. parsed_bookmarks = 1;
  1718. }
  1719. }
  1720. if (_fib_place_width > 0) {
  1721. _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX);
  1722. }
  1723. if (spacer > 0 && spacer < _placecnt -1) {
  1724. _placelist[ spacer ].flags |= 4;
  1725. }
  1726. }
  1727. static uint8_t font_err = 0;
  1728. static int x_error_handler (Display *d, XErrorEvent *e) {
  1729. font_err = 1;
  1730. return 0;
  1731. // unused
  1732. (void)d; (void)e;
  1733. }
  1734. int x_fib_show (Display *dpy, Window parent, int x, int y, double scalefactor) {
  1735. if (_fib_win) {
  1736. XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime);
  1737. return -1;
  1738. }
  1739. _status = 0;
  1740. _rv_open[0] = '\0';
  1741. Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy));
  1742. _c_gray1.flags = DoRed | DoGreen | DoBlue;
  1743. _c_gray0.red = _c_gray0.green = _c_gray0.blue = 0x5000; // 95% hover prelight
  1744. _c_gray1.red = _c_gray1.green = _c_gray1.blue = 0x1100; // 93% window bg, scrollbar-fg
  1745. _c_gray2.red = _c_gray2.green = _c_gray2.blue = 0x1c00; // 83% button & list bg
  1746. _c_gray3.red = _c_gray3.green = _c_gray3.blue = 0x0a00; // 75% heading + scrollbar-bg
  1747. _c_gray4.red = _c_gray4.green = _c_gray4.blue = 0xd600; // 40% prelight text, sep lines
  1748. _c_gray5.red = _c_gray5.green = _c_gray5.blue = 0x3000; // 20% 3D border
  1749. if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1;
  1750. if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1;
  1751. if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1;
  1752. if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1;
  1753. if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1;
  1754. if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1;
  1755. XSetWindowAttributes attr;
  1756. memset (&attr, 0, sizeof(XSetWindowAttributes));
  1757. attr.border_pixel = _c_gray2.pixel;
  1758. attr.event_mask = ExposureMask | KeyPressMask
  1759. | ButtonPressMask | ButtonReleaseMask
  1760. | ConfigureNotify | StructureNotifyMask
  1761. | PointerMotionMask | LeaveWindowMask;
  1762. _fib_win = XCreateWindow (
  1763. dpy, DefaultRootWindow (dpy),
  1764. x, y, _fib_width * scalefactor, _fib_height * scalefactor,
  1765. 1, CopyFromParent, InputOutput, CopyFromParent,
  1766. CWEventMask | CWBorderPixel, &attr);
  1767. _scalefactor = scalefactor;
  1768. if (!_fib_win) { return 1; }
  1769. if (parent)
  1770. XSetTransientForHint (dpy, _fib_win, parent);
  1771. XStoreName (dpy, _fib_win, "Select File");
  1772. Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True);
  1773. XSetWMProtocols (dpy, _fib_win, &wmDelete, 1);
  1774. _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL);
  1775. XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter);
  1776. const char dl[1] = {1};
  1777. XSetDashes (dpy, _fib_gc, 0, dl, 1);
  1778. int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler);
  1779. #define _XTESTFONT(FN) \
  1780. { \
  1781. font_err = 0; \
  1782. _fibfont = XLoadFont (dpy, FN); \
  1783. XSetFont (dpy, _fib_gc, _fibfont); \
  1784. XSync (dpy, False); \
  1785. }
  1786. font_err = 1;
  1787. if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT"));
  1788. if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font);
  1789. if (scalefactor >= 2.5) {
  1790. if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*-*");
  1791. if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-18-*-*-*-*-*-*-*");
  1792. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-20-*-*-*-*-*-*-*");
  1793. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*");
  1794. } else if (scalefactor >= 2) {
  1795. if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-16-*-*-*-*-*-*-*");
  1796. if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-16-*-*-*-*-*-*-*");
  1797. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*");
  1798. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-16-*-*-*-*-*-*-*");
  1799. } else if (scalefactor >= 1.5) {
  1800. if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-14-*-*-*-*-*-*-*");
  1801. if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-14-*-*-*-*-*-*-*");
  1802. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-15-*-*-*-*-*-*-*");
  1803. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*");
  1804. } else {
  1805. if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*");
  1806. if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*");
  1807. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*");
  1808. if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*");
  1809. }
  1810. if (font_err) _fibfont = None;
  1811. XSync (dpy, False);
  1812. XSetErrorHandler (handler);
  1813. if (_fib_font_height == 0) { // 1st time only
  1814. query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL);
  1815. query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL);
  1816. if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) {
  1817. XFreeGC (dpy, _fib_gc);
  1818. XDestroyWindow (dpy, _fib_win);
  1819. _fib_win = 0;
  1820. return -1;
  1821. }
  1822. _fib_font_height += 3 * scalefactor;
  1823. _fib_font_ascent += 2 * scalefactor;
  1824. _fib_font_vsep = _fib_font_height + 2 * scalefactor;
  1825. }
  1826. populate_places (dpy);
  1827. strcpy (_btn_ok.text, "Open");
  1828. strcpy (_btn_cancel.text, "Cancel");
  1829. strcpy (_btn_filter.text, "List All Files");
  1830. strcpy (_btn_places.text, "Show Places");
  1831. strcpy (_btn_hidden.text, "Show Hidden");
  1832. _btn_ok.callback = &cb_open;
  1833. _btn_cancel.callback = &cb_cancel;
  1834. _btn_filter.callback = &cb_filter;
  1835. _btn_places.callback = &cb_places;
  1836. _btn_hidden.callback = &cb_hidden;
  1837. _btn_filter.flags |= 4;
  1838. _btn_places.flags |= 4;
  1839. _btn_hidden.flags |= 4;
  1840. if (!_fib_filter_function) {
  1841. _btn_filter.flags |= 8;
  1842. }
  1843. size_t i;
  1844. int btncnt = 0;
  1845. _btn_w = 0;
  1846. _btn_span = 0;
  1847. for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) {
  1848. if (_btns[i]->flags & 8) { continue; }
  1849. query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL);
  1850. if (_btns[i]->flags & 4) {
  1851. _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor;
  1852. } else {
  1853. ++btncnt;
  1854. if (_btns[i]->tw > _btn_w)
  1855. _btn_w = _btns[i]->tw;
  1856. }
  1857. }
  1858. _btn_w += (BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP) * scalefactor;
  1859. _btn_span += _btn_w * btncnt + DSEP * scalefactor * (i - 1) + (FAREAMRGR + FAREAMRGB) * scalefactor;
  1860. for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) {
  1861. if (_btns[i]->flags & 8) { continue; }
  1862. if (_btns[i]->flags & 4) {
  1863. _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor;
  1864. } else {
  1865. _btns[i]->xw = _btn_w;
  1866. }
  1867. }
  1868. sync_button_states () ;
  1869. _fib_height = _fib_font_vsep * 15.8 * (1.0 + (scalefactor - 1.0) / 2.0);
  1870. _fib_width = MAX (_btn_span, 480 * scalefactor);
  1871. XResizeWindow (dpy, _fib_win, _fib_width, _fib_height);
  1872. XTextProperty x_wname, x_iname;
  1873. XSizeHints hints;
  1874. XWMHints wmhints;
  1875. hints.flags = PSize | PMinSize;
  1876. hints.min_width = _btn_span;
  1877. hints.min_height = 8 * _fib_font_vsep;
  1878. char *w_name = & _fib_cfg_title[0];
  1879. wmhints.input = True;
  1880. wmhints.flags = InputHint;
  1881. if (XStringListToTextProperty (&w_name, 1, &x_wname) &&
  1882. XStringListToTextProperty (&w_name, 1, &x_iname))
  1883. {
  1884. XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL);
  1885. XFree (x_wname.value);
  1886. XFree (x_iname.value);
  1887. }
  1888. XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel);
  1889. _fib_mapped = 0;
  1890. XMapRaised (dpy, _fib_win);
  1891. if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) {
  1892. fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL);
  1893. }
  1894. #if 0
  1895. XGrabPointer (dpy, _fib_win, True,
  1896. ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask,
  1897. GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
  1898. XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime);
  1899. //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime);
  1900. #endif
  1901. _recentlock = 1;
  1902. return 0;
  1903. }
  1904. void x_fib_close (Display *dpy) {
  1905. if (!_fib_win) return;
  1906. XFreeGC (dpy, _fib_gc);
  1907. XDestroyWindow (dpy, _fib_win);
  1908. _fib_win = 0;
  1909. free (_dirlist);
  1910. _dirlist = NULL;
  1911. free (_pathbtn);
  1912. _pathbtn = NULL;
  1913. if (_fibfont != None) XUnloadFont (dpy, _fibfont);
  1914. _fibfont = None;
  1915. free (_placelist);
  1916. _placelist = NULL;
  1917. _dircount = 0;
  1918. _pathparts = 0;
  1919. _placecnt = 0;
  1920. if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer);
  1921. _pixbuffer = None;
  1922. Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy));
  1923. XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0);
  1924. XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0);
  1925. XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0);
  1926. XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0);
  1927. XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0);
  1928. XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0);
  1929. _recentlock = 0;
  1930. }
  1931. int x_fib_handle_events (Display *dpy, XEvent *event) {
  1932. if (!_fib_win) return 0;
  1933. if (_status) return 0;
  1934. if (event->xany.window != _fib_win) {
  1935. return 0;
  1936. }
  1937. switch (event->type) {
  1938. case MapNotify:
  1939. _fib_mapped = 1;
  1940. break;
  1941. case UnmapNotify:
  1942. _fib_mapped = 0;
  1943. break;
  1944. case LeaveNotify:
  1945. fib_update_hover (dpy, 1, 0, 0);
  1946. break;
  1947. case ClientMessage:
  1948. if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) {
  1949. _status = -1;
  1950. }
  1951. break;
  1952. case ConfigureNotify:
  1953. if (
  1954. (event->xconfigure.width > 1 && event->xconfigure.height > 1)
  1955. &&
  1956. (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height)
  1957. )
  1958. {
  1959. _fib_width = event->xconfigure.width;
  1960. _fib_height = event->xconfigure.height;
  1961. _fib_resized = 1;
  1962. }
  1963. break;
  1964. case Expose:
  1965. if (event->xexpose.count == 0) {
  1966. fib_expose (dpy, event->xany.window);
  1967. }
  1968. break;
  1969. case MotionNotify:
  1970. fib_motion (dpy, event->xmotion.x, event->xmotion.y);
  1971. if (event->xmotion.is_hint == NotifyHint) {
  1972. XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL);
  1973. }
  1974. break;
  1975. case ButtonPress:
  1976. fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time);
  1977. break;
  1978. case ButtonRelease:
  1979. fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time);
  1980. break;
  1981. case KeyRelease:
  1982. break;
  1983. case KeyPress:
  1984. {
  1985. KeySym key;
  1986. char buf[100];
  1987. static XComposeStatus stat;
  1988. XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat);
  1989. switch (key) {
  1990. case XK_Escape:
  1991. _status = -1;
  1992. break;
  1993. case XK_Up:
  1994. if (_fsel > 0) {
  1995. fib_select (dpy, _fsel - 1);
  1996. }
  1997. break;
  1998. case XK_Down:
  1999. if (_fsel < _dircount -1) {
  2000. fib_select ( dpy, _fsel + 1);
  2001. }
  2002. break;
  2003. case XK_Page_Up:
  2004. if (_fsel > 0) {
  2005. int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  2006. if (llen < 1) llen = 1; else --llen;
  2007. int fs = MAX (0, _fsel - llen);
  2008. fib_select ( dpy, fs);
  2009. }
  2010. break;
  2011. case XK_Page_Down:
  2012. if (_fsel < _dircount) {
  2013. int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep;
  2014. if (llen < 1) llen = 1; else --llen;
  2015. int fs = MIN (_dircount - 1, _fsel + llen);
  2016. fib_select ( dpy, fs);
  2017. }
  2018. break;
  2019. case XK_Left:
  2020. if (_pathparts > 1) {
  2021. int i = 0;
  2022. char path[1024] = "/";
  2023. while (++i < _pathparts - 1) {
  2024. strcat (path, _pathbtn[i].name);
  2025. strcat (path, "/");
  2026. }
  2027. char *sel = strdup (_pathbtn[_pathparts-1].name);
  2028. fib_opendir (dpy, path, sel);
  2029. free (sel);
  2030. }
  2031. break;
  2032. case XK_Right:
  2033. if (_fsel >= 0 && _fsel < _dircount) {
  2034. if (_dirlist[_fsel].flags & 4) {
  2035. cb_open (dpy);
  2036. }
  2037. }
  2038. break;
  2039. case XK_Return:
  2040. cb_open (dpy);
  2041. break;
  2042. default:
  2043. if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) {
  2044. int i;
  2045. for (i = 0; i < _dircount; ++i) {
  2046. int j = (_fsel + i + 1) % _dircount;
  2047. char kcmp = _dirlist[j].name[0];
  2048. if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20;
  2049. if (kcmp == (char)key) {
  2050. fib_select ( dpy, j);
  2051. break;
  2052. }
  2053. }
  2054. }
  2055. break;
  2056. }
  2057. }
  2058. break;
  2059. }
  2060. if (_status) {
  2061. x_fib_close (dpy);
  2062. }
  2063. return _status;
  2064. }
  2065. int x_fib_status () {
  2066. return _status;
  2067. }
  2068. int x_fib_configure (int k, const char *v) {
  2069. if (_fib_win) { return -1; }
  2070. switch (k) {
  2071. case 0:
  2072. if (strlen (v) >= sizeof(_cur_path) -1) return -2;
  2073. if (strlen (v) < 1) return -2;
  2074. if (v[0] != '/') return -2;
  2075. if (strstr (v, "//")) return -2;
  2076. strncpy (_cur_path, v, sizeof(_cur_path));
  2077. break;
  2078. case 1:
  2079. if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2;
  2080. strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title));
  2081. break;
  2082. case 2:
  2083. if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2;
  2084. strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font));
  2085. break;
  2086. case 3:
  2087. if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2;
  2088. strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places));
  2089. break;
  2090. default:
  2091. return -2;
  2092. }
  2093. return 0;
  2094. }
  2095. int x_fib_cfg_buttons (int k, int v) {
  2096. if (_fib_win) { return -1; }
  2097. switch (k) {
  2098. case 1:
  2099. if (v < 0) {
  2100. _btn_hidden.flags |= 8;
  2101. } else {
  2102. _btn_hidden.flags &= ~8;
  2103. }
  2104. if (v == 1) {
  2105. _btn_hidden.flags |= 2;
  2106. _fib_hidden_fn = 1;
  2107. } else if (v == 0) {
  2108. _btn_hidden.flags &= 2;
  2109. _fib_hidden_fn = 0;
  2110. }
  2111. break;
  2112. case 2:
  2113. if (v < 0) {
  2114. _btn_places.flags |= 8;
  2115. } else {
  2116. _btn_places.flags &= ~8;
  2117. }
  2118. if (v == 1) {
  2119. _btn_places.flags |= 2;
  2120. _fib_show_places = 1;
  2121. } else if (v == 0) {
  2122. _btn_places.flags &= ~2;
  2123. _fib_show_places = 0;
  2124. }
  2125. break;
  2126. case 3:
  2127. // NB. filter button is automatically hidden
  2128. // IFF the filter-function is NULL.
  2129. if (v < 0) {
  2130. _btn_filter.flags |= 8;
  2131. } else {
  2132. _btn_filter.flags &= ~8;
  2133. }
  2134. if (v == 1) {
  2135. _btn_filter.flags &= ~2; // inverse - 'show all' = !filter
  2136. _fib_filter_fn = 1;
  2137. } else if (v == 0) {
  2138. _btn_filter.flags |= 2;
  2139. _fib_filter_fn = 0;
  2140. }
  2141. break;
  2142. default:
  2143. return -2;
  2144. }
  2145. return 0;
  2146. }
  2147. int x_fib_cfg_filter_callback (int (*cb)(const char*)) {
  2148. if (_fib_win) { return -1; }
  2149. _fib_filter_function = cb;
  2150. return 0;
  2151. }
  2152. char *x_fib_filename () {
  2153. if (_status > 0 && !_fib_win)
  2154. return strdup (_rv_open);
  2155. else
  2156. return NULL;
  2157. }
  2158. #endif // HAVE_X11
  2159. #if defined(__clang__)
  2160. # pragma clang diagnostic pop
  2161. #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
  2162. # pragma GCC diagnostic pop
  2163. #endif
  2164. /* example usage */
  2165. #ifdef SOFD_TEST
  2166. static int fib_filter_movie_filename (const char *name) {
  2167. if (!_fib_filter_fn) return 1;
  2168. const int l3 = strlen (name) - 3;
  2169. const int l4 = l3 - 1;
  2170. const int l5 = l4 - 1;
  2171. const int l6 = l5 - 1;
  2172. const int l9 = l6 - 3;
  2173. if (
  2174. (l4 > 0 && (
  2175. !strcasecmp (&name[l4], ".avi")
  2176. || !strcasecmp (&name[l4], ".mov")
  2177. || !strcasecmp (&name[l4], ".ogg")
  2178. || !strcasecmp (&name[l4], ".ogv")
  2179. || !strcasecmp (&name[l4], ".mpg")
  2180. || !strcasecmp (&name[l4], ".mov")
  2181. || !strcasecmp (&name[l4], ".mp4")
  2182. || !strcasecmp (&name[l4], ".mkv")
  2183. || !strcasecmp (&name[l4], ".vob")
  2184. || !strcasecmp (&name[l4], ".asf")
  2185. || !strcasecmp (&name[l4], ".avs")
  2186. || !strcasecmp (&name[l4], ".dts")
  2187. || !strcasecmp (&name[l4], ".flv")
  2188. || !strcasecmp (&name[l4], ".m4v")
  2189. )) ||
  2190. (l5 > 0 && (
  2191. !strcasecmp (&name[l5], ".h264")
  2192. || !strcasecmp (&name[l5], ".webm")
  2193. )) ||
  2194. (l6 > 0 && (
  2195. !strcasecmp (&name[l6], ".dirac")
  2196. )) ||
  2197. (l9 > 0 && (
  2198. !strcasecmp (&name[l9], ".matroska")
  2199. )) ||
  2200. (l3 > 0 && (
  2201. !strcasecmp (&name[l3], ".dv")
  2202. || !strcasecmp (&name[l3], ".ts")
  2203. ))
  2204. )
  2205. {
  2206. return 1;
  2207. }
  2208. return 0;
  2209. }
  2210. int main (int argc, char **argv) {
  2211. Display* dpy = XOpenDisplay (0);
  2212. if (!dpy) return -1;
  2213. x_fib_cfg_filter_callback (fib_filter_movie_filename);
  2214. x_fib_configure (1, "Open Movie File");
  2215. x_fib_load_recent ("/tmp/sofd.recent");
  2216. x_fib_show (dpy, 0, 300, 300);
  2217. while (1) {
  2218. XEvent event;
  2219. while (XPending (dpy) > 0) {
  2220. XNextEvent (dpy, &event);
  2221. if (x_fib_handle_events (dpy, &event)) {
  2222. if (x_fib_status () > 0) {
  2223. char *fn = x_fib_filename ();
  2224. printf ("OPEN '%s'\n", fn);
  2225. x_fib_add_recent (fn, time (NULL));
  2226. free (fn);
  2227. }
  2228. }
  2229. }
  2230. if (x_fib_status ()) {
  2231. break;
  2232. }
  2233. usleep (80000);
  2234. }
  2235. x_fib_close (dpy);
  2236. x_fib_save_recent ("/tmp/sofd.recent");
  2237. x_fib_free_recent ();
  2238. XCloseDisplay (dpy);
  2239. return 0;
  2240. }
  2241. #endif