forked from Lephenixnoir/hex-editor
heditor: edit and save the front buffer (not the whole source)
This commit is contained in:
parent
a08e7fa89d
commit
215d2c6512
|
@ -32,6 +32,8 @@ heditor *heditor_create(jwidget *parent)
|
|||
e->bytes_per_line = 8;
|
||||
e->line_spacing = 3;
|
||||
e->font = dfont_default();
|
||||
e->half_byte = false;
|
||||
e->front_buffer_dirty = false;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
@ -57,6 +59,11 @@ static void shake(heditor *e)
|
|||
heditor_move_to(e, e->cursor);
|
||||
}
|
||||
|
||||
static void emit_changed(heditor *e)
|
||||
{
|
||||
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
|
||||
}
|
||||
|
||||
bool heditor_move_to(heditor *e, int target_cursor)
|
||||
{
|
||||
bool changed = false;
|
||||
|
@ -77,8 +84,10 @@ bool heditor_move_to(heditor *e, int target_cursor)
|
|||
|
||||
target_cursor = max(0, min(target_cursor, max_cursor));
|
||||
|
||||
if(target_cursor != e->cursor)
|
||||
if(target_cursor != e->cursor) {
|
||||
changed = true;
|
||||
e->half_byte = false;
|
||||
}
|
||||
e->cursor = target_cursor;
|
||||
|
||||
/* Determine the scroll offset for the target. If the target falls within
|
||||
|
@ -108,16 +117,32 @@ bool heditor_move_to(heditor *e, int target_cursor)
|
|||
|
||||
int view_size = e->visible_lines * bpl;
|
||||
view_size = min(view_size, max(source_size(e->source) - e->scroll, 0));
|
||||
if(source_ensure_loaded(e->source, e->scroll, view_size))
|
||||
changed = true;
|
||||
if(source_requires_load(e->source, e->scroll, view_size)) {
|
||||
/* Don't lose the contents of the front buffer if it fails to save */
|
||||
if(!e->front_buffer_dirty || source_save_front_buffer(e->source)) {
|
||||
source_ensure_loaded(e->source, e->scroll, view_size);
|
||||
e->front_buffer_dirty = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(changed) {
|
||||
e->widget.update = true;
|
||||
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
|
||||
emit_changed(e);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool heditor_save_front_buffer(heditor *e)
|
||||
{
|
||||
if(e && source_save_front_buffer(e->source)) {
|
||||
e->front_buffer_dirty = false;
|
||||
emit_changed(e);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void heditor_set_insert_mode(heditor *e, bool insert)
|
||||
{
|
||||
e->insert = false;
|
||||
|
@ -132,7 +157,7 @@ void heditor_set_source(heditor *e, source_t *source)
|
|||
/* Force a change event */
|
||||
if(!heditor_move_to(e, 0)) {
|
||||
e->widget.update = true;
|
||||
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
|
||||
emit_changed(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +191,6 @@ static void heditor_poly_csize(void *e0)
|
|||
heditor *e = e0;
|
||||
jwidget *w = &e->widget;
|
||||
|
||||
// TODO: heditor: Subtler content size estimation
|
||||
w->w = 128;
|
||||
w->h = 64;
|
||||
}
|
||||
|
@ -207,7 +231,10 @@ static void heditor_poly_render(void *e0, int x, int y)
|
|||
if(o >= s->buf_offset && o < front_buffer_max) {
|
||||
int c = *(uint8_t *)(s->buf->mem + o - s->buf_offset);
|
||||
|
||||
sprintf(byte, "%02X", c);
|
||||
if(e->cursor == o && e->half_byte)
|
||||
sprintf(byte, "%01X_", c >> 4);
|
||||
else
|
||||
sprintf(byte, "%02X", c);
|
||||
dprint(ascii_x, line_y, C_BLACK, "%c",
|
||||
(c >= 0x20 && c < 0x7f) ? c : '.');
|
||||
}
|
||||
|
@ -237,14 +264,26 @@ static void heditor_poly_render(void *e0, int x, int y)
|
|||
}
|
||||
}
|
||||
|
||||
static int get_hexdigit(int key)
|
||||
{
|
||||
int digit = keycode_digit(key);
|
||||
if(digit >= 0)
|
||||
return digit;
|
||||
if(key >= KEY_XOT && key <= KEY_TAN)
|
||||
return key - KEY_XOT + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool heditor_poly_event(void *e0, jevent ev)
|
||||
{
|
||||
heditor *e = e0;
|
||||
if(!e->source)
|
||||
return false;
|
||||
if(ev.type != JSCENE_KEY || ev.key.type == KEYEV_UP)
|
||||
source_t *s = e->source;
|
||||
|
||||
if(!s || ev.type != JSCENE_KEY || ev.key.type == KEYEV_UP)
|
||||
return false;
|
||||
|
||||
// Movement //
|
||||
|
||||
int key = ev.key.key;
|
||||
int move_speed = (ev.key.alpha ? e->visible_lines : 1);
|
||||
|
||||
|
@ -275,6 +314,40 @@ static bool heditor_poly_event(void *e0, jevent ev)
|
|||
return true;
|
||||
}
|
||||
|
||||
// Edition //
|
||||
|
||||
if(!(e->source->intf->cap & SOURCE_WRITE))
|
||||
return false;
|
||||
if(e->insert && !(e->source->intf->cap & SOURCE_EXPAND))
|
||||
return false;
|
||||
|
||||
int offset = e->cursor - s->buf_offset;
|
||||
if(offset < 0 || (size_t)offset >= s->buf->data_size)
|
||||
return false;
|
||||
|
||||
uint8_t *pointed_byte = s->buf->mem + (e->cursor - s->buf_offset);
|
||||
|
||||
int hexdigit = get_hexdigit(key);
|
||||
if(hexdigit >= 0) {
|
||||
if(e->half_byte) {
|
||||
*pointed_byte = (*pointed_byte & 0xf0) | hexdigit;
|
||||
e->half_byte = false;
|
||||
e->front_buffer_dirty = true;
|
||||
e->widget.update = true;
|
||||
if(!heditor_move_to(e, e->cursor + 1))
|
||||
emit_changed(e);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
*pointed_byte = (hexdigit << 4);
|
||||
e->half_byte = true;
|
||||
e->front_buffer_dirty = true;
|
||||
e->widget.update = true;
|
||||
emit_changed(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ typedef struct {
|
|||
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 */
|
||||
|
@ -34,6 +35,11 @@ typedef struct {
|
|||
/* Rendering font */
|
||||
font_t const *font;
|
||||
|
||||
/* Whether we are currently writing into a half-finished byte */
|
||||
bool half_byte;
|
||||
/* Whether the front buffer is dirty */
|
||||
bool front_buffer_dirty;
|
||||
|
||||
} heditor;
|
||||
|
||||
/* Type IDs */
|
||||
|
@ -49,6 +55,9 @@ void heditor_set_source(heditor *e, source_t *source);
|
|||
/* Scroll to the requested cursor position. */
|
||||
bool heditor_move_to(heditor *e, int offset);
|
||||
|
||||
/* Save the front buffer. */
|
||||
bool heditor_save_front_buffer(heditor *e);
|
||||
|
||||
/* 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);
|
||||
|
|
137
src/main.c
137
src/main.c
|
@ -22,8 +22,12 @@ struct {
|
|||
jlabel *title;
|
||||
jlabel *status;
|
||||
jfkeys *fkeys;
|
||||
/* Main editor. */
|
||||
heditor *editor;
|
||||
/* Source info widget. */
|
||||
jlabel *source_info;
|
||||
/* Source stats widget. */
|
||||
jlabel *source_stats;
|
||||
|
||||
/* Current data source. */
|
||||
source_t *source;
|
||||
|
@ -45,6 +49,8 @@ enum {
|
|||
FK_EDITOR,
|
||||
FK_EDITOR_SOURCE,
|
||||
FK_EDITOR_OPEN,
|
||||
FK_INFO,
|
||||
FK_STATS,
|
||||
FK_VIEW,
|
||||
};
|
||||
|
||||
|
@ -73,11 +79,17 @@ static void update_fkeys(void)
|
|||
}
|
||||
}
|
||||
else if(app.fkmenu == FK_EDITOR_SOURCE) {
|
||||
jfkeys_set(app.fkeys, "@SAVE;@SAVE AS;;;;");
|
||||
jfkeys_set(app.fkeys, "@SAVE;@SAVE AS;@FRONT;;;");
|
||||
}
|
||||
else if(app.fkmenu == FK_EDITOR_OPEN) {
|
||||
jfkeys_set(app.fkeys, "@FILE;@LAZY;@MEMORY;@NEW");
|
||||
}
|
||||
else if(app.fkmenu == FK_INFO) {
|
||||
jfkeys_set(app.fkeys, ";;;;;#VIEW");
|
||||
}
|
||||
else if(app.fkmenu == FK_STATS) {
|
||||
jfkeys_set(app.fkeys, ";;;;;#VIEW");
|
||||
}
|
||||
else if(app.fkmenu == FK_VIEW) {
|
||||
fkeys_sprintf(app.fkeys, ";;;%sEDITOR;%sINFO;%sSTATS",
|
||||
app.view == VIEW_EDITOR ? "#" : "@",
|
||||
|
@ -88,7 +100,6 @@ static void update_fkeys(void)
|
|||
|
||||
static void update_status(void)
|
||||
{
|
||||
/* TODO: (*) and (**) markers on title/status bar */
|
||||
source_t *s = app.source;
|
||||
|
||||
if(!s) {
|
||||
|
@ -100,8 +111,16 @@ static void update_status(void)
|
|||
size_t size = source_size(s);
|
||||
jwidget_set_visible(app.status, true);
|
||||
|
||||
if(s->origin)
|
||||
jlabel_asprintf(app.title, "%s (%d B) - Hex Editor", s->origin, size);
|
||||
if(s->origin) {
|
||||
char const *dirty = "";
|
||||
if(app.editor->front_buffer_dirty)
|
||||
dirty = " **";
|
||||
/* TODO: (*) marker on title bar */
|
||||
else if(0)
|
||||
dirty = " *";
|
||||
jlabel_asprintf(app.title, "%s%s (%d B) - Hex Editor",
|
||||
s->origin, dirty, size);
|
||||
}
|
||||
else
|
||||
jlabel_set_text(app.title, "(nil) - Hex Editor");
|
||||
|
||||
|
@ -132,35 +151,15 @@ static void update_source_info(void)
|
|||
"%s\n"
|
||||
"Size: %d bytes\n"
|
||||
"Front buffer: %p, size %d/%d (x%d)\n"
|
||||
"Front buffer maps to: @%d, size TODO\n"
|
||||
"Front buffer maps to: @%d, size %d\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, */
|
||||
s->buf_offset, s->buf_segment_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)
|
||||
{
|
||||
app.source = NULL;
|
||||
|
@ -192,10 +191,11 @@ void hex_view(void)
|
|||
|
||||
// Main tab //
|
||||
|
||||
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);
|
||||
jwidget *tab_editor = jwidget_create(NULL);
|
||||
heditor *mem = heditor_create(tab_editor); /* previously 321x153 */
|
||||
jinput *goto_input = jinput_create("Goto: ", 12, tab_editor);
|
||||
jlabel *status = jlabel_create("", tab_editor);
|
||||
app.editor = mem;
|
||||
app.status = status;
|
||||
|
||||
jwidget_set_margin(mem, 2, 0, 0, 0);
|
||||
|
@ -208,9 +208,9 @@ void hex_view(void)
|
|||
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);
|
||||
jlayout_set_vbox(tab_editor)->spacing = 3;
|
||||
jwidget_add_child(stack, tab_editor);
|
||||
jwidget_set_stretch(tab_editor, 1, 1, false);
|
||||
|
||||
// File selection tab //
|
||||
|
||||
|
@ -218,16 +218,27 @@ void hex_view(void)
|
|||
jfileselect_set_show_file_size(fileselect, true);
|
||||
jwidget_set_stretch(fileselect, 1, 1, false);
|
||||
|
||||
// Source info popup //
|
||||
// Info tab //
|
||||
|
||||
jwidget *popup_source_info = make_popup(scene, DWIDTH*3/4, DHEIGHT/2);
|
||||
jlayout_set_vbox(popup_source_info)->spacing = 3;
|
||||
jwidget *tab_info = jwidget_create(stack);
|
||||
jwidget_set_stretch(tab_info, 1, 1, false);
|
||||
jlayout_set_vbox(tab_info)->spacing = 3;
|
||||
|
||||
jlabel *source_info = jlabel_create("", popup_source_info);
|
||||
jlabel *source_info = jlabel_create("", tab_info);
|
||||
jlabel_set_wrap_mode(source_info, J_WRAP_WORD);
|
||||
jlabel_set_line_spacing(source_info, 2);
|
||||
jlabel_set_line_spacing(source_info, 4);
|
||||
jlabel_set_block_alignment(source_info, J_ALIGN_LEFT, J_ALIGN_TOP);
|
||||
jwidget_set_stretch(source_info, 1, 1, false);
|
||||
app.source_info = source_info;
|
||||
|
||||
// Stats tab //
|
||||
|
||||
jwidget *tab_stats = jwidget_create(stack);
|
||||
jwidget_set_stretch(tab_stats, 1, 1, false);
|
||||
jlayout_set_vbox(tab_stats)->spacing = 3;
|
||||
|
||||
jlabel *source_stats = jlabel_create("TODO", tab_stats);
|
||||
app.source_stats = source_stats;
|
||||
|
||||
// Initial state //
|
||||
|
||||
|
@ -325,13 +336,21 @@ void hex_view(void)
|
|||
else if(key == KEY_F2 && app.source) {
|
||||
/* TODO: Source save as */
|
||||
}
|
||||
else if(key == KEY_F3 && app.source) {
|
||||
heditor_save_front_buffer(mem);
|
||||
}
|
||||
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) {
|
||||
if(key == KEY_F1) {
|
||||
/* TODO: Check if current source is unsaved? */
|
||||
jfileselect_browse(fileselect, "/");
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
}
|
||||
else if(key == KEY_F2) {
|
||||
/* TODO: Check if current source is unsaved? */
|
||||
jfileselect_browse(fileselect, "/");
|
||||
jscene_show_and_focus(scene, fileselect);
|
||||
|
@ -347,31 +366,45 @@ void hex_view(void)
|
|||
update_fkeys();
|
||||
}
|
||||
}
|
||||
else if(app.fkmenu == FK_INFO) {
|
||||
if(key == KEY_F6 && app.source) {
|
||||
app.fkmenu = FK_VIEW;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
else if(app.fkmenu == FK_STATS) {
|
||||
if(key == KEY_F6 && app.source) {
|
||||
app.fkmenu = FK_VIEW;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
else if(app.fkmenu == FK_VIEW) {
|
||||
if(key == KEY_F4) {
|
||||
jscene_show_and_focus(scene, mem);
|
||||
app.fkmenu = FK_EDITOR;
|
||||
app.view = VIEW_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();
|
||||
jscene_show_and_focus(scene, source_info);
|
||||
app.fkmenu = FK_INFO;
|
||||
app.view = VIEW_INFO;
|
||||
update_source_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();
|
||||
jscene_show_and_focus(scene, source_stats);
|
||||
app.fkmenu = FK_STATS;
|
||||
app.view = VIEW_STATS;
|
||||
update_fkeys();
|
||||
}
|
||||
else if(key == KEY_EXIT) {
|
||||
// if(current_tab == tab_editor)
|
||||
if(app.view == VIEW_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;
|
||||
else if(app.view == VIEW_INFO)
|
||||
app.fkmenu = FK_INFO;
|
||||
else if(app.view == VIEW_STATS)
|
||||
app.fkmenu = FK_STATS;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
|
|
23
src/source.c
23
src/source.c
|
@ -33,24 +33,31 @@ ssize_t source_load(source_t *s, off_t offset, size_t segment_size)
|
|||
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);
|
||||
|
||||
if(rc < 0)
|
||||
return -1;
|
||||
|
||||
s->buf_offset = offset;
|
||||
s->buf_segment_size = rc;
|
||||
s->buf->data_size = rc;
|
||||
return rc;
|
||||
}
|
||||
|
||||
bool source_ensure_loaded(source_t *s, off_t offset, int n)
|
||||
bool source_requires_load(source_t *s, off_t offset, int n)
|
||||
{
|
||||
if(!s)
|
||||
return false;
|
||||
|
||||
if(offset >= s->buf_offset &&
|
||||
offset + n <= (int)(s->buf_offset + s->buf->data_size))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void source_ensure_loaded(source_t *s, off_t offset, int n)
|
||||
{
|
||||
if(!source_requires_load(s, offset, n))
|
||||
return;
|
||||
|
||||
int s_size = source_size(s);
|
||||
int max_offset = max(s_size - n, 0);
|
||||
|
@ -61,7 +68,15 @@ bool source_ensure_loaded(source_t *s, off_t offset, int n)
|
|||
|
||||
/* Use 2 blocks of buffer by default */
|
||||
source_load(s, new_s_offset, 2 * s->buf->block_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool source_save_front_buffer(source_t *s)
|
||||
{
|
||||
if(!s || !s->intf->write)
|
||||
return false;
|
||||
|
||||
return s->intf->write(s->cookie, s->buf->mem, s->buf->data_size,
|
||||
s->buf_offset, s->buf_segment_size);
|
||||
}
|
||||
|
||||
void source_free(source_t *s)
|
||||
|
|
13
src/source.h
13
src/source.h
|
@ -52,6 +52,10 @@ typedef struct {
|
|||
buffer_t *buf;
|
||||
/* Address of the front buffer in the source */
|
||||
off_t buf_offset;
|
||||
/* Length of the front buffer in the source */
|
||||
int buf_segment_size;
|
||||
/* TODO: Whether the source is dirty, ie. has unsaved data other than in
|
||||
the front buffer */
|
||||
} source_t;
|
||||
|
||||
/* Open a data source; this is a low-level function used by source-specific
|
||||
|
@ -65,10 +69,17 @@ size_t source_size(source_t *s);
|
|||
/* Replace the source's buffer's contents with data read from the source. */
|
||||
ssize_t source_load(source_t *s, off_t offset, size_t segment_size);
|
||||
|
||||
/* Determine whether the `n` bytes following `offset` are currently loaded in
|
||||
the front buffer. */
|
||||
bool source_requires_load(source_t *s, off_t offset, int n);
|
||||
|
||||
/* Make sure that the `n` bytes following `offset` are loaded in the front
|
||||
buffer. If not, load the region around there. Returns true if the region
|
||||
changed, otherwise false. */
|
||||
bool source_ensure_loaded(source_t *s, off_t offset, int n);
|
||||
void source_ensure_loaded(source_t *s, off_t offset, int n);
|
||||
|
||||
/* Push the front buffer into the data source. */
|
||||
bool source_save_front_buffer(source_t *s);
|
||||
|
||||
/* Free a source and its data. */
|
||||
void source_free(source_t *s);
|
||||
|
|
Loading…
Reference in New Issue