Files
milsko/src/dialog/filechooser.c
NishiOwO 1853cd1e7f fix
2025-12-15 11:42:09 +09:00

573 lines
14 KiB
C

#include <Mw/Milsko.h>
#include "../external/stb_ds.h"
typedef struct filechooser {
char* path;
char** history;
int history_seek;
int dir_only;
MwDirectoryEntry** entries;
MwDirectoryEntry** sorted_entries;
MwWidget nav;
MwWidget files;
MwWidget addr;
MwWidget addr_back;
MwWidget addr_fwd;
MwWidget addr_up;
MwWidget filename_label;
MwWidget filename;
MwWidget okay;
MwWidget cancel;
MwLLPixmap dir;
MwLLPixmap file;
MwLLPixmap back;
MwLLPixmap forward;
MwLLPixmap up;
MwLLPixmap computer;
} filechooser_t;
static void scan(MwWidget handle, const char* path, int record);
static void destroy(MwWidget handle) {
filechooser_t* fc = handle->opaque;
int i;
for(i = 0; i < arrlen(fc->history); i++) free(fc->history[i]);
arrfree(fc->history);
arrfree(fc->sorted_entries);
for(i = 0; i < arrlen(fc->entries); i++) MwDirectoryFreeEntry(fc->entries[i]);
arrfree(fc->entries);
MwLLDestroyPixmap(fc->dir);
MwLLDestroyPixmap(fc->file);
MwLLDestroyPixmap(fc->back);
MwLLDestroyPixmap(fc->forward);
MwLLDestroyPixmap(fc->up);
MwLLDestroyPixmap(fc->computer);
free(handle->opaque);
MwDestroyWidget(handle);
}
static void cancel(MwWidget handle, void* user, void* call) {
(void)user;
(void)call;
destroy(handle->parent);
}
static void cancel_window(MwWidget handle, void* user, void* call) {
(void)user;
(void)call;
destroy(handle);
}
static void okay(MwWidget handle, void* user, void* call) {
(void)user;
(void)call;
destroy(handle->parent);
}
static void msgbox_okay(MwWidget handle, void* user, void* call) {
(void)user;
(void)call;
MwMessageBoxDestroy(handle->parent);
}
static void files_activate(MwWidget handle, void* user, void* call) {
int index = *(int*)call;
filechooser_t* fc = handle->parent->opaque;
(void)user;
if(fc->sorted_entries[index - 1]->type == MwDIRECTORY_DIRECTORY) {
char* p = MwDirectoryJoin(fc->path, fc->sorted_entries[index - 1]->name);
scan(handle->parent, p, 1);
free(p);
} else {
char* p;
p = MwDirectoryJoin(fc->path, fc->sorted_entries[MwGetInteger(fc->files, MwNvalue) - 1]->name);
MwDispatchUserHandler(handle->parent, fc->dir_only ? MwNdirectoryChosenHandler : MwNfileChosenHandler, p);
free(p);
okay(fc->okay, NULL, NULL);
}
}
static void okay_activate(MwWidget handle, void* user, void* call) {
filechooser_t* fc = handle->parent->opaque;
char* t = (char*)MwGetText(fc->filename, MwNtext);
struct stat s;
char* p = NULL;
(void)user;
(void)call;
if(t != NULL && t[0] == '/') {
p = MwStringDuplicate(t);
} else {
p = MwDirectoryJoin(fc->path, t);
}
if(t == NULL || strlen(t) == 0) {
MwWidget msgbox = MwMessageBox(handle->parent, fc->dir_only ? "You have to type directory!" : "You have to type filename!", "Error", MwMB_ICONERROR | MwMB_BUTTONOK);
MwAddUserHandler(MwMessageBoxGetChild(msgbox, MwMB_BUTTONOK), MwNactivateHandler, msgbox_okay, NULL);
} else if(stat(p, &s) != 0) {
MwWidget msgbox = MwMessageBox(handle->parent, "File does not exist!", "Error", MwMB_ICONERROR | MwMB_BUTTONOK);
MwAddUserHandler(MwMessageBoxGetChild(msgbox, MwMB_BUTTONOK), MwNactivateHandler, msgbox_okay, NULL);
#ifdef _S_IFDIR
} else if(fc->dir_only ? ((s.st_mode & _S_IFDIR) != _S_IFDIR) : 0) {
#else
} else if(fc->dir_only ? !S_ISDIR(s.st_mode) : 0) {
#endif
MwWidget msgbox = MwMessageBox(handle->parent, "File is not permitted here!", "Error", MwMB_ICONERROR | MwMB_BUTTONOK);
MwAddUserHandler(MwMessageBoxGetChild(msgbox, MwMB_BUTTONOK), MwNactivateHandler, msgbox_okay, NULL);
} else {
MwDispatchUserHandler(handle->parent, fc->dir_only ? MwNdirectoryChosenHandler : MwNfileChosenHandler, p);
okay(fc->okay, NULL, NULL);
}
if(p != NULL) free(p);
}
static void nav_activate(MwWidget handle, void* user, void* call) {
int index = *(int*)call;
const char* e = MwListBoxGet(handle, index);
(void)user;
if(strcmp(e, "Home") == 0) {
#ifdef _WIN32
#else
struct passwd* p = getpwuid(getuid());
scan(handle->parent, p->pw_dir, 1);
#endif
}
}
static void addr_up_activate(MwWidget handle, void* user, void* call) {
filechooser_t* fc = handle->parent->opaque;
char* p = MwDirectoryJoin(fc->path, "..");
(void)user;
(void)call;
scan(handle->parent, p, 1);
free(p);
}
static void addr_back_activate(MwWidget handle, void* user, void* call) {
filechooser_t* fc = handle->parent->opaque;
(void)user;
(void)call;
if(fc->history_seek > 1) {
fc->history_seek -= 2;
scan(handle->parent, fc->history[fc->history_seek], 0);
fc->history_seek++;
}
}
static void addr_fwd_activate(MwWidget handle, void* user, void* call) {
filechooser_t* fc = handle->parent->opaque;
(void)user;
(void)call;
if(fc->history_seek < arrlen(fc->history)) {
scan(handle->parent, fc->history[fc->history_seek++], 0);
}
}
static void addr_activate(MwWidget handle, void* user, void* call) {
(void)user;
(void)call;
scan(handle->parent, MwGetText(handle, MwNtext), 1);
}
static void layout(MwWidget handle) {
filechooser_t* fc = handle->opaque;
int w = MwGetInteger(handle, MwNwidth);
int h = MwGetInteger(handle, MwNheight);
int wx, wy, ww, wh;
wx = 5;
wy = 5 + 24 + 5;
ww = 160;
wh = h - 10 - 24 - 5 - 24 - 5 - 24 - 5;
if(fc->nav == NULL) {
MwListBoxPacket* p = MwListBoxCreatePacket();
int index;
index = MwListBoxPacketInsert(p, -1);
MwListBoxPacketSet(p, index, 0, "Home");
MwListBoxPacketSetIcon(p, index, fc->computer);
fc->nav = MwVaCreateWidget(MwListBoxClass, "nav", handle, wx, wy, ww, wh,
MwNleftPadding, 16,
NULL);
MwListBoxInsert(fc->nav, -1, p);
MwAddUserHandler(fc->nav, MwNactivateHandler, nav_activate, NULL);
MwListBoxDestroyPacket(p);
} else {
MwVaApply(fc->nav,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5 + 160 + 5;
wy = 5 + 24 + 5;
ww = w - 5 - 160 - 5 - 5;
wh = h - 10 - 24 - 5 - 24 - 5 - 24 - 5;
if(fc->files == NULL) {
fc->files = MwVaCreateWidget(MwListBoxClass, "files", handle, wx, wy, ww, wh,
MwNhasHeading, 1,
MwNleftPadding, 16,
NULL);
MwAddUserHandler(fc->files, MwNactivateHandler, files_activate, NULL);
} else {
MwVaApply(fc->files,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5 + ((24 + 5) * 3);
wy = 5;
ww = w - 10 - ((24 + 5) * 3);
wh = 24;
if(fc->addr == NULL) {
fc->addr = MwCreateWidget(MwEntryClass, "addr", handle, wx, wy, ww, wh);
MwAddUserHandler(fc->addr, MwNactivateHandler, addr_activate, NULL);
} else {
MwVaApply(fc->addr,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5;
wy = 5;
ww = 24;
wh = 24;
if(fc->addr_back == NULL) {
fc->addr_back = MwVaCreateWidget(MwButtonClass, "addr_back", handle, wx, wy, ww, wh,
MwNpixmap, fc->back,
NULL);
MwAddUserHandler(fc->addr_back, MwNactivateHandler, addr_back_activate, NULL);
} else {
MwVaApply(fc->addr_back,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5 + 24 + 5;
wy = 5;
ww = 24;
wh = 24;
if(fc->addr_fwd == NULL) {
fc->addr_fwd = MwVaCreateWidget(MwButtonClass, "addr_fwd", handle, wx, wy, ww, wh,
MwNpixmap, fc->forward,
NULL);
MwAddUserHandler(fc->addr_fwd, MwNactivateHandler, addr_fwd_activate, NULL);
} else {
MwVaApply(fc->addr_fwd,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5 + 24 + 5 + 24 + 5;
wy = 5;
ww = 24;
wh = 24;
if(fc->addr_up == NULL) {
fc->addr_up = MwVaCreateWidget(MwButtonClass, "addr_up", handle, wx, wy, ww, wh,
MwNpixmap, fc->up,
NULL);
MwAddUserHandler(fc->addr_up, MwNactivateHandler, addr_up_activate, NULL);
} else {
MwVaApply(fc->addr_up,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5 + ((24 + 5) * 3);
wy = h - 5 - 24 - 5 - 24;
ww = w - 10 - ((24 + 5) * 3);
wh = 24;
if(fc->filename == NULL) {
fc->filename = MwCreateWidget(MwEntryClass, "filename", handle, wx, wy, ww, wh);
MwAddUserHandler(fc->filename, MwNactivateHandler, okay_activate, NULL);
} else {
MwVaApply(fc->filename,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = 5;
wy = h - 5 - 24 - 5 - 24;
ww = ((24 + 5) * 3);
wh = 24;
if(fc->filename_label == NULL) {
fc->filename_label = MwVaCreateWidget(MwLabelClass, "filename_label", handle, wx, wy, ww, wh,
MwNtext, fc->dir_only ? "Directory:" : "Filename:",
NULL);
} else {
MwVaApply(fc->filename_label,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = w - 5 - 24 * 3 - 5 - 24 * 3;
wy = h - 5 - 24;
ww = 24 * 3;
wh = 24;
if(fc->okay == NULL) {
fc->okay = MwVaCreateWidget(MwButtonClass, "ok", handle, wx, wy, ww, wh,
MwNtext, "Okay",
NULL);
MwAddUserHandler(fc->okay, MwNactivateHandler, okay_activate, NULL);
} else {
MwVaApply(fc->okay,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
wx = w - 5 - 24 * 3;
wy = h - 5 - 24;
ww = 24 * 3;
wh = 24;
if(fc->cancel == NULL) {
fc->cancel = MwVaCreateWidget(MwButtonClass, "cancel", handle, wx, wy, ww, wh,
MwNtext, "Cancel",
NULL);
MwAddUserHandler(fc->cancel, MwNactivateHandler, cancel, NULL);
} else {
MwVaApply(fc->cancel,
MwNx, wx,
MwNy, wy,
MwNwidth, ww,
MwNheight, wh,
NULL);
}
}
static void resize(MwWidget handle, void* user, void* call) {
(void)user;
(void)call;
layout(handle);
}
static int qsort_files(const void* a, const void* b) {
MwDirectoryEntry* aent = *(MwDirectoryEntry**)a;
MwDirectoryEntry* bent = *(MwDirectoryEntry**)b;
return strcmp(aent->name, bent->name);
}
static void scan(MwWidget handle, const char* path, int record) {
filechooser_t* fc = handle->opaque;
void* dir = MwDirectoryOpen(path);
int i;
MwListBoxPacket* p = MwListBoxCreatePacket();
int index;
if(dir != NULL) {
MwDirectoryEntry* entry;
arrfree(fc->sorted_entries);
for(i = 0; i < arrlen(fc->entries); i++) MwDirectoryFreeEntry(fc->entries[i]);
arrfree(fc->entries);
while((entry = MwDirectoryRead(dir)) != NULL) arrput(fc->entries, entry);
MwDirectoryClose(dir);
qsort(fc->entries, arrlen(fc->entries), sizeof(MwDirectoryEntry*), qsort_files);
if(record) {
char* str = MwStringDuplicate(path);
while(arrlen(fc->history) > fc->history_seek) {
free(fc->history[fc->history_seek]);
arrdel(fc->history, fc->history_seek);
}
arrins(fc->history, fc->history_seek, str);
fc->history_seek++;
}
} else {
MwWidget msgbox = MwMessageBox(handle, "Directory does not exist!", "Error", MwMB_ICONERROR | MwMB_BUTTONOK);
MwAddUserHandler(MwMessageBoxGetChild(msgbox, MwMB_BUTTONOK), MwNactivateHandler, msgbox_okay, NULL);
return;
}
if(fc->path != NULL) free(fc->path);
fc->path = MwStringDuplicate(path);
MwVaApply(fc->addr,
MwNtext, path,
NULL);
if(fc->dir_only) {
MwVaApply(fc->filename,
MwNtext, path,
NULL);
} else {
MwVaApply(fc->filename,
MwNtext, "",
NULL);
}
MwListBoxReset(fc->files);
MwListBoxSetWidth(fc->files, 0, -128 - 96);
MwListBoxSetWidth(fc->files, 1, 128);
MwListBoxSetWidth(fc->files, 2, 0);
index = MwListBoxPacketInsert(p, -1);
MwListBoxPacketSet(p, index, -1, "Name");
MwListBoxPacketSet(p, index, -1, "Date modified");
MwListBoxPacketSet(p, index, -1, "Size");
for(i = 0; i < arrlen(fc->entries); i++) {
if(strcmp(fc->entries[i]->name, ".") == 0 || strcmp(fc->entries[i]->name, "..") == 0) continue;
if(fc->entries[i]->type == MwDIRECTORY_DIRECTORY) {
char date[128];
MwStringTime(date, fc->entries[i]->mtime);
index = MwListBoxPacketInsert(p, -1);
MwListBoxPacketSetIcon(p, index, fc->dir);
MwListBoxPacketSet(p, index, -1, fc->entries[i]->name);
MwListBoxPacketSet(p, index, -1, date);
MwListBoxPacketSet(p, index, -1, NULL);
arrput(fc->sorted_entries, fc->entries[i]);
}
}
for(i = 0; i < arrlen(fc->entries); i++) {
if(fc->entries[i]->type == MwDIRECTORY_FILE && !fc->dir_only) {
char date[128];
char size[128];
MwStringTime(date, fc->entries[i]->mtime);
MwStringSize(size, fc->entries[i]->size);
index = MwListBoxPacketInsert(p, -1);
MwListBoxPacketSetIcon(p, index, fc->file);
MwListBoxPacketSet(p, index, -1, fc->entries[i]->name);
MwListBoxPacketSet(p, index, -1, date);
MwListBoxPacketSet(p, index, -1, size);
arrput(fc->sorted_entries, fc->entries[i]);
}
}
MwListBoxInsert(fc->files, -1, p);
MwListBoxDestroyPacket(p);
}
MwWidget MwFileChooserEx(MwWidget handle, const char* title, int dir) {
MwWidget window;
int w, h;
filechooser_t* fc = malloc(sizeof(*fc));
char* path;
MwLLPixmap icon;
int wx;
int wy;
memset(fc, 0, sizeof(*fc));
w = 700;
h = w * 2 / 3;
if(handle == NULL) {
wx = wy = MwDEFAULT;
} else {
wx = MwGetInteger(handle, MwNx) + (MwGetInteger(handle, MwNwidth) - w) / 2;
wy = MwGetInteger(handle, MwNy) + (MwGetInteger(handle, MwNheight) - h) / 2;
}
window = MwVaCreateWidget(MwWindowClass, "filechooser", NULL, wx, wy, w, h,
MwNtitle, title,
NULL);
if(handle != NULL) MwReparent(window, handle);
fc->history_seek = 0;
fc->dir = MwLoadIcon(window, MwIconDirectory);
fc->file = MwLoadIcon(window, MwIconFile);
fc->back = MwLoadIcon(window, MwIconBack);
fc->forward = MwLoadIcon(window, MwIconForward);
fc->up = MwLoadIcon(window, MwIconUp);
fc->computer = MwLoadIcon(window, MwIconComputer);
fc->dir_only = dir;
icon = MwLoadIcon(window, MwIconSearch);
MwVaApply(window,
MwNiconPixmap, icon,
NULL);
window->opaque = fc;
layout(window);
MwAddUserHandler(window, MwNresizeHandler, resize, NULL);
MwAddUserHandler(window, MwNcloseHandler, cancel_window, NULL);
path = MwDirectoryCurrent();
scan(window, path, 1);
free(path);
MwLLBeginStateChange(window->lowlevel);
MwLLMakePopup(window->lowlevel, handle == NULL ? NULL : handle->lowlevel);
MwLLEndStateChange(window->lowlevel);
return window;
}
MwWidget MwFileChooser(MwWidget handle, const char* title) {
return MwFileChooserEx(handle, title, 0);
}