forked from Lephenixnoir/hex-editor
per-source caps, hlist, and memory region selector
This commit is contained in:
parent
ffa2199824
commit
9f1d806af8
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
#include "hlist.h"
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 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();
|
||||
}
|
|
@ -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 <justui/defs.h>
|
||||
#include <justui/jwidget.h>
|
||||
#include <gint/display.h>
|
||||
|
||||
/* 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);
|
60
src/main.c
60
src/main.c
|
@ -1,5 +1,6 @@
|
|||
#include "source.h"
|
||||
#include "heditor.h"
|
||||
#include "hlist.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
|
@ -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()) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
#include "source.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 <stddef.h>
|
||||
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
|
|
19
src/util.c
19
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue