diff --git a/assets-cg/icon-sel.png b/assets-cg/icon-sel.png index 2d6b250..738a9f9 100644 Binary files a/assets-cg/icon-sel.png and b/assets-cg/icon-sel.png differ diff --git a/assets-cg/icon-uns.png b/assets-cg/icon-uns.png index 59586b4..acc867a 100644 Binary files a/assets-cg/icon-uns.png and b/assets-cg/icon-uns.png differ diff --git a/assets-cg/icon.xcf b/assets-cg/icon.xcf index d57fd44..caa65b5 100644 Binary files a/assets-cg/icon.xcf and b/assets-cg/icon.xcf differ diff --git a/src/heditor.c b/src/heditor.c index dc9186f..0c67174 100644 --- a/src/heditor.c +++ b/src/heditor.c @@ -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); diff --git a/src/heditor.h b/src/heditor.h index 7605d82..6feacca 100644 --- a/src/heditor.h +++ b/src/heditor.h @@ -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); diff --git a/src/main.c b/src/main.c index 66c4f54..128526d 100644 --- a/src/main.c +++ b/src/main.c @@ -15,38 +15,170 @@ #include #include -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 : "", + 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); } diff --git a/src/source.c b/src/source.c index acf4dd6..de30c3b 100644 --- a/src/source.c +++ b/src/source.c @@ -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);