From 9f1d806af813b1362885f28c20227ffebfa0896a Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Fri, 24 Jun 2022 20:08:47 +0100 Subject: [PATCH] per-source caps, hlist, and memory region selector --- CMakeLists.txt | 2 + src/heditor.c | 6 +- src/hlist.c | 170 +++++++++++++++++++++++++++++++++++++++ src/hlist.h | 61 ++++++++++++++ src/main.c | 60 +++++++++++--- src/source-lazy-file.c | 2 +- src/source-loaded-file.c | 3 +- src/source-memory.c | 49 +++++++++++ src/source-memory.h | 23 +++++- src/source.h | 4 +- src/util.c | 19 +++++ src/util.h | 3 + 12 files changed, 385 insertions(+), 17 deletions(-) create mode 100644 src/hlist.c create mode 100644 src/hlist.h create mode 100644 src/source-memory.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 52a5c05..7f328af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,10 +12,12 @@ find_package(JustUI 1.0 REQUIRED) set(SOURCES src/buffer.c src/heditor.c + src/hlist.c src/main.c src/source.c src/source-lazy-file.c src/source-loaded-file.c + src/source-memory.c src/util.c ) set(ASSETS diff --git a/src/heditor.c b/src/heditor.c index 4c31d3e..e9dc27f 100644 --- a/src/heditor.c +++ b/src/heditor.c @@ -164,7 +164,7 @@ bool heditor_save(heditor *e, char const *outfile) void heditor_set_insert_mode(heditor *e, bool insert) { e->insert = false; - if(e->source && e->source->intf->cap & SOURCE_RESIZE) + if(e->source && e->source->cap & SOURCE_RESIZE) e->insert = insert; shake(e); e->widget.update = true; @@ -345,9 +345,9 @@ static bool heditor_poly_event(void *e0, jevent ev) // Edition // - if(!(e->source->intf->cap & SOURCE_WRITE)) + if(!(e->source->cap & SOURCE_WRITE)) return false; - if(e->insert && !(e->source->intf->cap & SOURCE_RESIZE)) + if(e->insert && !(e->source->cap & SOURCE_RESIZE)) return false; int hexdigit = get_hexdigit(key); diff --git a/src/hlist.c b/src/hlist.c new file mode 100644 index 0000000..e831b7c --- /dev/null +++ b/src/hlist.c @@ -0,0 +1,170 @@ +#include "hlist.h" +#include +#include + +#include +#include + +/* Type identifier for hlist */ +static int hlist_type_id = -1; + +/* Events */ +uint16_t HLIST_VALIDATED; +uint16_t HLIST_CANCELED; + +hlist *hlist_create(void *parent) +{ + if(hlist_type_id < 0) return NULL; + + hlist *l = malloc(sizeof *l); + if(!l) return NULL; + + jwidget_init(&l->widget, hlist_type_id, parent); + + l->entries = NULL; + l->entry_size = 0; + l->entry_count = 0; + + l->render_height = 0; + l->render_func = NULL; + + l->selected_index = -1; + + l->cursor = 0; + l->scroll = 0; + l->visible_lines = 0; + + return l; +} + +static void count_visible_lines(hlist *l) +{ + int ch = jwidget_content_height(l); + l->visible_lines = ch / l->render_height; +} + +void hlist_set_entries(hlist *l, void *entries, size_t entry_size, int count) +{ + l->entries = entries; + l->entry_size = entry_size; + l->entry_count = count; + l->cursor = 0; + l->scroll = 0; + l->widget.update = true; +} + +void hlist_set_renderer(hlist *l, int render_height, void *render) +{ + if(l->render_height != render_height) + l->widget.dirty = true; + + l->render_height = render_height; + l->render_func = render; + l->widget.update = true; +} + +int hlist_selected_index(hlist *l) +{ + return l->selected_index; +} + +// Polymorphic widget operations // + +static void hlist_poly_csize(void *l0) +{ + hlist *l = l0; + int lines = l->entry_count; + int lheight = l->render_height; + + l->widget.w = 128; + l->widget.h = (lines && lheight) ? lines * lheight : 64; +} + +static void hlist_poly_layout(void *l0) +{ + hlist *l = l0; + count_visible_lines(l); +} + +static void hlist_poly_render(void *l0, int x, int y) +{ + hlist *l = l0; + if(!l->entries) + return; + + int cw = jwidget_content_width(l); + + for(int i = 0; i < l->visible_lines && l->scroll+i < l->entry_count; i++) { + bool selected = (l->cursor == l->scroll + i); + void *entry = l->entries + l->entry_size * i; + + int line_y = y + l->render_height * i; + if(l->render_func) + l->render_func(x, line_y, cw, l->render_height, entry, selected); + } +} + +static bool hlist_poly_event(void *l0, jevent e) +{ + hlist *l = l0; + if(!l->entries || e.type != JWIDGET_KEY) + return false; + + key_event_t ev = e.key; + if(ev.type != KEYEV_DOWN && ev.type != KEYEV_HOLD) + return false; + int key = ev.key; + + bool moved = false; + + if(key == KEY_UP && l->cursor > 0) { + l->cursor = ev.shift ? 0 : l->cursor - 1; + moved = true; + } + if(key == KEY_DOWN && l->cursor < l->entry_count - 1) { + l->cursor = ev.shift ? l->entry_count - 1 : l->cursor + 1; + moved = true; + } + + if(l->scroll > 0 && l->cursor <= l->scroll) + l->scroll = max(l->cursor - 1, 0); + if(l->scroll + l->visible_lines < l->entry_count + && l->cursor >= l->scroll + l->visible_lines - 2) { + l->scroll = min(l->cursor - l->visible_lines + 2, + l->entry_count - l->visible_lines); + } + + if(moved) { + l->widget.update = true; + return true; + } + + if(key == KEY_EXIT) { + jwidget_emit(l, (jevent){ .type = HLIST_CANCELED }); + return true; + } + else if(key == KEY_EXE) { + l->selected_index = l->cursor; + jwidget_emit(l, (jevent){ .type = HLIST_VALIDATED }); + return true; + } + + return false; +} + +/* hlist type definition */ +static jwidget_poly type_hlist = { + .name = "hlist", + .csize = hlist_poly_csize, + .layout = hlist_poly_layout, + .render = hlist_poly_render, + .event = hlist_poly_event, +}; + +__attribute__((constructor(1004))) +static void j_register_hlist(void) +{ + hlist_type_id = j_register_widget(&type_hlist, "jwidget"); + HLIST_VALIDATED = j_register_event(); + HLIST_CANCELED = j_register_event(); +} diff --git a/src/hlist.h b/src/hlist.h new file mode 100644 index 0000000..b90e2cd --- /dev/null +++ b/src/hlist.h @@ -0,0 +1,61 @@ +// hlist: A basic list selector +// +// This is like jfileselect, but for custom items. And also, maybe, a base for +// a reasonably-generic list view backed by a reasonable list model. Who knows. + +#pragma once +#include +#include +#include + +/* hlist: Generic list view and selector + + Events: + * HLIST_VALIDATED + * HLIST_CANCELED */ +typedef struct { + jwidget widget; + + /* List of entries; size of individual entries; number of entries */ + void *entries; + size_t entry_size; + int entry_count; + + /* Height in pixels for each entry */ + int render_height; + /* Renderer function */ + void (*render_func)(int x, int y, int w, int h, void *entry,bool selected); + + /* Selected entry ID */ + int selected_index; + + /* Current cursor position (0 .. entry_count-1) */ + int16_t cursor; + /* Current scroll position */ + int16_t scroll; + /* Number of visible lines */ + int8_t visible_lines; + +} hlist; + +/* Event IDs */ +extern uint16_t HLIST_VALIDATED; +extern uint16_t HLIST_CANCELED; + +/* hlist_create(): Create a list view and selector + Initially, there is no data and the widget does not handle events. */ +hlist *hlist_create(void *parent); + +/* hlist_set_entries(): Load the model (list of entries) */ +void hlist_set_entries(hlist *l, void *entries, size_t entry_size, int count); + +/* hlist_set_renderer(): Set the rendering settings for the entries + The rendering function should be of type: + void render(int x, int y, int width, int height, + void *pointer_to_entry, bool selected); */ +void hlist_set_renderer(hlist *h, int entry_height, void *render); + +/* hlist_selected_index(): Index of the selected entry + The selected index is -1 until hlist_set_entries() is called and the user + selects an entry in the interface. */ +int hlist_selected_index(hlist *l); diff --git a/src/main.c b/src/main.c index 08d3eb2..e105665 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ #include "source.h" #include "heditor.h" +#include "hlist.h" #include "util.h" #include @@ -78,7 +79,7 @@ static void update_fkeys(void) if(!app.source) jfkeys_set(app.fkeys, "/OPEN;;;;;"); else { - bool can_insert = (app.source->intf->cap & SOURCE_RESIZE) != 0; + bool can_insert = (app.source->cap & SOURCE_RESIZE) != 0; fkeys_sprintf(app.fkeys, "/OPEN;/SOURCE;%s%s;@GOTO;;#VIEW", can_insert ? "@" : "#", app.insert ? "INSERT" : "OVERW"); @@ -87,7 +88,7 @@ static void update_fkeys(void) else if(app.fkmenu == FK_EDITOR_SOURCE) { fkeys_sprintf(app.fkeys, "%s;%s;;;;", !app.source->no_origin ? "@SAVE" : "", - app.source->intf->cap & SOURCE_SAVEAS ? "@SAVE AS" : ""); + app.source->cap & SOURCE_SAVEAS ? "@SAVE AS" : ""); } else if(app.fkmenu == FK_EDITOR_OPEN) { jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW"); @@ -151,11 +152,11 @@ static void update_source_info(void) char caps[48]; memset(caps, 0, sizeof caps); - if(s->intf->cap & SOURCE_WRITE) + if(s->cap & SOURCE_WRITE) strcat(caps, " WRITE"); - if(s->intf->cap & SOURCE_RESIZE) + if(s->cap & SOURCE_RESIZE) strcat(caps, " RESIZE"); - if(s->intf->cap & SOURCE_SAVEAS) + if(s->cap & SOURCE_SAVEAS) strcat(caps, " SAVEAS"); jlabel_asprintf(app.source_info, @@ -253,6 +254,23 @@ static void open_source(source_t *source, bool insert) update_status(); } +static void render_memory_entry(int x, int y, int w, int h, + memory_region_t **r_ptr, bool selected) +{ + memory_region_t *r = *r_ptr; + + int fg = C_BLACK; + if(selected) { + drect(x, y, x+w-1, y+h-1, C_BLACK); + fg = C_WHITE; + } + + dprint(x+2, y+2, fg, "%08X %s", r->start, r->name); + dprint(x+3*w/4, y+2, fg, "(%s)", r->writable ? "rw" : "r"); + dtext_opt(x+w-3, y+2, fg, C_NONE, DTEXT_RIGHT, DTEXT_TOP, + human_isize(r->end - r->start, NULL), -1); +} + void hex_view(void) { memset(&app, 0, sizeof app); @@ -318,6 +336,17 @@ void hex_view(void) jfileselect_set_show_file_size(fileselect, true); jwidget_set_stretch(fileselect, 1, 1, false); + // Memory region selection tab // + + int memory_region_count = 0; + while(memory_all_regions[memory_region_count]) memory_region_count++; + + hlist *memoryselect = hlist_create(stack); + jwidget_set_stretch(memoryselect, 1, 1, false); + hlist_set_entries(memoryselect, memory_all_regions, + sizeof *memory_all_regions, memory_region_count); + hlist_set_renderer(memoryselect, 13, render_memory_entry); + // Info tab // jwidget *tab_info = jwidget_create(stack); @@ -380,7 +409,7 @@ void hex_view(void) char const *path = jfileselect_current_folder(fileselect); jlabel_asprintf(title, "Browsing: %s", path ? path : "(nil)"); } - if(e.type == JFILESELECT_CANCELED) { + if(e.type == JFILESELECT_CANCELED || e.type == HLIST_CANCELED) { jscene_show_and_focus(scene, mem); update_status(); app.fkmenu = FK_EDITOR; @@ -410,6 +439,15 @@ void hex_view(void) if(e.type == HEDITOR_CHANGED) { update_status(); } + if(e.type == HLIST_VALIDATED) { + int i = hlist_selected_index(memoryselect); + if(i >= 0) { + if(app.source) + source_free(app.source); + open_source(source_memory_open(memory_all_regions[i]), false); + jscene_show_and_focus(scene, mem); + } + } // Fkey menu navigation // @@ -441,7 +479,7 @@ void hex_view(void) update_fkeys(); } else if(key == KEY_F3 && app.source) { - if(app.source->intf->cap & SOURCE_RESIZE) { + if(app.source->cap & SOURCE_RESIZE) { app.insert = !app.insert; heditor_set_insert_mode(mem, app.insert); update_fkeys(); @@ -464,7 +502,7 @@ void hex_view(void) } } else if(key == KEY_F2 && app.source) { - if(app.source->intf->cap & SOURCE_SAVEAS) { + if(app.source->cap & SOURCE_SAVEAS) { browser_mode = SAVE_AS; jfileselect_set_saveas(fileselect, true); jfileselect_browse(fileselect, "/"); @@ -496,7 +534,11 @@ void hex_view(void) scene->widget.update = true; } else if(key == KEY_F3) { - /* TODO: Memory browser */ + if(!has_unsaved_changes() || confirm_discard()) { + jscene_show_and_focus(scene, memoryselect); + jlabel_set_text(title, "Browsing: memory"); + } + scene->widget.update = true; } else if(key == KEY_F4) { if(!has_unsaved_changes() || confirm_discard()) { diff --git a/src/source-lazy-file.c b/src/source-lazy-file.c index 36ecb5e..2607fe5 100644 --- a/src/source-lazy-file.c +++ b/src/source-lazy-file.c @@ -77,7 +77,6 @@ static void lz_close(void *_cookie) static source_intf_t lz_intf = { .name = "Lazy File", - .cap = SOURCE_WRITE, .close_on_return_to_menu = true, .read = lz_read, .write = lz_write, @@ -104,6 +103,7 @@ source_t *source_lazy_file_open(char const *path) source = source_open(&lz_intf, lz, path); if(!source) goto fail; + source->cap = SOURCE_WRITE; return source; fail: diff --git a/src/source-loaded-file.c b/src/source-loaded-file.c index 4362cec..8213561 100644 --- a/src/source-loaded-file.c +++ b/src/source-loaded-file.c @@ -87,7 +87,6 @@ static void lf_close(void *_cookie) static source_intf_t lf_intf = { .name = "Loaded File", - .cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS, .close_on_return_to_menu = false, .read = lf_read, .write = lf_write, @@ -126,6 +125,7 @@ source_t *source_loaded_file_open(char const *path) source = source_open(&lf_intf, lf, path); if(!source) goto fail; + source->cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS; close(fd); return source; @@ -154,6 +154,7 @@ source_t *source_loaded_file_new(void) if(!source) goto fail; source->no_origin = true; + source->cap = SOURCE_WRITE | SOURCE_RESIZE | SOURCE_SAVEAS; return source; fail: diff --git a/src/source-memory.c b/src/source-memory.c new file mode 100644 index 0000000..e929f3c --- /dev/null +++ b/src/source-memory.c @@ -0,0 +1,49 @@ +#include "source.h" +#include + +static memory_region_t REGION_ROM = { + .start = 0x80000000, + .end = 0x82000000, + .writable = false, + .name = "ROM", +}; +static memory_region_t REGION_RAM = { + .start = 0x8c000000, + .end = 0x8c800000, + .writable = false, + .name = "RAM", +}; +static memory_region_t REGION_ILRAM = { + .start = 0xe5200000, + .end = 0xe5201000, + .writable = true, + .name = "ILRAM", +}; +static memory_region_t REGION_XYRAM = { + .start = 0xe500e000, + .end = 0xe5012000, + .writable = true, + .name = "XRAM/YRAM", +}; +static memory_region_t REGION_RS = { + .start = 0xfd800000, + .end = 0xfd800800, + .writable = true, + .name = "RS", +}; + +memory_region_t *memory_all_regions[] = { + ®ION_ROM, + ®ION_RAM, + ®ION_ILRAM, + ®ION_XYRAM, + ®ION_RS, + NULL, +}; + +// TODO: Add offset to source + heditor view so we can see real memory addresses + +source_t *source_memory_open(memory_region_t *region) +{ + return NULL; +} diff --git a/src/source-memory.h b/src/source-memory.h index 70b786d..c6ccd81 100644 --- a/src/source-memory.h +++ b/src/source-memory.h @@ -1 +1,22 @@ -// TODO +// source-memory: A region of memory +// +// This data source is just a chunk of virtual memory. Be careful that memory +// is very volatile and the hex editor doesn't update automatically. + +#pragma once +#include + +typedef struct { + /* Start and end addresses */ + uint32_t start, end; + /* Whether that particular region is writable */ + bool writable; + /* Region name */ + char const *name; + +} memory_region_t; + +/* All memory regions (NULL terminated) */ +extern memory_region_t *memory_all_regions[]; + +source_t *source_memory_open(memory_region_t *region); diff --git a/src/source.h b/src/source.h index 8f46464..eb3b3f4 100644 --- a/src/source.h +++ b/src/source.h @@ -24,8 +24,6 @@ typedef struct { /* Interface name */ char const *name; - /* Source capabilities */ - int cap; /* Whether the source should be closed before we return to menu */ bool close_on_return_to_menu; /* Load a chunk from the data source into a buffer. */ @@ -53,6 +51,8 @@ typedef struct { /* Interface identifier, and opaque interface data */ source_intf_t *intf; void *cookie; + /* Source capabilities */ + int cap; /* Source name. Can be any string: file path, "memory at X", etc. */ char *origin; /* If set, there is no origin and the "SAVE" button is disabled (only diff --git a/src/util.c b/src/util.c index fd1d1de..bed2014 100644 --- a/src/util.c +++ b/src/util.c @@ -76,3 +76,22 @@ char const *human_size(int size, char *str) return str; } + +char const *human_isize(int size, char *str) +{ + static char default_buffer[64]; + if(str == NULL) + str = default_buffer; + + char digits[16]; + sprintf(digits, "%d", size); + + if(size < (1 << 10)) /* 1 kiB */ + sprintf(str, "%d B", size); + else if(size < (1 << 20)) /* 1 MiB */ + sprintf(str, "%d kiB", size >> 10); + else + sprintf(str, "%d MiB", size >> 20); + + return str; +} diff --git a/src/util.h b/src/util.h index 84e126e..a74d0c3 100644 --- a/src/util.h +++ b/src/util.h @@ -13,3 +13,6 @@ bool confirm_discard(void); /* Human version of file sizes. If str=NULL, returns a static buffer that is overridden by further calls. */ char const *human_size(int size, char *str); + +/* Human version of file sizes with SI units. */ +char const *human_isize(int size, char *str);