mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-28 04:23:40 +01:00
jfileselect: show file size + sort folders first
This commit is contained in:
parent
4e01a99b0b
commit
9a70a6398e
2 changed files with 157 additions and 68 deletions
|
@ -27,13 +27,14 @@ typedef struct {
|
||||||
|
|
||||||
/* Folder currently being browsed */
|
/* Folder currently being browsed */
|
||||||
char *path;
|
char *path;
|
||||||
/* Corresponding directory stream */
|
/* List of entries */
|
||||||
DIR *dp;
|
void *entries;
|
||||||
/* Entry previously validated (with EXE) */
|
/* Number of entries in the current folder */
|
||||||
|
int entry_count;
|
||||||
|
|
||||||
|
/* Full path to file last selected with EXE */
|
||||||
char *selected_file;
|
char *selected_file;
|
||||||
|
|
||||||
/* Number of entries in the current folder */
|
|
||||||
int folder_entries;
|
|
||||||
/* Current cursor position (0 .. folder_entries-1) */
|
/* Current cursor position (0 .. folder_entries-1) */
|
||||||
int16_t cursor;
|
int16_t cursor;
|
||||||
/* Current scroll position */
|
/* Current scroll position */
|
||||||
|
@ -43,6 +44,8 @@ typedef struct {
|
||||||
|
|
||||||
/* Additional pixels of spacing per line (base is font->height) */
|
/* Additional pixels of spacing per line (base is font->height) */
|
||||||
int8_t line_spacing;
|
int8_t line_spacing;
|
||||||
|
/* Whether to show the file size on the right */
|
||||||
|
bool show_file_size;
|
||||||
/* Rendering font */
|
/* Rendering font */
|
||||||
font_t const *font;
|
font_t const *font;
|
||||||
|
|
||||||
|
@ -84,5 +87,6 @@ char const *jfileselect_current_folder(jfileselect *fs);
|
||||||
/* Trivial properties */
|
/* Trivial properties */
|
||||||
void jfileselect_set_font(jfileselect *fs, font_t const *font);
|
void jfileselect_set_font(jfileselect *fs, font_t const *font);
|
||||||
void jfileselect_set_line_spacing(jfileselect *fs, int line_spacing);
|
void jfileselect_set_line_spacing(jfileselect *fs, int line_spacing);
|
||||||
|
void jfileselect_set_show_file_size(jfileselect *fs, bool show_file_size);
|
||||||
|
|
||||||
#endif /* _J_JFILESELECT */
|
#endif /* _J_JFILESELECT */
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
/* Type identifier for jfileselect */
|
/* Type identifier for jfileselect */
|
||||||
static int jfileselect_type_id = -1;
|
static int jfileselect_type_id = -1;
|
||||||
|
@ -17,6 +18,20 @@ uint16_t JFILESELECT_LOADED;
|
||||||
uint16_t JFILESELECT_VALIDATED;
|
uint16_t JFILESELECT_VALIDATED;
|
||||||
uint16_t JFILESELECT_CANCELED;
|
uint16_t JFILESELECT_CANCELED;
|
||||||
|
|
||||||
|
/* We can try pretty hard to not duplicate the information held by the
|
||||||
|
directory descriptor, which is already a full array of all entries on gint.
|
||||||
|
However, the standard API behind readdir(3) does not allow us any complex
|
||||||
|
operations; no filtering, no sorting, no accessing auxiliary data such as
|
||||||
|
the file size. So we do this manually. */
|
||||||
|
struct fileinfo {
|
||||||
|
/* Entry name (owned by the structure) */
|
||||||
|
char *name;
|
||||||
|
/* File size in bytes if file, number of entries if folder */
|
||||||
|
int size;
|
||||||
|
/* Type from [struct dirent] */
|
||||||
|
int type;
|
||||||
|
};
|
||||||
|
|
||||||
jfileselect *jfileselect_create(void *parent)
|
jfileselect *jfileselect_create(void *parent)
|
||||||
{
|
{
|
||||||
if(jfileselect_type_id < 0) return NULL;
|
if(jfileselect_type_id < 0) return NULL;
|
||||||
|
@ -27,16 +42,18 @@ jfileselect *jfileselect_create(void *parent)
|
||||||
jwidget_init(&fs->widget, jfileselect_type_id, parent);
|
jwidget_init(&fs->widget, jfileselect_type_id, parent);
|
||||||
|
|
||||||
fs->path = NULL;
|
fs->path = NULL;
|
||||||
fs->dp = NULL;
|
fs->entries = NULL;
|
||||||
|
fs->entry_count = 0;
|
||||||
|
|
||||||
fs->selected_file = NULL;
|
fs->selected_file = NULL;
|
||||||
|
|
||||||
fs->folder_entries = -1;
|
|
||||||
fs->cursor = -1;
|
fs->cursor = -1;
|
||||||
fs->scroll = 0;
|
fs->scroll = 0;
|
||||||
fs->visible_lines = 0;
|
fs->visible_lines = 0;
|
||||||
|
|
||||||
fs->line_spacing = 4;
|
fs->line_spacing = 4;
|
||||||
fs->font = dfont_default();
|
fs->font = dfont_default();
|
||||||
|
fs->show_file_size = false;
|
||||||
|
|
||||||
return fs;
|
return fs;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +65,18 @@ static void count_visible_lines(jfileselect *fs)
|
||||||
fs->visible_lines = ch / line_height;
|
fs->visible_lines = ch / line_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void set_finfo(jfileselect *fs, struct fileinfo *finfo, int n)
|
||||||
|
{
|
||||||
|
struct fileinfo *old = fs->entries;
|
||||||
|
if(old) {
|
||||||
|
for(int i = 0; i < fs->entry_count; i++)
|
||||||
|
free(old[i].name);
|
||||||
|
free(old);
|
||||||
|
}
|
||||||
|
fs->entries = finfo;
|
||||||
|
fs->entry_count = n;
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Getters and setters
|
// Getters and setters
|
||||||
//---
|
//---
|
||||||
|
@ -64,6 +93,12 @@ void jfileselect_set_line_spacing(jfileselect *fs, int line_spacing)
|
||||||
count_visible_lines(fs);
|
count_visible_lines(fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void jfileselect_set_show_file_size(jfileselect *fs, bool show_file_size)
|
||||||
|
{
|
||||||
|
fs->show_file_size = show_file_size;
|
||||||
|
fs->widget.update = true;
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Path and folder manipulation
|
// Path and folder manipulation
|
||||||
//---
|
//---
|
||||||
|
@ -97,58 +132,118 @@ static char *path_up(char const *path)
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int accept_entry(struct dirent *ent)
|
static bool accept_entry(struct dirent *ent)
|
||||||
{
|
{
|
||||||
/* TODO: jfileselect: Programmable filter */
|
/* TODO: jfileselect: Programmable filter */
|
||||||
if(!strcmp(ent->d_name, "@MainMem"))
|
if(!strcmp(ent->d_name, "@MainMem"))
|
||||||
return 0;
|
return false;
|
||||||
if(!strcmp(ent->d_name, "SAVE-F"))
|
if(!strcmp(ent->d_name, "SAVE-F"))
|
||||||
return 0;
|
return false;
|
||||||
if(!strcmp(ent->d_name, "."))
|
if(!strcmp(ent->d_name, "."))
|
||||||
return 0;
|
return false;
|
||||||
if(!strcmp(ent->d_name, ".."))
|
if(!strcmp(ent->d_name, ".."))
|
||||||
return 0;
|
return false;
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dirent *read_nth_dir_entry(DIR *dp, int n)
|
static int count_accepted_entries(DIR *dp)
|
||||||
{
|
{
|
||||||
struct dirent *entry = NULL;
|
int n = 0;
|
||||||
|
struct dirent *ent;
|
||||||
|
|
||||||
rewinddir(dp);
|
rewinddir(dp);
|
||||||
|
while((ent = readdir(dp)))
|
||||||
|
n += accept_entry(ent);
|
||||||
|
|
||||||
for(int i = 0; i <= n;) {
|
return n;
|
||||||
entry = readdir(dp);
|
|
||||||
if(!entry)
|
|
||||||
return NULL;
|
|
||||||
i += accept_entry(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool load_folder(jfileselect *fs, char *path)
|
static int compare_entries(void const *i1_0, void const *i2_0)
|
||||||
{
|
{
|
||||||
if(fs->dp)
|
struct fileinfo const *i1 = i1_0, *i2 = i2_0;
|
||||||
closedir(fs->dp);
|
int d1 = (i1->type == DT_DIR);
|
||||||
|
int d2 = (i2->type == DT_DIR);
|
||||||
|
|
||||||
fs->dp = (DIR *)gint_world_switch(GINT_CALL(opendir, path));
|
/* Group directories first */
|
||||||
if(!fs->dp)
|
if(d1 != d2)
|
||||||
|
return d2 - d1;
|
||||||
|
/* Then group by name */
|
||||||
|
return strcmp(i1->name, i2->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool load_folder_switch(jfileselect *fs, char *path)
|
||||||
|
{
|
||||||
|
set_finfo(fs, NULL, 0);
|
||||||
|
|
||||||
|
struct dirent *ent;
|
||||||
|
DIR *dp = opendir(path);
|
||||||
|
if(!dp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
/* Count entries */
|
||||||
|
int n = count_accepted_entries(dp);
|
||||||
|
|
||||||
|
/* Allocate memory for the fileinfo structures */
|
||||||
|
struct fileinfo *finfo = malloc(n * sizeof *finfo);
|
||||||
|
if(!finfo) {
|
||||||
|
closedir(dp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read the fileinfo structures */
|
||||||
|
rewinddir(dp);
|
||||||
|
for(int i = 0; i < n && (ent = readdir(dp));) {
|
||||||
|
if(!accept_entry(ent))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
finfo[i].name = strdup(ent->d_name);
|
||||||
|
finfo[i].type = ent->d_type;
|
||||||
|
finfo[i].size = -1;
|
||||||
|
|
||||||
|
if(!finfo[i].name) {
|
||||||
|
/* Profesionnal unwinding isn't it? */
|
||||||
|
for(int j = 0; j < i; j++) free(finfo[j].name);
|
||||||
|
free(finfo);
|
||||||
|
closedir(dp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *full_path = path_down(path, ent->d_name);
|
||||||
|
if(full_path) {
|
||||||
|
if(ent->d_type == DT_DIR) {
|
||||||
|
DIR *sub = opendir(full_path);
|
||||||
|
if(sub) {
|
||||||
|
finfo[i].size = count_accepted_entries(sub);
|
||||||
|
closedir(sub);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
struct stat st;
|
||||||
|
if(stat(full_path, &st) >= 0)
|
||||||
|
finfo[i].size = st.st_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
qsort(finfo, n, sizeof *finfo, compare_entries);
|
||||||
|
|
||||||
|
closedir(dp);
|
||||||
free(fs->path);
|
free(fs->path);
|
||||||
fs->path = path;
|
fs->path = path;
|
||||||
|
set_finfo(fs, finfo, n);
|
||||||
fs->folder_entries = 0;
|
|
||||||
struct dirent *ent;
|
|
||||||
while ((ent = readdir(fs->dp)))
|
|
||||||
fs->folder_entries += accept_entry(ent);
|
|
||||||
|
|
||||||
fs->widget.update = true;
|
fs->widget.update = true;
|
||||||
|
|
||||||
jwidget_emit(fs, (jevent){ .type = JFILESELECT_LOADED });
|
jwidget_emit(fs, (jevent){ .type = JFILESELECT_LOADED });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool load_folder(jfileselect *fs, char *path)
|
||||||
|
{
|
||||||
|
return gint_world_switch(GINT_CALL(load_folder_switch, (void *)fs, path));
|
||||||
|
}
|
||||||
|
|
||||||
bool jfileselect_browse(jfileselect *fs, char const *path)
|
bool jfileselect_browse(jfileselect *fs, char const *path)
|
||||||
{
|
{
|
||||||
char *path_copy = strdup(path);
|
char *path_copy = strdup(path);
|
||||||
|
@ -198,42 +293,32 @@ static void jfileselect_poly_layout(void *fs0)
|
||||||
static void jfileselect_poly_render(void *fs0, int x, int y)
|
static void jfileselect_poly_render(void *fs0, int x, int y)
|
||||||
{
|
{
|
||||||
jfileselect *fs = fs0;
|
jfileselect *fs = fs0;
|
||||||
if(!fs->path || !fs->dp)
|
if(!fs->path || !fs->entries)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
font_t const *old_font = dfont(fs->font);
|
font_t const *old_font = dfont(fs->font);
|
||||||
int line_height = fs->font->line_height + fs->line_spacing;
|
int line_height = fs->font->line_height + fs->line_spacing;
|
||||||
int cw = jwidget_content_width(fs);
|
int cw = jwidget_content_width(fs);
|
||||||
|
struct fileinfo *finfo = fs->entries;
|
||||||
|
|
||||||
rewinddir(fs->dp);
|
for(int i = 0; i < fs->visible_lines && i < fs->entry_count; i++) {
|
||||||
struct dirent *ent;
|
|
||||||
char const *entry_name;
|
|
||||||
bool isfolder;
|
|
||||||
|
|
||||||
for(int i = -fs->scroll; i < fs->visible_lines;) {
|
|
||||||
bool selected = (fs->cursor == fs->scroll + i);
|
bool selected = (fs->cursor == fs->scroll + i);
|
||||||
|
struct fileinfo *info = &finfo[fs->scroll + i];
|
||||||
ent = readdir(fs->dp);
|
bool isfolder = (info->type == DT_DIR);
|
||||||
if(!ent) break;
|
|
||||||
if(!accept_entry(ent)) continue;
|
|
||||||
|
|
||||||
entry_name = ent->d_name;
|
|
||||||
isfolder = (ent->d_type == DT_DIR);
|
|
||||||
|
|
||||||
if(i < 0) {
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int line_y = y + line_height * i;
|
int line_y = y + line_height * i;
|
||||||
if(selected)
|
if(selected)
|
||||||
drect(x, line_y, x + cw - 1, line_y + line_height - 1, C_BLACK);
|
drect(x, line_y, x + cw - 1, line_y + line_height - 1, C_BLACK);
|
||||||
|
|
||||||
/* Round `line_spacing / 2` down so there is more spacing below */
|
/* Round `line_spacing / 2` down so there is more spacing below */
|
||||||
dprint(x+2, line_y + (fs->line_spacing + 0) / 2,
|
int text_y = line_y + (fs->line_spacing + 0) / 2;
|
||||||
selected ? C_WHITE : C_BLACK,
|
int fg = selected ? C_WHITE : C_BLACK;
|
||||||
"%s%s", entry_name, isfolder ? "/" : "");
|
|
||||||
i++;
|
dprint(x+2, text_y, fg, "%s%s", info->name, isfolder ? "/" : "");
|
||||||
|
if(fs->show_file_size && info->size >= 0) {
|
||||||
|
dprint_opt(x + cw - 3, text_y, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP,
|
||||||
|
isfolder ? "%d entries" : "%d B", info->size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dfont(old_font);
|
dfont(old_font);
|
||||||
|
@ -242,7 +327,7 @@ static void jfileselect_poly_render(void *fs0, int x, int y)
|
||||||
static bool jfileselect_poly_event(void *fs0, jevent e)
|
static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
{
|
{
|
||||||
jfileselect *fs = fs0;
|
jfileselect *fs = fs0;
|
||||||
if(!fs->path || !fs->dp)
|
if(!fs->path || !fs->entries)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(e.type == JWIDGET_KEY) {
|
if(e.type == JWIDGET_KEY) {
|
||||||
|
@ -257,17 +342,17 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
fs->cursor = ev.shift ? 0 : fs->cursor - 1;
|
fs->cursor = ev.shift ? 0 : fs->cursor - 1;
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
if(key == KEY_DOWN && fs->cursor < fs->folder_entries - 1) {
|
if(key == KEY_DOWN && fs->cursor < fs->entry_count - 1) {
|
||||||
fs->cursor = ev.shift ? fs->folder_entries - 1 : fs->cursor + 1;
|
fs->cursor = ev.shift ? fs->entry_count - 1 : fs->cursor + 1;
|
||||||
moved = true;
|
moved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fs->scroll > 0 && fs->cursor <= fs->scroll)
|
if(fs->scroll > 0 && fs->cursor <= fs->scroll)
|
||||||
fs->scroll = max(fs->cursor - 1, 0);
|
fs->scroll = max(fs->cursor - 1, 0);
|
||||||
if(fs->scroll + fs->visible_lines < fs->folder_entries
|
if(fs->scroll + fs->visible_lines < fs->entry_count
|
||||||
&& fs->cursor >= fs->scroll + fs->visible_lines - 2) {
|
&& fs->cursor >= fs->scroll + fs->visible_lines - 2) {
|
||||||
fs->scroll = min(fs->cursor - fs->visible_lines + 2,
|
fs->scroll = min(fs->cursor - fs->visible_lines + 2,
|
||||||
fs->folder_entries - fs->visible_lines);
|
fs->entry_count - fs->visible_lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(moved) {
|
if(moved) {
|
||||||
|
@ -289,9 +374,10 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(key == KEY_EXE) {
|
else if(key == KEY_EXE) {
|
||||||
struct dirent *ent = read_nth_dir_entry(fs->dp, fs->cursor);
|
struct fileinfo *finfo = fs->entries;
|
||||||
if(ent->d_type == DT_DIR) {
|
struct fileinfo *i = &finfo[fs->cursor];
|
||||||
char *child = path_down(fs->path, ent->d_name);
|
if(i->type == DT_DIR) {
|
||||||
|
char *child = path_down(fs->path, i->name);
|
||||||
if(child) {
|
if(child) {
|
||||||
load_folder(fs, child);
|
load_folder(fs, child);
|
||||||
fs->cursor = 0;
|
fs->cursor = 0;
|
||||||
|
@ -300,7 +386,7 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
fs->selected_file = path_down(fs->path, ent->d_name);
|
fs->selected_file = path_down(fs->path, i->name);
|
||||||
if(fs->selected_file) {
|
if(fs->selected_file) {
|
||||||
jwidget_emit(fs,(jevent){ .type = JFILESELECT_VALIDATED });
|
jwidget_emit(fs,(jevent){ .type = JFILESELECT_VALIDATED });
|
||||||
return true;
|
return true;
|
||||||
|
@ -317,8 +403,7 @@ static void jfileselect_poly_destroy(void *fs0)
|
||||||
jfileselect *fs = fs0;
|
jfileselect *fs = fs0;
|
||||||
|
|
||||||
free(fs->path);
|
free(fs->path);
|
||||||
if(fs->dp)
|
set_finfo(fs, NULL, 0);
|
||||||
closedir(fs->dp);
|
|
||||||
free(fs->selected_file);
|
free(fs->selected_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue