UI improvements, relative goto, better program icon

This commit is contained in:
Lephenixnoir 2022-06-21 21:24:27 +01:00
parent 52b4aea3f4
commit a08e7fa89d
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
7 changed files with 308 additions and 66 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

View File

@ -27,6 +27,7 @@ heditor *heditor_create(jwidget *parent)
e->source = NULL;
e->scroll = 0;
e->cursor = 0;
e->insert = false;
e->visible_lines = 0;
e->bytes_per_line = 8;
e->line_spacing = 3;
@ -117,6 +118,14 @@ bool heditor_move_to(heditor *e, int target_cursor)
return changed;
}
void heditor_set_insert_mode(heditor *e, bool insert)
{
e->insert = false;
if(e->source && e->source->intf->cap & SOURCE_EXPAND)
e->insert = insert;
e->widget.update = true;
}
void heditor_set_source(heditor *e, source_t *source)
{
e->source = source;
@ -208,9 +217,15 @@ static void heditor_poly_render(void *e0, int x, int y)
int fg = C_BLACK;
if(e->cursor == o) {
drect(bytes_x - 1, line_y - 1, bytes_x + text_w,
line_y + e->font->line_height, C_BLACK);
fg = C_WHITE;
if(e->insert) {
drect(bytes_x - 2, line_y - 1, bytes_x - 1,
line_y + e->font->line_height, C_BLACK);
}
else {
drect(bytes_x - 1, line_y - 1, bytes_x + text_w,
line_y + e->font->line_height, C_BLACK);
fg = C_WHITE;
}
}
dtext(bytes_x, line_y, fg, byte);

View File

@ -23,6 +23,8 @@ typedef struct {
int scroll;
/* Current cursor position in source, in bytes */
int cursor;
/* Current insertion mode (false=overwrite, true=insert) */
bool insert;
/* Number of visible lines */
int8_t visible_lines;
/* Number of bytes printed per line */
@ -47,6 +49,10 @@ void heditor_set_source(heditor *e, source_t *source);
/* Scroll to the requested cursor position. */
bool heditor_move_to(heditor *e, int offset);
/* Change the insertion mode; true for insertion, false for overwrite. If the
source doesn't support insertion, this function is a no-op. */
void heditor_set_insert_mode(heditor *e, bool insert);
/* Trivial properties */
void heditor_set_font(heditor *e, font_t const *font);
void heditor_set_line_spacing(heditor *e, int line_spacig);

View File

@ -15,38 +15,170 @@
#include <stdlib.h>
#include <string.h>
static void update_status(jlabel *status, jlabel *title, source_t *s)
// Global application data //
struct {
/* Title widget, status bar widget, f-keys widget. */
jlabel *title;
jlabel *status;
jfkeys *fkeys;
/* Source info widget. */
jlabel *source_info;
/* Current data source. */
source_t *source;
/* Current edition mode. */
bool insert;
/* Current view. */
int view;
/* Current position in the menus. */
int fkmenu;
} app;
enum {
VIEW_EDITOR,
VIEW_INFO,
VIEW_STATS,
};
enum {
FK_EDITOR,
FK_EDITOR_SOURCE,
FK_EDITOR_OPEN,
FK_VIEW,
};
// General UI update functions //
static void fkeys_sprintf(jfkeys *fk, char const *fmt, ...)
{
static char str[128];
va_list args;
va_start(args, fmt);
vsnprintf(str, sizeof str, fmt, args);
va_end(args);
jfkeys_set(fk, str);
}
static void update_fkeys(void)
{
if(app.fkmenu == FK_EDITOR) {
if(!app.source)
jfkeys_set(app.fkeys, "/OPEN;;;;;");
else {
bool can_insert = (app.source->intf->cap & SOURCE_EXPAND) != 0;
fkeys_sprintf(app.fkeys, "/OPEN;/SOURCE;%s%s;@GOTO;;#VIEW",
can_insert ? "@" : "#",
app.insert ? "INSERT" : "OVERW");
}
}
else if(app.fkmenu == FK_EDITOR_SOURCE) {
jfkeys_set(app.fkeys, "@SAVE;@SAVE AS;;;;");
}
else if(app.fkmenu == FK_EDITOR_OPEN) {
jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW");
}
else if(app.fkmenu == FK_VIEW) {
fkeys_sprintf(app.fkeys, ";;;%sEDITOR;%sINFO;%sSTATS",
app.view == VIEW_EDITOR ? "#" : "@",
app.view == VIEW_INFO ? "#" : "@",
app.view == VIEW_STATS ? "#" : "@");
}
}
static void update_status(void)
{
/* TODO: (*) and (**) markers on title/status bar */
source_t *s = app.source;
if(!s) {
jlabel_set_text(status, "-");
jlabel_set_text(title, "Hex Editor");
jwidget_set_visible(app.status, false);
jlabel_set_text(app.title, "Hex Editor");
return;
}
size_t size = source_size(s);
jwidget_set_visible(app.status, true);
if(s->origin)
jlabel_asprintf(title, "%s (%d B) - Hex Editor", s->origin, size);
jlabel_asprintf(app.title, "%s (%d B) - Hex Editor", s->origin, size);
else
jlabel_set_text(title, "(nil) - Hex Editor");
jlabel_set_text(app.title, "(nil) - Hex Editor");
jlabel_asprintf(status, "Buffer: @%X %d/%d",
jlabel_asprintf(app.status, "Buffer: @%X %d/%d",
s->buf_offset, s->buf->data_size, s->buf->mem_size);
}
static void update_source_info(void)
{
source_t *s = app.source;
if(!s) {
jlabel_set_text(app.source_info, "No source.");
return;
}
char caps[48];
memset(caps, 0, sizeof caps);
if(s->intf->cap & SOURCE_WRITE)
strcat(caps, " WRITE");
if(s->intf->cap & SOURCE_EXPAND)
strcat(caps, " EXPAND");
if(s->intf->cap & SOURCE_SHRINK)
strcat(caps, " SHRINK");
jlabel_asprintf(app.source_info,
"%s\n"
"Size: %d bytes\n"
"Front buffer: %p, size %d/%d (x%d)\n"
"Front buffer maps to: @%d, size TODO\n"
"Capabilities: %s",
s->origin ? s->origin : "<unnamed source>",
source_size(s),
s->buf->mem, s->buf->data_size, s->buf->mem_size, s->buf->block_size,
s->buf_offset, /* s->buf_size, */
caps + 1);
}
static jwidget *make_popup(void *parent0, int w, int h)
{
J_CAST(parent)
jwidget *popup = jwidget_create(parent);
if(!popup)
return NULL;
jwidget_set_floating(popup, true);
jwidget_set_border(popup, J_BORDER_SOLID, 2, C_BLACK);
jwidget_set_padding(popup, 4, 4, 4, 4);
jwidget_set_background(popup, C_WHITE);
jwidget_set_visible(popup, false);
jwidget_set_fixed_size(popup, w, h);
popup->x = (DWIDTH - w) / 2;
popup->y = (DHEIGHT - h) / 2;
return popup;
}
void hex_view(void)
{
source_t *s = NULL;
app.source = NULL;
app.insert = false;
app.view = VIEW_EDITOR;
app.fkmenu = FK_EDITOR;
jscene *scene = jscene_create_fullscreen(NULL);
jlabel *title = jlabel_create("Hex Editor", scene);
jwidget *stack = jwidget_create(scene);
jfkeys *fkeys = jfkeys_create("@JUMP;@OPEN",scene);
jfkeys *fkeys = jfkeys_create(";;;;;",scene);
if(!scene || !title || !stack || !fkeys) {
jwidget_destroy(scene);
return;
}
app.title = title;
app.fkeys = fkeys;
jwidget_set_background(title, C_BLACK);
jlabel_set_text_color(title, C_WHITE);
@ -62,58 +194,51 @@ void hex_view(void)
jwidget *tab = jwidget_create(NULL);
heditor *mem = heditor_create(tab); /* previously 321x153 */
jinput *goto_input = jinput_create("Goto: ", 12, tab);
jlabel *status = jlabel_create("", tab);
app.status = status;
jwidget_set_margin(mem, 3, 0, 3, 0);
jwidget_set_margin(mem, 2, 0, 0, 0);
jwidget_set_stretch(mem, 1, 1, false);
jwidget_set_stretch(status, 1, 0, false);
jwidget_set_padding(status, 2, 0, 0, 0);
jwidget_set_borders(status, J_BORDER_SOLID, C_BLACK, 1, 0, 0, 0);
jwidget_set_stretch(goto_input, 1, 0, false);
jwidget_set_visible(goto_input, false);
jlayout_set_vbox(tab)->spacing = 3;
jwidget_add_child(stack, tab);
jwidget_set_stretch(tab, 1, 1, false);
update_status(status, title, s);
jscene_show_and_focus(scene, mem);
// File selection tab //
jfileselect *fileselect = jfileselect_create(stack);
jfileselect_set_show_file_size(fileselect, true);
jwidget_set_stretch(fileselect, 1, 1, false);
// Goto popup //
// Source info popup //
jwidget *popup_goto = jwidget_create(scene);
jwidget_set_floating(popup_goto, true);
jwidget_set_border(popup_goto, J_BORDER_SOLID, 2, C_BLACK);
jwidget_set_padding(popup_goto, 4, 4, 4, 4);
jwidget_set_background(popup_goto, C_WHITE);
jwidget_set_visible(popup_goto, false);
jwidget_set_fixed_size(popup_goto, DWIDTH * 3 / 4, DHEIGHT / 2);
popup_goto->x = DWIDTH / 8;
popup_goto->y = DHEIGHT / 4;
jlayout_set_vbox(popup_goto)->spacing = 3;
jwidget *popup_source_info = make_popup(scene, DWIDTH*3/4, DHEIGHT/2);
jlayout_set_vbox(popup_source_info)->spacing = 3;
jlabel *source_info = jlabel_create("", popup_source_info);
jlabel_set_wrap_mode(source_info, J_WRAP_WORD);
jlabel_set_line_spacing(source_info, 2);
jlabel_set_block_alignment(source_info, J_ALIGN_LEFT, J_ALIGN_TOP);
jwidget_set_stretch(source_info, 1, 1, false);
jinput *goto_input = jinput_create("Go to: ", 12, popup_goto);
jwidget_set_stretch(goto_input, 1, 0, false);
// Initial state //
jlabel *goto_help = jlabel_create(
"In hexadecimal. Use [ALPHA] to type letters.\n"
"Prefix with [+] or [-] to move relative to the current position.",
popup_goto);
jlabel_set_wrap_mode(goto_help, J_WRAP_WORD);
jlabel_set_line_spacing(goto_help, 2);
jlabel_set_block_alignment(goto_help, J_ALIGN_LEFT, J_ALIGN_TOP);
jwidget_set_stretch(goto_help, 1, 1, false);
update_status();
update_fkeys();
jscene_show_and_focus(scene, mem);
// Event handling //
int key = 0;
while(key != KEY_EXIT)
{
bool input_focus = (jscene_focused_widget(scene) == goto_input);
while(1) {
jevent e = jscene_run(scene);
if(e.type == JSCENE_PAINT) {
@ -124,50 +249,146 @@ void hex_view(void)
if(e.type == JINPUT_VALIDATED) {
/* Parse string into hexa */
char const *str = jinput_value(goto_input);
uint32_t target = strtoul(str, NULL, 16);
heditor_move_to(mem, target);
int mode = (*str == '+') ? 1 : (*str == '-') ? -1 : 0;
int target = strtoul(str + (mode != 0), NULL, 16);
heditor_move_to(mem, mode ? mem->cursor + target * mode : target);
}
if(e.type == JINPUT_VALIDATED || e.type == JINPUT_CANCELED) {
jwidget_set_visible(popup_goto, false);
jwidget_set_visible(goto_input, false);
jscene_show_and_focus(scene, mem);
}
if(e.type == JFILESELECT_LOADED) {
char const *path = jfileselect_current_folder(fileselect);
jlabel_asprintf(title, "Browsing: %s", path ? path : "(nil)");
}
if(e.type == JFILESELECT_CANCELED || e.type == JFILESELECT_VALIDATED) {
if(e.type == JFILESELECT_CANCELED) {
jscene_show_and_focus(scene, mem);
if(e.type == JFILESELECT_VALIDATED) {
char const *file = jfileselect_selected_file(fileselect);
if(file) {
if(s)
source_free(s);
s = source_loaded_file_open(file);
if(s) {
source_load(s, 0, 2048);
}
heditor_set_source(mem, s);
}
update_status();
}
if(e.type == JFILESELECT_VALIDATED) {
char const *file = jfileselect_selected_file(fileselect);
if(file) {
if(app.source)
source_free(app.source);
app.source = source_loaded_file_open(file);
app.fkmenu = FK_EDITOR;
app.insert = false;
source_load(app.source, 0, 2048);
heditor_set_source(mem, app.source);
heditor_set_insert_mode(mem, app.insert);
update_fkeys();
}
update_status(status, title, s);
jscene_show_and_focus(scene, mem);
update_status();
}
if(e.type == HEDITOR_CHANGED) {
update_status(status, title, s);
update_status();
}
// Fkey menu navigation //
if(e.type != JSCENE_KEY || e.key.type == KEYEV_UP) continue;
key = e.key.key;
if(key == KEY_F1 && stack->layout_stack.active == 0 && !input_focus) {
jinput_clear(goto_input);
jwidget_set_visible(popup_goto, true);
jscene_set_focused_widget(scene, goto_input);
if(jscene_focused_widget(scene) == goto_input)
continue;
if(app.fkmenu == FK_EDITOR) {
if(key == KEY_F1) {
app.fkmenu = FK_EDITOR_OPEN;
update_fkeys();
}
else if(key == KEY_F2 && app.source) {
app.fkmenu = FK_EDITOR_SOURCE;
update_fkeys();
}
else if(key == KEY_F3 && app.source) {
if(app.source->intf->cap & SOURCE_EXPAND) {
app.insert = !app.insert;
heditor_set_insert_mode(mem, app.insert);
update_fkeys();
}
}
else if(key == KEY_F4 && app.source) {
jinput_clear(goto_input);
jscene_show_and_focus(scene, goto_input);
}
else if(key == KEY_F6 && app.source) {
app.fkmenu = FK_VIEW;
update_fkeys();
}
}
if(key == KEY_F2 && !input_focus) {
jfileselect_browse(fileselect, "/");
jscene_show_and_focus(scene, fileselect);
else if(app.fkmenu == FK_EDITOR_SOURCE) {
if(key == KEY_F1 && app.source) {
/* TODO: Source save */
}
else if(key == KEY_F2 && app.source) {
/* TODO: Source save as */
}
else if(key == KEY_EXIT) {
app.fkmenu = FK_EDITOR;
update_fkeys();
}
}
else if(app.fkmenu == FK_EDITOR_OPEN) {
if(key == KEY_F1 || key == KEY_F2) {
/* TODO: Check if current source is unsaved? */
jfileselect_browse(fileselect, "/");
jscene_show_and_focus(scene, fileselect);
}
else if(key == KEY_F3) {
/* TODO: Memory browser */
}
else if(key == KEY_F4) {
/* TODO: New empty source */
}
else if(key == KEY_EXIT) {
app.fkmenu = FK_EDITOR;
update_fkeys();
}
}
else if(app.fkmenu == FK_VIEW) {
if(key == KEY_F4) {
jscene_show_and_focus(scene, mem);
app.fkmenu = FK_EDITOR;
update_fkeys();
}
else if(key == KEY_F5) {
/* TODO: Show info panel */
// jscene_show_and_focus(scene, tab_info);
// app.fkmenu = FK_INFO;
// update_fkeys();
}
else if(key == KEY_F6) {
/* TODO: Show statistics panel */
// jscene_show_and_focus(scene, tab_stats);
// app.fkmenu = FK_STATS;
// update_fkeys();
}
else if(key == KEY_EXIT) {
// if(current_tab == tab_editor)
app.fkmenu = FK_EDITOR;
// else if(current_tab == tab_info)
// app.fkmenu = FK_INFO;
// else if(current_tab == tab_stats)
// app.fkmenu = FK_STATS;
update_fkeys();
}
}
#if 0
if(key == KEY_F3 && !input_focus) {
if(!jwidget_visible(popup_source_info)) {
update_source_info();
jwidget_set_visible(popup_source_info, true);
jscene_set_focused_widget(scene, source_info);
}
else {
jwidget_set_visible(popup_source_info, false);
jscene_show_and_focus(scene, mem);
}
}
#endif
}
jwidget_destroy(scene);
}

View File

@ -30,8 +30,8 @@ size_t source_size(source_t *s)
ssize_t source_load(source_t *s, off_t offset, size_t segment_size)
{
if(!buffer_resize(s->buf, segment_size))
return false;
if(!s || !buffer_resize(s->buf, segment_size))
return -1;
s->buf_offset = offset;
ssize_t rc = s->intf->read(s->cookie, s->buf->mem, offset, segment_size);