ui_file.h 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. /*
  2. * ui_file.h
  3. * https://gitlab.com/bztsrc/smgui
  4. *
  5. * Copyright (C) 2024 bzt (bztsrc@gitlab), MIT license
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a copy
  8. * of this software and associated documentation files (the "Software"), to
  9. * deal in the Software without restriction, including without limitation the
  10. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  11. * sell copies of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in
  15. * all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ANY
  20. * DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
  22. * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. *
  24. * @brief Text input with file picker for SMGUI
  25. */
  26. #ifndef UI_FILE_H
  27. #define UI_FILE_H 1
  28. #ifndef UI_H
  29. #include <ui.h>
  30. #undef UI_H
  31. #endif
  32. #ifdef __cplusplus
  33. extern "C" {
  34. #endif
  35. #ifdef __WIN32__
  36. #define DIRSEP "\\"
  37. #include <windows.h>
  38. #include <wchar.h>
  39. #else
  40. #define DIRSEP "/"
  41. #endif
  42. #define __USE_MISC
  43. #include <limits.h>
  44. #include <stdio.h>
  45. #include <unistd.h>
  46. #include <dirent.h>
  47. #include <time.h>
  48. #include <sys/types.h>
  49. #include <sys/stat.h>
  50. #ifndef FILENAME_MAX
  51. #define FILENAME_MAX 255
  52. #endif
  53. #ifndef PATH_MAX
  54. #ifdef MAX_PATH
  55. #define PATH_MAX MAX_PATH
  56. #else
  57. #define PATH_MAX 4096
  58. #endif
  59. #endif
  60. extern char *realpath (const char *__name, char *__resolved);
  61. #define UI_FILE UI_CUSTOM,.bbox=ui_file_bbox,.view=ui_file_view,.ctrl=ui_file_ctrl,.fini=ui_path_stop
  62. #define UI_PATH UI_CUSTOM,.bbox=ui_path_bbox,.view=ui_path_view,.ctrl=ui_path_ctrl,.fini=ui_path_stop
  63. static ui_form_t _ui_file_popup[] = { { .type = UI_END } };
  64. typedef struct {
  65. uint64_t size;
  66. uint64_t date;
  67. int idx;
  68. char type;
  69. char name[FILENAME_MAX + 1];
  70. } ui_files_t;
  71. #define UI_PATH_SEARCH 1
  72. #define UI_PATH_NEWDIR 2
  73. #define UI_PATH_ONLYDIR 4
  74. #define UI_PATH_HIDDEN 8
  75. typedef int (*ui_form_select)(char *path, int isdir);
  76. typedef struct {
  77. char exts[256], filter[FILENAME_MAX + 1], fn[FILENAME_MAX + 1];
  78. int flags, numfiles, shfiles, numpath, pathscr, pathsel, pathh, sw, tw, srt, scr, sel;
  79. uint16_t pathw[256];
  80. ui_files_t *files;
  81. ui_form_select select;
  82. } ui_path_t;
  83. static ui_path_t _ui_path_ctx;
  84. static ui_form_t _ui_path_vscr;
  85. /* we must not rely on all fonts always have these glyphs */
  86. uint16_t _ui_path_glyphs[6*16] = {
  87. 0,0,0,480,528,1032,1288,1288,1160,528,3552,6144,12288,0,0,0,0,2048,18808,8824,40958,4098,24058,14074,30714,32762,32762,32762,
  88. 32762,16380,0,0,0,0,120,120,8190,4098,32762,32762,32762,32762,32762,32762,32762,16380,0,0,0,0,248,392,648,1160,2184,8072,4104,
  89. 4104,4104,4104,4104,8184,0,0
  90. };
  91. /**
  92. * Recalculate form element bounding box
  93. */
  94. int ui_path_bbox(ui_t *ctx, int x, int y, int w, int h, ui_form_t *form, int *dw, int *dh)
  95. {
  96. if(!ctx || !form || !dw || !dh) return UI_ERR_BADINP;
  97. (void)x; (void)y; (void)w; (void)h;
  98. if(ctx->fnt && ctx->bbox && form->ptr) {
  99. /* if string is empty, use a default one to get height and baseline correctly */
  100. (*ctx->bbox)(ctx->fnt, form->ptr && *((char*)form->ptr) ? (char*)form->ptr : "Ag", NULL, dw, dh, &form->l, &form->t);
  101. if(!form->ptr || !*((char*)form->ptr)) *dw = 0;
  102. (*dh) += 4;
  103. }
  104. if(form->ew > 0) *dw = form->ew;
  105. return UI_OK;
  106. }
  107. /**
  108. * Display a file browser
  109. */
  110. int ui_path_view(ui_t *ctx, int x, int y, int w, int h, ui_form_t *form)
  111. {
  112. ui_files_t *file;
  113. ui_path_t *path;
  114. uint64_t size, now;
  115. int i, j, n, X, Y, ph, ps, pt, c, dw, dh, t;
  116. char *s, *e, u, tmp[32];
  117. struct tm *lt;
  118. if(!ctx || !form || !form->data || !ctx->fnt || !ctx->bbox || !ctx->draw) return UI_ERR_BADINP;
  119. if(form->flags & (UI_HIDDEN || UI_DISABLED)) return UI_OK;
  120. path = (ui_path_t*)form->data;
  121. /* path buttons */
  122. ph = path->pathh;
  123. if((path->flags & UI_PATH_NEWDIR) && path->pathh < 16) path->pathh = 16;
  124. ph += 6;
  125. for(X = x, s = form->ptr, i = 0; *s && i < path->numpath; s = e, i++) {
  126. #ifdef __WIN32__
  127. if(!i) { e = s + 2; } else
  128. #endif
  129. { for(e = s + 1; *e && e[-1] != DIRSEP[0]; e++){} if(e[-1] != DIRSEP[0]) break; }
  130. if(i >= path->pathscr) {
  131. _ui_text(ctx, X, y, path->pathw[i] - 2, ph, 0, form->t, 0, 64 | (i == path->pathsel ? 32 : 16), s, e);
  132. X += path->pathw[i];
  133. }
  134. }
  135. X = x + w;
  136. if(path->flags & UI_PATH_NEWDIR) {
  137. _ui_text(ctx, X - 20, y, 20, ph, 0, 0, 0, path->pathsel == -2 ? 0 : 16, "", NULL);
  138. _ui_bmp16(ctx, X - 18, y + (ph - 16) / 2 + !!(path->pathsel == -2), &_ui_path_glyphs[16], ctx->theme[UI_IFG]);
  139. X -= 24;
  140. }
  141. if(path->flags & UI_PATH_SEARCH) {
  142. j = w / 3;
  143. if(j > 128) j = 128;
  144. X -= j;
  145. _ui_text(ctx, X, y, j, ph, 0, form->t, 0, ctx->str == path->filter, path->filter, NULL);
  146. if(!(path->flags & UI_PATH_NEWDIR) && !path->filter[0] && ctx->str != path->filter)
  147. _ui_bmp16(ctx, X + 2, y + (ph - 16) / 2, &_ui_path_glyphs[0], ctx->theme[UI_IL]);
  148. }
  149. y += ph + 2; h -= ph + 2;
  150. /* file list border */
  151. _ui_rect(ctx, x, y, w, h, ctx->theme[UI_ID], ctx->theme[UI_IBG], ctx->theme[UI_IL]);
  152. x++; y++; w -= 2; h -= 2;
  153. /* file list header */
  154. pt = w - path->tw; ps = pt - path->sw;
  155. if(form->str > 0) {
  156. _ui_header(ctx, x, y, ps, path->pathh + 4, path->pathsel == -4, path->srt, ctx->txtv[form->str]);
  157. _ui_header(ctx, x + ps, y, path->sw, path->pathh + 4, path->pathsel == -5, path->srt - 2, ctx->txtv[form->str + 1]);
  158. _ui_header(ctx, x + pt, y, path->tw, path->pathh + 4, path->pathsel == -6, path->srt - 4, ctx->txtv[form->str + 2]);
  159. y += path->pathh + 4; h -= path->pathh + 4;
  160. }
  161. /* file list */
  162. w -= ctx->sw;
  163. _ui_vscrbar(ctx, x + w, y, h, path->scr, path->shfiles * path->pathh, path->pathsel == -7 && ctx->vscr && ctx->vscr->ptr == &path->scr);
  164. ctx->cx0 = x; ctx->cx1 = x + w; ctx->cy0 = y; ctx->cy1 = y + h;
  165. _ui_boxbg(ctx, x, y, w, h, path->pathh, 0, path->scr);
  166. if(path->files) {
  167. now = time(NULL);
  168. n = x + ps - 2 < ctx->cx1 ? x + ps - 2 : ctx->cx1;
  169. if(path->scr + h > path->shfiles * path->pathh) path->scr = path->shfiles * path->pathh - h;
  170. if(path->scr < 0) path->scr = 0;
  171. for(i = 0, Y = y - path->scr; i < path->shfiles && Y < ctx->cy1; i++, Y += path->pathh) {
  172. if(Y + path->pathh < y) continue;
  173. /* background */
  174. file = (ui_files_t*)&path->files[path->files[i].idx];
  175. j = (file->type + 2) * 16;
  176. if(i == path->sel) {
  177. if(ctx->skin[UI_INP].buf)
  178. _ui_blit(ctx, x, Y, w, path->pathh, &ctx->skin[UI_HL], 0, 0, 0);
  179. else
  180. _ui_frect(ctx, x, Y, w, path->pathh, ctx->theme[UI_HLBG]);
  181. c = UI_HLFG;
  182. } else
  183. c = UI_FG;
  184. /* file name */
  185. _ui_bmp16(ctx, x + 2, Y + (path->pathh - 16) / 2, &_ui_path_glyphs[j], ctx->theme[c]);
  186. (*ctx->draw)(ctx->fnt, file->name, NULL, ctx->screen.buf, ctx->theme[c], x + 20, Y, 0, form->t, ctx->screen.p,
  187. x + 20, ctx->cy0, n, ctx->cy1);
  188. if(file->size != -1U) {
  189. /* file size */
  190. u = 0; size = file->size;
  191. if(size > 1023) {
  192. size = (size + 1023) >> 10; u = 'k';
  193. if(size > 1023) {
  194. size = (size + 1023) >> 10; u = 'M';
  195. if(size > 1023) {
  196. size = (size + 1023) >> 10; u = 'G';
  197. if(size > 1023) { size = (size + 1023) >> 10; u = 'T'; }
  198. }
  199. }
  200. }
  201. sprintf(tmp, "%u%c", (uint32_t)size, u);
  202. (*ctx->bbox)(ctx->fnt, tmp, NULL, &dw, &dh, NULL, &t);
  203. (*ctx->draw)(ctx->fnt, tmp, NULL, ctx->screen.buf, ctx->theme[c], x + pt - 2 - dw, Y, 0, t, ctx->screen.p,
  204. x + pt - 2 - dw, ctx->cy0, x + pt, ctx->cy1);
  205. /* file date */
  206. tmp[0] = 0;
  207. if(form->str) {
  208. /* localized human friendly */
  209. size = now - file->date;
  210. if(size < 120) strcpy(tmp, ctx->txtv[form->str + 3]); else
  211. if(size < 3600) sprintf(tmp, "%u %s", (uint32_t)(size/60), ctx->txtv[form->str + 4]); else
  212. if(size < 7200) strcpy(tmp, ctx->txtv[form->str + 5]); else
  213. if(size < 24*3600) sprintf(tmp, "%u %s", (uint32_t)(size/3600), ctx->txtv[form->str + 6]); else
  214. if(size < 48*3600) strcpy(tmp, ctx->txtv[form->str + 7]);
  215. }
  216. if(!tmp[0]) {
  217. /* non-localized, standard ISO date otherwise */
  218. lt = localtime((const time_t*)&file->date);
  219. sprintf(tmp, "%04d-%02d-%02d", lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday);
  220. }
  221. (*ctx->draw)(ctx->fnt, tmp, NULL, ctx->screen.buf, ctx->theme[c], x + pt + 2, Y, 0, t, ctx->screen.p,
  222. x + pt + 2, ctx->cy0, x + w - 2, ctx->cy1);
  223. }
  224. y += path->pathh; h -= path->pathh;
  225. }
  226. }
  227. return UI_OK;
  228. }
  229. /**
  230. * Sorting helpers
  231. */
  232. int _ui_strnatcmp(const char *a, const char *b, int n)
  233. {
  234. #define UI_ISDIGIT(x) (((unsigned int)(x) - '0' ) < 10U)
  235. int result = 0, i;
  236. for(i = 1; *a && *b; a++, b++, i++) {
  237. if(UI_ISDIGIT(*a) && UI_ISDIGIT(*b)) {
  238. for(result = 0; UI_ISDIGIT(*a) && UI_ISDIGIT(*b); a++, b++, i++)
  239. if(!result) result = *a - *b;
  240. if(!UI_ISDIGIT(*a) && UI_ISDIGIT(*b)) return -1;
  241. if(!UI_ISDIGIT(*b) && UI_ISDIGIT(*a)) return +1;
  242. if(result || (!*a && !*b)) return result;
  243. }
  244. result = (*a >= 'a' && *a <= 'z' ? *a + 'A' - 'a' : *a) - (*b >= 'a' && *b <= 'z' ? *b + 'A' - 'a' : *b);
  245. if(result || (n > 0 && i >= n)) return result;
  246. }
  247. return *a - *b;
  248. #undef UI_ISDIGIT
  249. }
  250. static int _ui_path_nameasc(const void *a, const void *b)
  251. {
  252. ui_files_t *A = (ui_files_t*)a, *B = (ui_files_t*)b;
  253. return A->type != B->type ? A->type - B->type : _ui_strnatcmp(A->name, B->name, 0);
  254. }
  255. static int _ui_path_namedec(const void *a, const void *b)
  256. {
  257. ui_files_t *A = (ui_files_t*)a, *B = (ui_files_t*)b;
  258. return A->type != B->type ? A->type - B->type : _ui_strnatcmp(B->name, A->name, 0);
  259. }
  260. static int _ui_path_sizeasc(const void *a, const void *b)
  261. {
  262. ui_files_t *A = (ui_files_t*)a, *B = (ui_files_t*)b;
  263. return A->type != B->type ? A->type - B->type : A->size > B->size;
  264. }
  265. static int _ui_path_sizedec(const void *a, const void *b)
  266. {
  267. ui_files_t *A = (ui_files_t*)a, *B = (ui_files_t*)b;
  268. return A->type != B->type ? A->type - B->type : B->size > A->size;
  269. }
  270. static int _ui_path_dateasc(const void *a, const void *b)
  271. {
  272. ui_files_t *A = (ui_files_t*)a, *B = (ui_files_t*)b;
  273. return A->type != B->type ? A->type - B->type : A->date > B->date;
  274. }
  275. static int _ui_path_datedec(const void *a, const void *b)
  276. {
  277. ui_files_t *A = (ui_files_t*)a, *B = (ui_files_t*)b;
  278. return A->type != B->type ? A->type - B->type : B->date > A->date;
  279. }
  280. /**
  281. * Sort and filter files list
  282. */
  283. void _ui_path_filter(ui_path_t *path)
  284. {
  285. int i, l;
  286. if(!path || !path->files) return;
  287. switch(path->srt) {
  288. case 1: qsort(path->files, path->numfiles, sizeof(ui_files_t), _ui_path_namedec); break;
  289. case 2: qsort(path->files, path->numfiles, sizeof(ui_files_t), _ui_path_sizeasc); break;
  290. case 3: qsort(path->files, path->numfiles, sizeof(ui_files_t), _ui_path_sizedec); break;
  291. case 4: qsort(path->files, path->numfiles, sizeof(ui_files_t), _ui_path_dateasc); break;
  292. case 5: qsort(path->files, path->numfiles, sizeof(ui_files_t), _ui_path_datedec); break;
  293. default: qsort(path->files, path->numfiles, sizeof(ui_files_t), _ui_path_nameasc); break;
  294. }
  295. if(!path->filter[0]) {
  296. path->shfiles = path->numfiles;
  297. for(i = 0; i < path->numfiles; i++)
  298. path->files[i].idx = i;
  299. } else {
  300. l = strlen(path->filter);
  301. for(path->shfiles = i = 0; i < path->numfiles; i++)
  302. if(!_ui_strnatcmp(path->files[i].name, path->filter, l))
  303. path->files[path->shfiles++].idx = i;
  304. }
  305. }
  306. /**
  307. * Get file list
  308. */
  309. void _ui_path_getfiles(ui_t *ctx, ui_form_t *form)
  310. {
  311. #ifdef __WIN32__
  312. static wchar_t szFn[PATH_MAX + FILENAME_MAX + 1];
  313. WIN32_FIND_DATAW ffd;
  314. HANDLE h;
  315. struct _stat64 st;
  316. #else
  317. static char fn[PATH_MAX + FILENAME_MAX + 1];
  318. DIR *dir;
  319. struct dirent *ent;
  320. struct stat st;
  321. #endif
  322. ui_path_t *path;
  323. int i, j, k, l, dw, dh, tw, pw;
  324. char *s, *e, tmp[32];
  325. if(!ctx || !form || !form->ptr || !form->data) return;
  326. path = (ui_path_t*)form->data;
  327. /* get path buttons */
  328. path->pathsel = -1;
  329. path->numpath = path->pathscr = path->pathh = path->sw = path->tw = path->scr = path->sel = path->shfiles = 0;
  330. if(ctx->fnt && ctx->bbox) {
  331. dw = 0; dh = ctx->ds; form->t = ctx->dt;
  332. if(path->pathh < dh) path->pathh = dh;
  333. for(s = form->ptr, i = 0; *s && i < 255; s = e) {
  334. #ifdef __WIN32__
  335. if(!i) { e = s + 2; } else
  336. #endif
  337. { for(e = s + 1; *e && e[-1] != DIRSEP[0]; e++){} if(e[-1] != DIRSEP[0]) break; }
  338. (*ctx->bbox)(ctx->fnt, s, e, &dw, &dh, NULL, NULL);
  339. path->pathw[i++] = dw + 8;
  340. if(path->pathh < dh) path->pathh = dh;
  341. }
  342. pw = form->ew;
  343. if(path->flags & UI_PATH_NEWDIR) {
  344. path->flags |= UI_PATH_SEARCH;
  345. pw -= 24;
  346. }
  347. if(path->flags & UI_PATH_SEARCH) {
  348. tw = form->ew / 3;
  349. if(tw > 128) tw = 128;
  350. pw -= tw + 8;
  351. }
  352. path->numpath = i;
  353. for(tw = 0; i > 0 && tw + path->pathw[i - 1] < pw; tw += path->pathw[--i]);
  354. path->pathscr = i;
  355. if(form->str > 0 && form->str + 7 < ctx->txtc && ctx->txtv) {
  356. (*ctx->bbox)(ctx->fnt, ctx->txtv[form->str + 1], NULL, &path->sw, &dh, NULL, NULL);
  357. (*ctx->bbox)(ctx->fnt, "9999M", NULL, &dw, &dh, NULL, NULL);
  358. if(path->sw < dw) path->sw = dw;
  359. path->sw += 8 + 4;
  360. (*ctx->bbox)(ctx->fnt, "9999-99-99", NULL, &path->tw, &dh, NULL, NULL);
  361. path->tw += 8;
  362. for(i = 2; i < 8; i++) {
  363. if(i == 4 || i == 6) {
  364. sprintf(tmp, "99 %s", ctx->txtv[form->str + i]);
  365. (*ctx->bbox)(ctx->fnt, tmp, NULL, &dw, &dh, NULL, NULL);
  366. } else
  367. (*ctx->bbox)(ctx->fnt, ctx->txtv[form->str + i], NULL, &dw, &dh, NULL, NULL);
  368. if(path->tw < dw) path->tw = dw;
  369. }
  370. path->tw += 4 + ctx->sw;
  371. } else form->str = 0;
  372. }
  373. /* file list */
  374. #ifdef __WIN32__
  375. if(!((char*)form->ptr)[0]) {
  376. if((path->files = (ui_files_t*)realloc(path->files, 27 * sizeof(ui_files_t)))) {
  377. memset(path->files, 0, 27 * sizeof(ui_files_t));
  378. dw = (int)GetLogicalDrives();
  379. for(i = l = 0; l < 27; l++)
  380. if(dw & (1 << l)) {
  381. path->files[i].size = -1U;
  382. path->files[i].name[0] = 'A' + l;
  383. path->files[i++].name[1] = ':';
  384. }
  385. path->numfiles = i;
  386. }
  387. path->srt = 0;
  388. memset(path->filter, 0, FILENAME_MAX);
  389. } else {
  390. MultiByteToWideChar(CP_UTF8, 0, (char*)form->ptr, -1, szFn, PATH_MAX + FILENAME_MAX);
  391. l = wcslen(szFn);
  392. if(l > 0 && szFn[l - 1] != DIRSEP[0]) szFn[l++] = DIRSEP[0];
  393. wcscpy_s(szFn + l, FILENAME_MAX, L"*.*");
  394. if((h = FindFirstFileW(szFn, &ffd)) != INVALID_HANDLE_VALUE) {
  395. i = 0;
  396. do {
  397. if(!wcscmp(ffd.cFileName, L".") || !wcscmp(ffd.cFileName, L"..") ||
  398. ((path->flags & UI_PATH_ONLYDIR) && !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ||
  399. (!(path->flags & UI_PATH_HIDDEN) && (ffd.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM))))
  400. continue;
  401. i++;
  402. } while(FindNextFileW(h, &ffd) != 0);
  403. if((path->files = (ui_files_t*)realloc(path->files, (i + 1) * sizeof(ui_files_t)))) {
  404. h = FindFirstFileW(szFn, &ffd);
  405. i = 0;
  406. do {
  407. if(!wcscmp(ffd.cFileName, L".") || !wcscmp(ffd.cFileName, L"..") ||
  408. ((path->flags & UI_PATH_ONLYDIR) && !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) ||
  409. (!(path->flags & UI_PATH_HIDDEN) && (ffd.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM))))
  410. continue;
  411. wcscpy_s(szFn + l, FILENAME_MAX, ffd.cFileName);
  412. if(!_wstat64(szFn, &st)) {
  413. path->files[i].type = !(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
  414. path->files[i].size = st.st_size;
  415. path->files[i].date = st.st_mtime;
  416. WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, path->files[i].name, FILENAME_MAX, NULL, NULL);
  417. #else
  418. l = strlen(form->ptr);
  419. memcpy(fn, form->ptr, l);
  420. if(l && fn[l - 1] == DIRSEP[0]) fn[--l] = 0;
  421. if((dir = opendir(form->ptr))) {
  422. fn[l++] = DIRSEP[0];
  423. i = 0;
  424. while((ent = readdir(dir))) {
  425. if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
  426. ((path->flags & UI_PATH_ONLYDIR) && ent->d_type != DT_DIR) ||
  427. (!(path->flags & UI_PATH_HIDDEN) && ent->d_name[0] == '.'))
  428. continue;
  429. i++;
  430. }
  431. rewinddir(dir);
  432. if((path->files = (ui_files_t*)realloc(path->files, (i + 1) * sizeof(ui_files_t)))) {
  433. i = 0;
  434. while((ent = readdir(dir))) {
  435. if(!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..") ||
  436. ((path->flags & UI_PATH_ONLYDIR) && ent->d_type != DT_DIR) ||
  437. (!(path->flags & UI_PATH_HIDDEN) && ent->d_name[0] == '.'))
  438. continue;
  439. strncpy(fn + l, ent->d_name, FILENAME_MAX);
  440. if(!stat(fn, &st)) {
  441. path->files[i].type = ent->d_type != DT_DIR;
  442. path->files[i].size = st.st_size;
  443. path->files[i].date = st.st_mtime;
  444. strncpy(path->files[i].name, ent->d_name, FILENAME_MAX);
  445. #endif
  446. if(path->exts[0]) {
  447. if((s = strrchr(path->files[i].name, '.')))
  448. for(s++, j = strlen(s), e = path->exts, k = 1; k > 0 && e < path->exts + 256 && *e; e += k + 1) {
  449. k = strlen(e);
  450. if(j == k && !_ui_strnatcmp(s, e, j)) { i++; break; }
  451. }
  452. } else
  453. i++;
  454. #ifdef __WIN32__
  455. }
  456. } while(FindNextFileW(h, &ffd) != 0);
  457. }
  458. FindClose(h);
  459. path->numfiles = i;
  460. }
  461. }
  462. #else
  463. }
  464. }
  465. }
  466. closedir(dir);
  467. path->numfiles = i;
  468. }
  469. #endif
  470. _ui_path_filter(path);
  471. if(path->fn[0]) {
  472. for(i = 0; i < path->shfiles; i++)
  473. if(!strcmp(path->files[path->files[i].idx].name, path->fn)) {
  474. path->sel = i;
  475. path->scr = i * path->pathh;
  476. }
  477. memset(path->fn, 0, FILENAME_MAX);
  478. }
  479. }
  480. /**
  481. * Create a directory
  482. */
  483. void _ui_path_mkdir(char *fn)
  484. {
  485. #ifdef __WIN32__
  486. wchar_t szFn[PATH_MAX + FILENAME_MAX + 1];
  487. if(!fn || !*fn) return;
  488. MultiByteToWideChar(CP_UTF8, 0, fn, -1, szFn, PATH_MAX + FILENAME_MAX);
  489. _wmkdir(szFn);
  490. #else
  491. if(!fn || !*fn) return;
  492. mkdir(fn, 0775);
  493. #endif
  494. }
  495. /**
  496. * Start file picker
  497. */
  498. void ui_path_start(ui_t *ctx, ui_form_t *form)
  499. {
  500. #ifdef __WIN32__
  501. wchar_t szFn[PATH_MAX + FILENAME_MAX + 1], szPath[PATH_MAX + FILENAME_MAX + 1];
  502. struct _stat64 st;
  503. #else
  504. struct stat st;
  505. #endif
  506. char path[PATH_MAX + FILENAME_MAX + 1], *s;
  507. if(!ctx || !form || !form->ptr || !form->data) return;
  508. if(!((char*)form->ptr)[0]) strcpy(form->ptr, ".");
  509. memset(path, 0, sizeof(path));
  510. #ifdef __WIN32__
  511. MultiByteToWideChar(CP_UTF8, 0, form->ptr, -1, szFn, PATH_MAX + FILENAME_MAX);
  512. memset(szPath, 0, sizeof(szPath));
  513. GetFullPathNameW(szFn, PATH_MAX + FILENAME_MAX, szPath, NULL);
  514. if(!szPath[0]) _wgetcwd(szPath, PATH_MAX + FILENAME_MAX);
  515. WideCharToMultiByte(CP_UTF8, 0, szPath, -1, path, PATH_MAX + FILENAME_MAX, NULL, NULL);
  516. if(_wstat64(szPath, &st)) st.st_mode = 0;
  517. #else
  518. realpath(form->ptr, path);
  519. if(!path[0]) getcwd(path, PATH_MAX + FILENAME_MAX);
  520. if(stat(path, &st)) st.st_mode = 0;
  521. #endif
  522. if(S_ISDIR(st.st_mode)) {
  523. if(path[strlen(path) - 1] != DIRSEP[0])
  524. strcat(path, DIRSEP);
  525. memset(((ui_path_t*)form->data)->fn, 0, FILENAME_MAX);
  526. } else {
  527. if((s = strrchr(path, DIRSEP[0]))) {
  528. strncpy(((ui_path_t*)form->data)->fn, s + 1, FILENAME_MAX);
  529. s[1] = 0;
  530. }
  531. }
  532. strncpy((char*)form->ptr, path, form->max - 1);
  533. if((char*)form->ptr == ctx->buf)
  534. ctx->end = ctx->cur = ctx->buf + strlen(ctx->buf);
  535. _ui_path_getfiles(ctx, form);
  536. }
  537. /**
  538. * Stop file picker
  539. */
  540. int ui_path_stop(ui_t *ctx, ui_form_t *form)
  541. {
  542. ui_path_t *path;
  543. if(!ctx || !form || !form->data) return UI_ERR_BADINP;
  544. path = (ui_path_t*)form->data;
  545. if(path->files) free(path->files);
  546. path->files = NULL;
  547. path->numfiles = path->shfiles = 0;
  548. return UI_OK;
  549. }
  550. /**
  551. * Process events
  552. */
  553. int ui_path_ctrl(ui_t *ctx, int x, int y, int w, int h, ui_form_t *form, ui_event_t *evt)
  554. {
  555. ui_files_t *file;
  556. ui_path_t *path;
  557. int sel = -1, clk = -1, i, j = w / 3, ph, pt, ps, X = x, Y = y, t, b;
  558. char *s, *e;
  559. if(!ctx || !form || !form->ptr || !form->data || !evt) return UI_ERR_BADINP;
  560. path = (ui_path_t*)form->data;
  561. ph = path->pathh;
  562. if((path->flags & UI_PATH_NEWDIR) && path->pathh < 16) path->pathh = 16;
  563. ph += 6;
  564. if(ctx->mousex >= x && ctx->mousex < x + w) {
  565. if(ctx->mousey >= y && ctx->mousey <= y + ph) {
  566. j = w / 3;
  567. if(j > 128) j = 128;
  568. /* one of the path's components */
  569. for(X = x, i = path->pathscr; i < path->numpath; X += path->pathw[i], i++)
  570. if(ctx->mousex >= X && ctx->mousex < X + path->pathw[i] - 2) { sel = i; break; }
  571. X = x + w;
  572. /* search or newdir button */
  573. if(path->flags & UI_PATH_NEWDIR) { X -= 24; if(ctx->mousex >= X + 4) sel = -2; }
  574. if(path->flags & UI_PATH_SEARCH) { X -= j; if(ctx->mousex >= X && ctx->mousex < X + j) sel = -3; }
  575. }
  576. y += ph + 2; h -= ph + 2;
  577. x++; y++; w -= 2; h -= 2;
  578. pt = w - path->tw; ps = pt - path->sw;
  579. if(form->str > 0) {
  580. /* one of the headers */
  581. if(ctx->mousey >= y && ctx->mousey < y + path->pathh + 4) {
  582. if(ctx->mousex >= x && ctx->mousex < x + ps) sel = -4; else
  583. if(ctx->mousex >= x + ps && ctx->mousex < x + ps + path->sw) sel = -5; else
  584. if(ctx->mousex >= x + pt && ctx->mousex < x + pt + path->tw) sel = -6;
  585. }
  586. y += path->pathh + 8; h -= path->pathh + 8;
  587. }
  588. if(ctx->mousey >= y && ctx->mousey < y + h) {
  589. if(ctx->mousex >= x + w - ctx->sw) sel = -7; else
  590. if(path->pathh > 0) clk = (ctx->mousey - y + path->scr) / path->pathh;
  591. }
  592. }
  593. if(evt->type == UI_EVT_KEY) {
  594. if(!memcmp(evt->key, "Up", 3)) {
  595. if(path->sel > 0) path->sel--;
  596. if(path->sel * path->pathh < path->scr) path->scr = path->sel * path->pathh;
  597. if((path->sel + 1) * path->pathh - path->scr > h) path->scr = (path->sel + 1) * path->pathh - h;
  598. evt->type = UI_EVT_NONE;
  599. } else
  600. if(!memcmp(evt->key, "Down", 5)) {
  601. if(path->sel + 1 < path->shfiles) path->sel++;
  602. if(path->sel * path->pathh < path->scr) path->scr = path->sel * path->pathh;
  603. if((path->sel + 1) * path->pathh - path->scr > h) path->scr = (path->sel + 1) * path->pathh - h;
  604. evt->type = UI_EVT_NONE;
  605. } else
  606. if(!ctx->str && (!memcmp(evt->key, "Left", 5) || evt->key[0] == '\b') && path->numpath > 1) {
  607. sel = path->numpath - 2; goto back;
  608. } else
  609. if(!ctx->str && (!memcmp(evt->key, "Right", 6) || evt->key[0] == '\n') && path->sel >= 0) {
  610. clk = path->sel; goto enter;
  611. } else
  612. if(ctx->str == path->filter && ctx->buf) {
  613. /* user typed in the filter input */
  614. strcpy(path->filter, ctx->buf);
  615. _ui_path_filter(path);
  616. }
  617. } else
  618. if(evt->type == UI_EVT_MOUSE) {
  619. if(evt->btn & UI_BTN_RELEASE) {
  620. if(path->pathsel == sel) {
  621. switch(sel) {
  622. case -2:
  623. if(path->filter[0]) {
  624. i = strlen((char*)form->ptr);
  625. t = strlen(path->filter);
  626. if(i + t + 2 < form->max) {
  627. s = (char*)form->ptr + i; if(i && s[-1] != DIRSEP[0]) *s++ = DIRSEP[0];
  628. strcpy(s, path->filter);
  629. s += t; if(s[-1] == DIRSEP[0]) *--s = 0;
  630. memset(path->fn, 0, FILENAME_MAX);
  631. _ui_path_mkdir(form->ptr); *s++ = DIRSEP[0]; *s = 0;
  632. _ui_path_getfiles(ctx, form);
  633. }
  634. }
  635. break;
  636. case -3: _ui_text_start(ctx, X, Y, j, ph, form, path->filter, sizeof(path->filter) - 1); return UI_OK;
  637. case -4: if(path->srt != 0) path->srt = 0; else path->srt = 1; _ui_path_filter(path); path->sel = path->scr = 0; break;
  638. case -5: if(path->srt != 2) path->srt = 2; else path->srt = 3; _ui_path_filter(path); path->sel = path->scr = 0; break;
  639. case -6: if(path->srt != 4) path->srt = 4; else path->srt = 5; _ui_path_filter(path); path->sel = path->scr = 0; break;
  640. default:
  641. back: if(sel >= 0 && sel < path->numpath) {
  642. s = form->ptr;
  643. #ifdef __WIN32__
  644. if(sel)
  645. #endif
  646. {
  647. for(i = 0; *s && i <= sel; s = e, i++) {
  648. #ifdef __WIN32__
  649. if(!i) { e = s + 2; } else
  650. #endif
  651. { for(e = s + 1; *e && e[-1] != DIRSEP[0]; e++){} if(e[-1] != DIRSEP[0]) break; }
  652. }
  653. }
  654. for(e = path->fn, i = 0; s[i] && s[i] != DIRSEP[0]; i++)
  655. e[i] = s[i];
  656. e[i] = 0;
  657. *s = 0;
  658. memset(path->filter, 0, sizeof(path->filter));
  659. _ui_path_getfiles(ctx, form);
  660. }
  661. break;
  662. }
  663. }
  664. path->pathsel = -1;
  665. ctx->vscr = NULL;
  666. } else
  667. if(evt->btn & (UI_BTN_U | UI_BTN_D)) {
  668. i = h / 10; if(i < 4) i = 4;
  669. if(evt->btn & UI_BTN_U) path->scr -= i; else path->scr += i;
  670. i = path->shfiles * path->pathh - h;
  671. if(path->scr > i) path->scr = i;
  672. if(path->scr < 0) path->scr = 0;
  673. } else
  674. if(evt->btn & UI_BTN_L) {
  675. path->pathsel = sel;
  676. if(sel == -7) {
  677. /* scrollbar */
  678. memset(&_ui_path_vscr, 0, sizeof(_ui_path_vscr));
  679. _ui_path_vscr.type = UI_VSCRBAR;
  680. _ui_path_vscr.max = path->shfiles * path->pathh;
  681. _ui_path_vscr.ptr = &path->scr;
  682. ctx->vscr = &_ui_path_vscr;
  683. ctx->sm = _ui_path_vscr.max - h;
  684. t = _ui_scr(h, path->scr, _ui_path_vscr.max, ctx->sw, &b);
  685. ctx->sb = ctx->mousey >= y + t && ctx->mousey < y + t + b ? ctx->mousey - y - t : b / 2;
  686. ctx->s1 = y; ctx->s2 = y + h - b + ctx->sb;
  687. } else
  688. if(clk >= 0) {
  689. /* file list */
  690. if(path->sel == clk) {
  691. enter: file = &path->files[path->files[clk].idx];
  692. i = strlen((char*)form->ptr);
  693. t = strlen(file->name);
  694. if(i + t + 2 < form->max) {
  695. path->sel = -1;
  696. s = (char*)form->ptr + i;
  697. if(i && s[-1] != DIRSEP[0]) { *s++ = DIRSEP[0]; *s = 0; }
  698. strcpy(s, file->name);
  699. memset(path->filter, 0, sizeof(path->filter));
  700. if(!file->type) {
  701. /* directory clicked */
  702. s += t; *s++ = DIRSEP[0]; *s = 0;
  703. if(path->select && (*path->select)(form->ptr, 1)) goto sel;
  704. _ui_path_getfiles(ctx, form);
  705. } else {
  706. /* file clicked */
  707. if(!path->select || (*path->select)(form->ptr, 0)) {
  708. sel: if((char*)form->ptr == ctx->buf)
  709. ctx->end = ctx->cur = ctx->buf + strlen(ctx->buf);
  710. ctx->flags |= UI_CLOSE | UI_DONE;
  711. _ui_text_ctrl(ctx, evt);
  712. } else *s = 0;
  713. }
  714. }
  715. } else path->sel = clk;
  716. }
  717. }
  718. }
  719. return UI_OK;
  720. }
  721. /**
  722. * Recalculate form element bounding box
  723. */
  724. int ui_file_bbox(ui_t *ctx, int x, int y, int w, int h, ui_form_t *form, int *dw, int *dh)
  725. {
  726. if(!ctx || !form || !dw || !dh) return UI_ERR_BADINP;
  727. (void)x; (void)y; (void)w; (void)h;
  728. if(ctx->fnt && ctx->bbox && form->ptr) {
  729. /* if string is empty, use a default one to get height and baseline correctly */
  730. (*ctx->bbox)(ctx->fnt, form->ptr && *((char*)form->ptr) ? (char*)form->ptr : "Ag", NULL, dw, dh, &form->l, &form->t);
  731. if(!form->ptr || !*((char*)form->ptr)) *dw = 0;
  732. (*dh) += 4;
  733. }
  734. if(form->ew > 0) *dw = form->ew;
  735. return UI_OK;
  736. }
  737. /**
  738. * Display a text input field and a file picker
  739. */
  740. int ui_file_view(ui_t *ctx, int x, int y, int w, int h, ui_form_t *form)
  741. {
  742. int i;
  743. if(!ctx || !form) return UI_ERR_BADINP;
  744. if(form == _ui_file_popup) {
  745. /* draw the file input box and a path box below on a popup */
  746. i = form->min;
  747. if(!(form->flags & UI_NOBORDER)) {
  748. _ui_rect(ctx, x, y, w, h, ctx->theme[UI_IB], ctx->theme[UI_IBG], ctx->theme[UI_IB]);
  749. x++; y++; w -= 2; h -= 2; i -= 2;
  750. }
  751. if(ctx->skin[UI_INP].buf)
  752. _ui_blit(ctx, x, y, w, h, &ctx->skin[UI_INP], 0, 0, 0);
  753. else
  754. _ui_frect(ctx, x, y, w, h, ctx->theme[UI_IBG]);
  755. _ui_text(ctx, x, y, w, i, form->l, form->t, form->flags, 9, form->ptr, NULL);
  756. ui_path_view(ctx, form->ex, form->ey, form->ew, form->eh, form);
  757. } else
  758. /* draw the input field */
  759. _ui_text(ctx, x, y, w, h, form->l, form->t, form->flags, ctx->text == form, form->ptr, NULL);
  760. return UI_OK;
  761. }
  762. /**
  763. * Process events
  764. */
  765. int ui_file_ctrl(ui_t *ctx, int x, int y, int w, int h, ui_form_t *form, ui_event_t *evt)
  766. {
  767. if(!ctx || !form || !evt) return UI_ERR_BADINP;
  768. if(!ctx->popup && evt->type == UI_EVT_MOUSE && (evt->btn & UI_BTN_L)) {
  769. /* user clicked on the text input field */
  770. if(form->ptr && form->max > 0) {
  771. evt->type = UI_EVT_NONE;
  772. ((char*)form->ptr)[form->max - 1] = 0;
  773. _ui_text_start(ctx, x, y, w, h, form, form->ptr, form->max);
  774. memcpy(_ui_file_popup, form, sizeof(ui_form_t));
  775. _ui_file_popup[0].ex = x + 4;
  776. _ui_file_popup[0].ey = y + h + 4;
  777. _ui_file_popup[0].ew = w - 8;
  778. _ui_file_popup[0].eh = form->min > h ? form->min : 256;
  779. _ui_file_popup[0].min = h;
  780. _ui_file_popup[0].ptr = ctx->buf;
  781. _ui_file_popup[0].data = &_ui_path_ctx;
  782. memset(&_ui_path_ctx, 0, sizeof(_ui_path_ctx));
  783. ui_path_start(ctx, _ui_file_popup);
  784. ctx->popup = _ui_file_popup;
  785. ctx->px = x;
  786. ctx->py = y;
  787. ctx->pw = w;
  788. ctx->ph = h + _ui_file_popup[0].eh + 8;
  789. ctx->pe = &ui_file_ctrl;
  790. ctx->dr = &ui_file_view;
  791. }
  792. } else
  793. if(ctx->popup) {
  794. /* popup event handler */
  795. ui_path_ctrl(ctx, ctx->popup->ex, ctx->popup->ey, ctx->popup->ew, ctx->popup->eh, ctx->popup, evt);
  796. }
  797. return UI_OK;
  798. }
  799. #ifdef __cplusplus
  800. }
  801. #endif
  802. #endif /* UI_FILE_H */