add saving and insertion mode

This commit is contained in:
Lephenixnoir 2022-06-23 13:04:52 +01:00
parent 215d2c6512
commit f3578e112d
Signed by untrusted user: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
12 changed files with 226 additions and 26 deletions

View File

@ -15,6 +15,7 @@ set(SOURCES
src/main.c
src/source.c
src/source-loaded-file.c
src/util.c
)
set(ASSETS
)

View File

@ -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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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,
};

View File

@ -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;

View File

@ -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)

View File

@ -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);

43
src/util.c Normal file
View File

@ -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;
}

11
src/util.h Normal file
View File

@ -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);