mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-28 04:23:40 +01:00
jfileselect: add a "save as" option
This commit is contained in:
parent
699576eb33
commit
32ef1536d7
4 changed files with 111 additions and 12 deletions
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <justui/defs.h>
|
#include <justui/defs.h>
|
||||||
#include <justui/jwidget.h>
|
#include <justui/jwidget.h>
|
||||||
|
#include <justui/jinput.h>
|
||||||
#include <gint/display.h>
|
#include <gint/display.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
|
@ -34,6 +35,12 @@ typedef struct {
|
||||||
|
|
||||||
/* Full path to file last selected with EXE */
|
/* Full path to file last selected with EXE */
|
||||||
char *selected_file;
|
char *selected_file;
|
||||||
|
/* "Save As" input field */
|
||||||
|
jinput *saveas_input;
|
||||||
|
/* Whether the "Save As" option is shown */
|
||||||
|
bool saveas;
|
||||||
|
/* Whether we are currently using the input field */
|
||||||
|
bool input_mode;
|
||||||
|
|
||||||
/* Current cursor position (0 .. folder_entries-1) */
|
/* Current cursor position (0 .. folder_entries-1) */
|
||||||
int16_t cursor;
|
int16_t cursor;
|
||||||
|
@ -62,6 +69,13 @@ extern uint16_t JFILESELECT_CANCELED;
|
||||||
any events in this state; a path must first be set before use. */
|
any events in this state; a path must first be set before use. */
|
||||||
jfileselect *jfileselect_create(void *parent);
|
jfileselect *jfileselect_create(void *parent);
|
||||||
|
|
||||||
|
/* jfileselect_set_saveas(): Select whether a "Save as" option is presented
|
||||||
|
|
||||||
|
If true, the browser will show a "<Create a new file here>" entry in every
|
||||||
|
directory. The result of jfileselect_selected_file(), in this case, might be
|
||||||
|
a file that does not yet exist. */
|
||||||
|
void jfileselect_set_saveas(jfileselect *fs, bool save_as);
|
||||||
|
|
||||||
/* jfileselect_browse(): Browse a folder
|
/* jfileselect_browse(): Browse a folder
|
||||||
|
|
||||||
This function loads the specified folder and allows the user to select a
|
This function loads the specified folder and allows the user to select a
|
||||||
|
|
|
@ -28,7 +28,7 @@ struct fileinfo {
|
||||||
char *name;
|
char *name;
|
||||||
/* File size in bytes if file, number of entries if folder */
|
/* File size in bytes if file, number of entries if folder */
|
||||||
int size;
|
int size;
|
||||||
/* Type from [struct dirent] */
|
/* Type from [struct dirent], -1 for the "Save As" entry */
|
||||||
int type;
|
int type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,11 +41,21 @@ jfileselect *jfileselect_create(void *parent)
|
||||||
|
|
||||||
jwidget_init(&fs->widget, jfileselect_type_id, parent);
|
jwidget_init(&fs->widget, jfileselect_type_id, parent);
|
||||||
|
|
||||||
|
jinput *input = jinput_create("Filename: ", 32, fs);
|
||||||
|
if(!input) {
|
||||||
|
free(fs);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
jwidget_set_floating(input, true);
|
||||||
|
jwidget_set_visible(input, false);
|
||||||
|
|
||||||
fs->path = NULL;
|
fs->path = NULL;
|
||||||
fs->entries = NULL;
|
fs->entries = NULL;
|
||||||
fs->entry_count = 0;
|
fs->entry_count = 0;
|
||||||
|
|
||||||
fs->selected_file = NULL;
|
fs->selected_file = NULL;
|
||||||
|
fs->saveas_input = input;
|
||||||
|
fs->saveas = false;
|
||||||
|
|
||||||
fs->cursor = -1;
|
fs->cursor = -1;
|
||||||
fs->scroll = 0;
|
fs->scroll = 0;
|
||||||
|
@ -77,6 +87,30 @@ static void set_finfo(jfileselect *fs, struct fileinfo *finfo, int n)
|
||||||
fs->entry_count = n;
|
fs->entry_count = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void jfileselect_set_saveas(jfileselect *fs, bool save_as)
|
||||||
|
{
|
||||||
|
fs->saveas = save_as;
|
||||||
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Input utilities
|
||||||
|
//---
|
||||||
|
|
||||||
|
static void start_input(jfileselect *fs)
|
||||||
|
{
|
||||||
|
fs->input_mode = true;
|
||||||
|
jinput_clear(fs->saveas_input);
|
||||||
|
jwidget_event(fs->saveas_input, (jevent){ .type = JWIDGET_FOCUS_IN });
|
||||||
|
fs->widget.update = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stop_input(jfileselect *fs)
|
||||||
|
{
|
||||||
|
fs->input_mode = false;
|
||||||
|
jwidget_event(fs->saveas_input, (jevent){ .type = JWIDGET_FOCUS_OUT });
|
||||||
|
fs->widget.update = true;
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Getters and setters
|
// Getters and setters
|
||||||
//---
|
//---
|
||||||
|
@ -161,12 +195,19 @@ static int count_accepted_entries(DIR *dp)
|
||||||
static int compare_entries(void const *i1_0, void const *i2_0)
|
static int compare_entries(void const *i1_0, void const *i2_0)
|
||||||
{
|
{
|
||||||
struct fileinfo const *i1 = i1_0, *i2 = i2_0;
|
struct fileinfo const *i1 = i1_0, *i2 = i2_0;
|
||||||
int d1 = (i1->type == DT_DIR);
|
|
||||||
int d2 = (i2->type == DT_DIR);
|
|
||||||
|
|
||||||
/* Group directories first */
|
/* Group directories first */
|
||||||
|
int d1 = (i1->type == DT_DIR);
|
||||||
|
int d2 = (i2->type == DT_DIR);
|
||||||
if(d1 != d2)
|
if(d1 != d2)
|
||||||
return d2 - d1;
|
return d2 - d1;
|
||||||
|
|
||||||
|
/* Then the "Save As" entry */
|
||||||
|
int sa1 = (i1->type == -1);
|
||||||
|
int sa2 = (i2->type == -1);
|
||||||
|
if(sa1 != sa2)
|
||||||
|
return sa2 - sa1;
|
||||||
|
|
||||||
/* Then group by name */
|
/* Then group by name */
|
||||||
return strcmp(i1->name, i2->name);
|
return strcmp(i1->name, i2->name);
|
||||||
}
|
}
|
||||||
|
@ -181,7 +222,7 @@ static bool load_folder_switch(jfileselect *fs, char *path)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Count entries */
|
/* Count entries */
|
||||||
int n = count_accepted_entries(dp);
|
int n = count_accepted_entries(dp) + fs->saveas;
|
||||||
|
|
||||||
/* Allocate memory for the fileinfo structures */
|
/* Allocate memory for the fileinfo structures */
|
||||||
struct fileinfo *finfo = malloc(n * sizeof *finfo);
|
struct fileinfo *finfo = malloc(n * sizeof *finfo);
|
||||||
|
@ -227,6 +268,13 @@ static bool load_folder_switch(jfileselect *fs, char *path)
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add the saveas entry */
|
||||||
|
if(fs->saveas) {
|
||||||
|
finfo[n-1].name = strdup("<Create a new file here>");
|
||||||
|
finfo[n-1].type = -1;
|
||||||
|
finfo[n-1].size = -1;
|
||||||
|
}
|
||||||
|
|
||||||
qsort(finfo, n, sizeof *finfo, compare_entries);
|
qsort(finfo, n, sizeof *finfo, compare_entries);
|
||||||
|
|
||||||
closedir(dp);
|
closedir(dp);
|
||||||
|
@ -258,6 +306,7 @@ bool jfileselect_browse(jfileselect *fs, char const *path)
|
||||||
|
|
||||||
fs->cursor = 0;
|
fs->cursor = 0;
|
||||||
fs->scroll = 0;
|
fs->scroll = 0;
|
||||||
|
stop_input(fs);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +337,7 @@ static void jfileselect_poly_layout(void *fs0)
|
||||||
{
|
{
|
||||||
jfileselect *fs = fs0;
|
jfileselect *fs = fs0;
|
||||||
count_visible_lines(fs);
|
count_visible_lines(fs);
|
||||||
|
fs->saveas_input->widget.w = jwidget_content_width(fs) - 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void jfileselect_poly_render(void *fs0, int x, int y)
|
static void jfileselect_poly_render(void *fs0, int x, int y)
|
||||||
|
@ -306,14 +356,22 @@ static void jfileselect_poly_render(void *fs0, int x, int y)
|
||||||
struct fileinfo *info = &finfo[fs->scroll + i];
|
struct fileinfo *info = &finfo[fs->scroll + i];
|
||||||
bool isfolder = (info->type == DT_DIR);
|
bool isfolder = (info->type == DT_DIR);
|
||||||
|
|
||||||
int line_y = y + line_height * i;
|
|
||||||
if(selected)
|
|
||||||
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 */
|
||||||
|
int line_y = y + line_height * i;
|
||||||
int text_y = line_y + (fs->line_spacing + 0) / 2;
|
int text_y = line_y + (fs->line_spacing + 0) / 2;
|
||||||
int fg = selected ? C_WHITE : C_BLACK;
|
int fg = selected ? C_WHITE : C_BLACK;
|
||||||
|
|
||||||
|
if(selected && fs->input_mode) {
|
||||||
|
/* Little bit of a hack */
|
||||||
|
fs->saveas_input->widget.visible = true;
|
||||||
|
jwidget_render(fs->saveas_input, x+2, text_y);
|
||||||
|
fs->saveas_input->widget.visible = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selected)
|
||||||
|
drect(x, line_y, x + cw - 1, line_y + line_height - 1, C_BLACK);
|
||||||
|
|
||||||
dprint(x+2, text_y, fg, "%s%s", info->name, isfolder ? "/" : "");
|
dprint(x+2, text_y, fg, "%s%s", info->name, isfolder ? "/" : "");
|
||||||
if(fs->show_file_size && info->size >= 0) {
|
if(fs->show_file_size && info->size >= 0) {
|
||||||
dprint_opt(x + cw - 3, text_y, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP,
|
dprint_opt(x + cw - 3, text_y, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP,
|
||||||
|
@ -330,6 +388,30 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
if(!fs->path || !fs->entries)
|
if(!fs->path || !fs->entries)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if(e.type == JINPUT_CANCELED && e.source == fs->saveas_input) {
|
||||||
|
stop_input(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if(e.type == JINPUT_VALIDATED && e.source == fs->saveas_input) {
|
||||||
|
stop_input(fs);
|
||||||
|
fs->selected_file = path_down(fs->path,jinput_value(fs->saveas_input));
|
||||||
|
if(fs->selected_file) {
|
||||||
|
jwidget_emit(fs,(jevent){ .type = JFILESELECT_VALIDATED });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
/* Send all events to the input when in input mode (without requiring
|
||||||
|
access to the jscene to actually move the focus */
|
||||||
|
else if(fs->input_mode) {
|
||||||
|
bool b = jwidget_event(fs->saveas_input, e);
|
||||||
|
if(b)
|
||||||
|
fs->widget.update = true;
|
||||||
|
/* We do capture all key events if not used by the input, so F-keys are
|
||||||
|
disabled/etc */
|
||||||
|
return b || e.type == JWIDGET_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
if(e.type == JWIDGET_KEY) {
|
if(e.type == JWIDGET_KEY) {
|
||||||
key_event_t ev = e.key;
|
key_event_t ev = e.key;
|
||||||
if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD)
|
if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD)
|
||||||
|
@ -376,6 +458,7 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
else if(key == KEY_EXE) {
|
else if(key == KEY_EXE) {
|
||||||
struct fileinfo *finfo = fs->entries;
|
struct fileinfo *finfo = fs->entries;
|
||||||
struct fileinfo *i = &finfo[fs->cursor];
|
struct fileinfo *i = &finfo[fs->cursor];
|
||||||
|
|
||||||
if(i->type == DT_DIR) {
|
if(i->type == DT_DIR) {
|
||||||
char *child = path_down(fs->path, i->name);
|
char *child = path_down(fs->path, i->name);
|
||||||
if(child) {
|
if(child) {
|
||||||
|
@ -385,6 +468,10 @@ static bool jfileselect_poly_event(void *fs0, jevent e)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(fs->saveas && i->type == -1) {
|
||||||
|
start_input(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
fs->selected_file = path_down(fs->path, i->name);
|
fs->selected_file = path_down(fs->path, i->name);
|
||||||
if(fs->selected_file) {
|
if(fs->selected_file) {
|
||||||
|
|
|
@ -99,8 +99,7 @@ void jscene_queue_event(jscene *s, jevent e)
|
||||||
{
|
{
|
||||||
/* Prevent filling and overflowing the queue */
|
/* Prevent filling and overflowing the queue */
|
||||||
int next = (s->queue_next + 1) % JSCENE_QUEUE_SIZE;
|
int next = (s->queue_next + 1) % JSCENE_QUEUE_SIZE;
|
||||||
if(next == s->queue_first)
|
if(next == s->queue_first) {
|
||||||
{
|
|
||||||
s->lost_events++;
|
s->lost_events++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -656,8 +656,7 @@ bool jwidget_event(void *w0, jevent e)
|
||||||
{
|
{
|
||||||
J_CAST(w)
|
J_CAST(w)
|
||||||
jwidget_poly const *poly = widget_types[w->type];
|
jwidget_poly const *poly = widget_types[w->type];
|
||||||
if(poly->event) return poly->event(w, e);
|
return poly->event && poly->event(w, e);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void jwidget_emit(void *w0, jevent e)
|
void jwidget_emit(void *w0, jevent e)
|
||||||
|
|
Loading…
Reference in a new issue