add saving and insertion mode
This commit is contained in:
parent
215d2c6512
commit
f3578e112d
|
@ -15,6 +15,7 @@ set(SOURCES
|
|||
src/main.c
|
||||
src/source.c
|
||||
src/source-loaded-file.c
|
||||
src/util.c
|
||||
)
|
||||
set(ASSETS
|
||||
)
|
||||
|
|
31
src/buffer.c
31
src/buffer.c
|
@ -68,6 +68,37 @@ bool buffer_write(buffer_t *b, void *data, size_t data_size, off_t offset,
|
|||
return true;
|
||||
}
|
||||
|
||||
uint8_t *buffer_at(buffer_t *b, off_t offset)
|
||||
{
|
||||
if(!b || !b->mem || offset < 0 || offset >= (int)b->data_size)
|
||||
return NULL;
|
||||
return b->mem + offset;
|
||||
}
|
||||
|
||||
uint8_t *buffer_insert_at(buffer_t *b, off_t offset)
|
||||
{
|
||||
if(!b || offset < 0 || offset > (int)b->data_size)
|
||||
return NULL;
|
||||
if(!buffer_resize(b, b->data_size + 1))
|
||||
return NULL;
|
||||
|
||||
uint8_t *byte = b->mem + offset;
|
||||
memmove(byte+1, byte, b->data_size - offset);
|
||||
b->data_size += 1;
|
||||
return byte;
|
||||
}
|
||||
|
||||
bool buffer_remove_at(buffer_t *b, off_t offset)
|
||||
{
|
||||
if(!b || offset < 0 || offset >= (int)b->data_size)
|
||||
return false;
|
||||
|
||||
uint8_t *byte = b->mem + offset;
|
||||
memmove(byte, byte+1, b->data_size - offset - 1);
|
||||
b->data_size--;
|
||||
return true;
|
||||
}
|
||||
|
||||
void buffer_free(buffer_t *b)
|
||||
{
|
||||
if(b)
|
||||
|
|
10
src/buffer.h
10
src/buffer.h
|
@ -40,5 +40,15 @@ bool buffer_write(buffer_t *b,
|
|||
void *data, size_t data_size, /* Source */
|
||||
off_t offset, size_t segment_size); /* Destination */
|
||||
|
||||
/* Return the address of byte at the given offset, NULL if out of bounds. */
|
||||
uint8_t *buffer_at(buffer_t *b, off_t offset);
|
||||
|
||||
/* Add a gap byte at the requested offset within the buffer. Returns pointer to
|
||||
byte, NULL on error. */
|
||||
uint8_t *buffer_insert_at(buffer_t *b, off_t offset);
|
||||
|
||||
/* Remove the byte at the specified offset */
|
||||
bool buffer_remove_at(buffer_t *b, off_t offset);
|
||||
|
||||
/* Free a buffer, including `b` itself. */
|
||||
void buffer_free(buffer_t *b);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include "heditor.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <justui/jwidget.h>
|
||||
#include <justui/jwidget-api.h>
|
||||
#include <justui/jscene.h>
|
||||
|
@ -135,7 +137,10 @@ bool heditor_move_to(heditor *e, int target_cursor)
|
|||
|
||||
bool heditor_save_front_buffer(heditor *e)
|
||||
{
|
||||
if(e && source_save_front_buffer(e->source)) {
|
||||
if(!e || !e->front_buffer_dirty)
|
||||
return true;
|
||||
|
||||
if(source_save_front_buffer(e->source)) {
|
||||
e->front_buffer_dirty = false;
|
||||
emit_changed(e);
|
||||
return true;
|
||||
|
@ -143,6 +148,19 @@ bool heditor_save_front_buffer(heditor *e)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool heditor_save(heditor *e)
|
||||
{
|
||||
if(!e)
|
||||
return true;
|
||||
if(e->front_buffer_dirty && !heditor_save_front_buffer(e))
|
||||
return false;
|
||||
if(!source_dirty(e->source))
|
||||
return true;
|
||||
|
||||
please_wait();
|
||||
return source_save(e->source, NULL);
|
||||
}
|
||||
|
||||
void heditor_set_insert_mode(heditor *e, bool insert)
|
||||
{
|
||||
e->insert = false;
|
||||
|
@ -154,6 +172,8 @@ void heditor_set_insert_mode(heditor *e, bool insert)
|
|||
void heditor_set_source(heditor *e, source_t *source)
|
||||
{
|
||||
e->source = source;
|
||||
e->front_buffer_dirty = false;
|
||||
|
||||
/* Force a change event */
|
||||
if(!heditor_move_to(e, 0)) {
|
||||
e->widget.update = true;
|
||||
|
@ -245,7 +265,8 @@ static void heditor_poly_render(void *e0, int x, int y)
|
|||
int fg = C_BLACK;
|
||||
if(e->cursor == o) {
|
||||
if(e->insert) {
|
||||
drect(bytes_x - 2, line_y - 1, bytes_x - 1,
|
||||
int x = bytes_x + (e->half_byte ? 8 : 0);
|
||||
drect(x - 2, line_y - 1, x - 1,
|
||||
line_y + e->font->line_height, C_BLACK);
|
||||
}
|
||||
else {
|
||||
|
@ -321,14 +342,17 @@ static bool heditor_poly_event(void *e0, jevent ev)
|
|||
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) {
|
||||
uint8_t *pointed_byte = NULL;
|
||||
if(e->insert && !e->half_byte)
|
||||
pointed_byte = buffer_insert_at(s->buf, e->cursor - s->buf_offset);
|
||||
else
|
||||
pointed_byte = buffer_at(s->buf, e->cursor - s->buf_offset);
|
||||
|
||||
if(!pointed_byte)
|
||||
return false;
|
||||
|
||||
if(e->half_byte) {
|
||||
*pointed_byte = (*pointed_byte & 0xf0) | hexdigit;
|
||||
e->half_byte = false;
|
||||
|
@ -348,6 +372,31 @@ static bool heditor_poly_event(void *e0, jevent ev)
|
|||
}
|
||||
}
|
||||
|
||||
else if(key == KEY_DEL && e->insert) {
|
||||
if(!e->half_byte) {
|
||||
off_t offset = e->cursor - 1 - s->buf_offset;
|
||||
uint8_t *pointed_byte = buffer_at(s->buf, offset);
|
||||
if(pointed_byte) {
|
||||
*pointed_byte &= 0xf0;
|
||||
e->front_buffer_dirty = true;
|
||||
e->widget.update = true;
|
||||
if(!heditor_move_to(e, e->cursor - 1))
|
||||
emit_changed(e);
|
||||
e->half_byte = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(buffer_remove_at(s->buf, e->cursor - s->buf_offset)) {
|
||||
e->half_byte = false;
|
||||
e->front_buffer_dirty = true;
|
||||
e->widget.update = true;
|
||||
emit_changed(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,9 @@ bool heditor_move_to(heditor *e, int offset);
|
|||
/* Save the front buffer. */
|
||||
bool heditor_save_front_buffer(heditor *e);
|
||||
|
||||
/* Save the data source. */
|
||||
bool heditor_save(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);
|
||||
|
|
18
src/main.c
18
src/main.c
|
@ -1,5 +1,6 @@
|
|||
#include "source.h"
|
||||
#include "heditor.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <gint/display.h>
|
||||
#include <gint/keyboard.h>
|
||||
|
@ -115,11 +116,10 @@ static void update_status(void)
|
|||
char const *dirty = "";
|
||||
if(app.editor->front_buffer_dirty)
|
||||
dirty = " **";
|
||||
/* TODO: (*) marker on title bar */
|
||||
else if(0)
|
||||
else if(source_dirty(app.source))
|
||||
dirty = " *";
|
||||
jlabel_asprintf(app.title, "%s%s (%d B) - Hex Editor",
|
||||
s->origin, dirty, size);
|
||||
jlabel_asprintf(app.title, "%s%s (%s) - Hex Editor",
|
||||
s->origin, dirty, human_size(size, NULL));
|
||||
}
|
||||
else
|
||||
jlabel_set_text(app.title, "(nil) - Hex Editor");
|
||||
|
@ -331,13 +331,19 @@ void hex_view(void)
|
|||
}
|
||||
else if(app.fkmenu == FK_EDITOR_SOURCE) {
|
||||
if(key == KEY_F1 && app.source) {
|
||||
/* TODO: Source save */
|
||||
if(heditor_save(mem)) {
|
||||
app.fkmenu = FK_EDITOR;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
else if(key == KEY_F2 && app.source) {
|
||||
/* TODO: Source save as */
|
||||
}
|
||||
else if(key == KEY_F3 && app.source) {
|
||||
heditor_save_front_buffer(mem);
|
||||
if(heditor_save_front_buffer(mem)) {
|
||||
app.fkmenu = FK_EDITOR;
|
||||
update_fkeys();
|
||||
}
|
||||
}
|
||||
else if(key == KEY_EXIT) {
|
||||
app.fkmenu = FK_EDITOR;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "source.h"
|
||||
#include <gint/gint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
@ -29,7 +30,11 @@ static bool lf_write(void *_cookie, void *data, size_t data_size, off_t offset,
|
|||
size_t segment_size)
|
||||
{
|
||||
loaded_file_t *lf = _cookie;
|
||||
return buffer_write(lf->buf, data, data_size, offset, segment_size);
|
||||
if(buffer_write(lf->buf, data, data_size, offset, segment_size)) {
|
||||
lf->dirty = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t lf_size(void *_cookie)
|
||||
|
@ -38,22 +43,30 @@ static size_t lf_size(void *_cookie)
|
|||
return lf->buf->data_size;
|
||||
}
|
||||
|
||||
static bool lf_save(void *_cookie)
|
||||
static bool lf_dirty(void *_cookie)
|
||||
{
|
||||
loaded_file_t *lf = _cookie;
|
||||
return lf->dirty;
|
||||
}
|
||||
|
||||
static bool lf_save_switch(loaded_file_t *lf, char const *destination)
|
||||
{
|
||||
if(!lf->dirty && destination == NULL)
|
||||
return true;
|
||||
|
||||
/* If the new file is larger than the original, we can just overwrite it
|
||||
without truncating, which is much faster with Fugue */
|
||||
int flags = O_WRONLY | O_TRUNC;
|
||||
if(lf->buf->data_size >= lf->original_size)
|
||||
flags = O_WRONLY;
|
||||
int fd = open(lf->path, flags);
|
||||
int flags = O_CREAT | O_WRONLY | O_TRUNC;
|
||||
if(destination == NULL && lf->buf->data_size >= lf->original_size)
|
||||
flags = O_CREAT | O_WRONLY;
|
||||
int fd = open(destination ? destination : lf->path, flags);
|
||||
if(!fd) goto fail;
|
||||
|
||||
ssize_t rc = write(fd, lf->buf->mem, lf->buf->data_size);
|
||||
if(rc < 0 || (size_t)rc != lf->buf->data_size) goto fail;
|
||||
|
||||
close(fd);
|
||||
lf->dirty = false;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
|
@ -61,6 +74,11 @@ fail:
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool lf_save(void *_cookie, char const *destination)
|
||||
{
|
||||
return gint_world_switch(GINT_CALL(lf_save_switch, _cookie, destination));
|
||||
}
|
||||
|
||||
static void lf_close(void *_cookie)
|
||||
{
|
||||
loaded_file_t *lf = _cookie;
|
||||
|
@ -72,6 +90,7 @@ static source_intf_t lf_intf = {
|
|||
.read = lf_read,
|
||||
.write = lf_write,
|
||||
.size = lf_size,
|
||||
.dirty = lf_dirty,
|
||||
.save = lf_save,
|
||||
.close = lf_close,
|
||||
};
|
||||
|
|
|
@ -16,6 +16,9 @@ typedef struct {
|
|||
size_t original_size;
|
||||
/* The entire file, as a RAM buffer */
|
||||
buffer_t *buf;
|
||||
/* Whether the source is dirty (ie. we changed the buffer through a call
|
||||
to write()) */
|
||||
bool dirty;
|
||||
|
||||
} loaded_file_t;
|
||||
|
||||
|
|
17
src/source.c
17
src/source.c
|
@ -25,7 +25,17 @@ fail:
|
|||
|
||||
size_t source_size(source_t *s)
|
||||
{
|
||||
return s && s->intf && s->intf->size ? s->intf->size(s->cookie) : 0;
|
||||
if(!s || !s->intf->size)
|
||||
return 0;
|
||||
|
||||
int base_size = s->intf->size(s->cookie);
|
||||
/* Add the size difference from the front buffer */
|
||||
return max(0, base_size - s->buf_segment_size + s->buf->data_size);
|
||||
}
|
||||
|
||||
bool source_dirty(source_t *s)
|
||||
{
|
||||
return s && s->intf->dirty && s->intf->dirty(s->cookie);
|
||||
}
|
||||
|
||||
ssize_t source_load(source_t *s, off_t offset, size_t segment_size)
|
||||
|
@ -79,6 +89,11 @@ bool source_save_front_buffer(source_t *s)
|
|||
s->buf_offset, s->buf_segment_size);
|
||||
}
|
||||
|
||||
bool source_save(source_t *s, char const *destination)
|
||||
{
|
||||
return s && s->intf->save && s->intf->save(s->cookie, destination);
|
||||
}
|
||||
|
||||
void source_free(source_t *s)
|
||||
{
|
||||
if(!s)
|
||||
|
|
19
src/source.h
19
src/source.h
|
@ -27,17 +27,20 @@ typedef struct {
|
|||
/* Load a chunk from the data source into a buffer. */
|
||||
ssize_t (*read)(void *_cookie, void *buf, off_t offset, size_t size);
|
||||
/* Write from a buffer into a segment of the data source. If the interface
|
||||
has the SOURCE_EXPAND capabilitiy, the buffer might be larger than the
|
||||
segment. Similarly, if the interfadce has the SOURCE_SHRINK capability,
|
||||
has the SOURCE_EXPAND capability, the buffer might be larger than the
|
||||
segment. Similarly, if the interface has the SOURCE_SHRINK capability,
|
||||
the buffer might be smaller than the segment. */
|
||||
bool (*write)(void *_cookie, void *buf, size_t buf_size, off_t offset,
|
||||
size_t segment_size);
|
||||
/* Current source data size. */
|
||||
size_t (*size)(void *_cookie);
|
||||
/* Whether the source is currently dirty (ie. requires a save). */
|
||||
bool (*dirty)(void *_cookie);
|
||||
/* Save the data to its original medium. */
|
||||
bool (*save)(void *_cookie);
|
||||
bool (*save)(void *_cookie, char const *destination);
|
||||
/* Close the data source. */
|
||||
void (*close)(void *_cookie);
|
||||
|
||||
} source_intf_t;
|
||||
|
||||
// An open data source //
|
||||
|
@ -54,8 +57,7 @@ typedef struct {
|
|||
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
|
||||
|
@ -66,6 +68,9 @@ source_t *source_open(source_intf_t *intf, void *cookie, char const *origin);
|
|||
expanded */
|
||||
size_t source_size(source_t *s);
|
||||
|
||||
/* Whether the source has been modified and needs a save. */
|
||||
bool source_dirty(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);
|
||||
|
||||
|
@ -81,6 +86,10 @@ 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);
|
||||
|
||||
/* Save the source. The destination is NULL for the current origin, or a
|
||||
source-type-specific string to save as. */
|
||||
bool source_save(source_t *s, char const *destination);
|
||||
|
||||
/* Free a source and its data. */
|
||||
void source_free(source_t *s);
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
#include "util.h"
|
||||
#include <gint/display.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void please_wait(void)
|
||||
{
|
||||
int w, h;
|
||||
char const *message = "Please wait...";
|
||||
dsize(message, NULL, &w, &h);
|
||||
|
||||
int x = DWIDTH / 2;
|
||||
int y = DHEIGHT / 2;
|
||||
|
||||
int mx = 14, bx = 8;
|
||||
int my = 12, by = 6;
|
||||
|
||||
drect(x - w/2 - mx, y - h/2 - my, x + w/2 + mx, y + h/2 + my, C_WHITE);
|
||||
drect_border(x - w/2 - bx, y - h/2 - by, x + w/2 + bx, y + h/2 + by,
|
||||
C_WHITE, 1, C_BLACK);
|
||||
dtext_opt(x, y, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_MIDDLE, message, -1);
|
||||
dupdate();
|
||||
}
|
||||
|
||||
char const *human_size(int size, char *str)
|
||||
{
|
||||
static char default_buffer[64];
|
||||
if(str == NULL)
|
||||
str = default_buffer;
|
||||
|
||||
char digits[16];
|
||||
sprintf(digits, "%d", size);
|
||||
|
||||
if(size < 1000) /* 1 kB */
|
||||
sprintf(str, "%d B", size);
|
||||
else if(size < 10000) /* 10 kB */
|
||||
sprintf(str, "%d.%d kB", size / 1000, (size / 100) % 10);
|
||||
else if(size < 1000000) /* 1 MB */
|
||||
sprintf(str, "%d kB", size / 1000);
|
||||
else
|
||||
sprintf(str, "%d.%d MB", size / 1000000, (size / 100000) % 10);
|
||||
|
||||
return str;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// util: Global utility functions
|
||||
|
||||
#pragma once
|
||||
|
||||
/* Show the "please wait" popup message over the current display. This gets
|
||||
overridden at the next application frame. Does draw to VRAM. */
|
||||
void please_wait(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);
|
Loading…
Reference in New Issue