426 lines
11 KiB
C
426 lines
11 KiB
C
#include "heditor.h"
|
|
#include "util.h"
|
|
|
|
#include <justui/jwidget.h>
|
|
#include <justui/jwidget-api.h>
|
|
#include <justui/jscene.h>
|
|
|
|
#include <gint/display.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Type identifier for heditor */
|
|
static int heditor_type_id = -1;
|
|
|
|
/* Events */
|
|
uint16_t HEDITOR_CHANGED;
|
|
|
|
heditor *heditor_create(jwidget *parent)
|
|
{
|
|
if(heditor_type_id < 0) return NULL;
|
|
|
|
heditor *e = malloc(sizeof *e);
|
|
if(!e) return NULL;
|
|
|
|
jwidget_init(&e->widget, heditor_type_id, parent);
|
|
|
|
e->source = NULL;
|
|
e->scroll = 0;
|
|
e->cursor = 0;
|
|
e->insert = false;
|
|
e->visible_lines = 0;
|
|
e->bytes_per_line = 8;
|
|
e->line_spacing = 3;
|
|
e->font = dfont_default();
|
|
e->half_byte = false;
|
|
e->front_buffer_dirty = false;
|
|
|
|
return e;
|
|
}
|
|
|
|
static void compute_maxima(heditor *e, int *max_scroll, int *max_cursor)
|
|
{
|
|
if(!e || !e->source)
|
|
return;
|
|
|
|
int bpl = e->bytes_per_line;
|
|
int size = source_size(e->source);
|
|
int rows = (size + (e->insert == true) + bpl - 1) / bpl;
|
|
|
|
if(max_scroll)
|
|
*max_scroll = max(rows - e->visible_lines, 0) * bpl;
|
|
if(max_cursor)
|
|
*max_cursor = size - (e->insert == false);
|
|
}
|
|
|
|
/* Adjust positioning to make sure we stay on legal values */
|
|
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;
|
|
int max_scroll=0, max_cursor=0;
|
|
compute_maxima(e, &max_scroll, &max_cursor);
|
|
|
|
/* Recompute the number of visible lines */
|
|
|
|
int ch = jwidget_content_height(e);
|
|
int line_height = e->font->line_height + e->line_spacing;
|
|
int visible_lines = (ch + e->line_spacing) / line_height;
|
|
|
|
if(e->visible_lines != visible_lines)
|
|
changed = true;
|
|
e->visible_lines = visible_lines;
|
|
|
|
/* Clamp the cursor to allowed limits */
|
|
|
|
target_cursor = max(0, min(target_cursor, max_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
|
|
the center of the current view (ie. everything but the first and last
|
|
lines), do nothing. Otherwise, scroll to make it the first/last line. */
|
|
int bpl = e->bytes_per_line;
|
|
int target_row = target_cursor - (target_cursor % bpl);
|
|
|
|
int target_scroll =
|
|
// Minimum scroll where target is in the center
|
|
max(target_row - (visible_lines - 2) * bpl,
|
|
// Default value
|
|
min(e->scroll,
|
|
// Maximum scroll where target is in the center
|
|
target_row - bpl));
|
|
|
|
/* Clamp the newly-decided scroll to allowed limits */
|
|
|
|
int new_scroll = target_scroll - (target_scroll % bpl);
|
|
new_scroll = max(0, min(new_scroll, max_scroll));
|
|
|
|
if(new_scroll != e->scroll)
|
|
changed = true;
|
|
e->scroll = new_scroll;
|
|
|
|
/* Keep the source's front buffer aligned with the view */
|
|
|
|
int view_size = e->visible_lines * bpl;
|
|
view_size = min(view_size, max(source_size(e->source) - e->scroll, 0));
|
|
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;
|
|
emit_changed(e);
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
bool heditor_save_front_buffer(heditor *e)
|
|
{
|
|
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;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool heditor_save(heditor *e, char const *outfile)
|
|
{
|
|
if(!e)
|
|
return true;
|
|
if(e->front_buffer_dirty && !heditor_save_front_buffer(e))
|
|
return false;
|
|
if(!outfile && !source_dirty(e->source))
|
|
return true;
|
|
|
|
please_wait();
|
|
return source_save(e->source, outfile);
|
|
}
|
|
|
|
void heditor_set_insert_mode(heditor *e, bool insert)
|
|
{
|
|
e->insert = false;
|
|
if(e->source && e->source->cap & SOURCE_RESIZE)
|
|
e->insert = insert;
|
|
shake(e);
|
|
e->widget.update = true;
|
|
}
|
|
|
|
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;
|
|
emit_changed(e);
|
|
}
|
|
}
|
|
|
|
// Trivial properties //
|
|
|
|
void heditor_set_font(heditor *e, font_t const *font)
|
|
{
|
|
e->font = font ? font : dfont_default();
|
|
e->widget.update = true;
|
|
shake(e);
|
|
}
|
|
|
|
void heditor_set_line_spacing(heditor *e, int line_spacing)
|
|
{
|
|
e->line_spacing = line_spacing;
|
|
e->widget.update = true;
|
|
shake(e);
|
|
}
|
|
|
|
void heditor_set_bytes_per_lines(heditor *e, int bytes_per_line)
|
|
{
|
|
e->bytes_per_line = bytes_per_line;
|
|
e->widget.update = true;
|
|
shake(e);
|
|
}
|
|
|
|
// Polymorphic widget operations //
|
|
|
|
static void heditor_poly_csize(void *e0)
|
|
{
|
|
heditor *e = e0;
|
|
jwidget *w = &e->widget;
|
|
|
|
w->w = 128;
|
|
w->h = 64;
|
|
}
|
|
|
|
static void heditor_poly_layout(void *e0)
|
|
{
|
|
heditor *e = e0;
|
|
shake(e);
|
|
}
|
|
|
|
static void heditor_poly_render(void *e0, int x, int y)
|
|
{
|
|
heditor *e = e0;
|
|
source_t *s = e->source;
|
|
char byte[16];
|
|
|
|
if(!s) {
|
|
dtext(x, y, C_BLACK, "No source opened");
|
|
return;
|
|
}
|
|
|
|
int offset = e->scroll;
|
|
int line_height = e->font->line_height + e->line_spacing;
|
|
int front_buffer_max = s->buf_offset + s->buf->data_size;
|
|
int n = e->bytes_per_line;
|
|
|
|
for(int i = 0; i < e->visible_lines; i++) {
|
|
int line_y = y + line_height * i;
|
|
dprint(x, line_y, C_BLACK, "%08X:", offset);
|
|
|
|
int bytes_x = x + 85;
|
|
int ascii_x = x + 250;
|
|
|
|
for(int k = 0; k < n; k++) {
|
|
off_t o = offset + k;
|
|
strcpy(byte, "--");
|
|
|
|
if(o >= s->buf_offset && o < front_buffer_max) {
|
|
int c = *(uint8_t *)(s->buf->mem + o - s->buf_offset);
|
|
|
|
if(e->cursor == o && e->half_byte)
|
|
sprintf(byte, "%01X_", c >> 4);
|
|
else
|
|
sprintf(byte, "%02X", c);
|
|
|
|
int fg = C_BLACK;
|
|
if(e->cursor == o) {
|
|
drect(ascii_x - 1, line_y - 1, ascii_x + 8,
|
|
line_y + e->font->line_height, C_BLACK);
|
|
fg = C_WHITE;
|
|
}
|
|
dprint(ascii_x, line_y, fg, "%c",
|
|
(c >= 0x20 && c < 0x7f) ? c : '.');
|
|
}
|
|
|
|
int text_w;
|
|
dsize(byte, NULL, &text_w, NULL);
|
|
|
|
int fg = C_BLACK;
|
|
if(e->cursor == o) {
|
|
if(e->insert) {
|
|
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 {
|
|
drect(bytes_x - 1, line_y - 1, bytes_x + text_w,
|
|
line_y + e->font->line_height, C_BLACK);
|
|
fg = C_WHITE;
|
|
}
|
|
}
|
|
dtext(bytes_x, line_y, fg, byte);
|
|
|
|
bytes_x += text_w + ((k & 1) ? 7 : 2);
|
|
ascii_x += 10;
|
|
}
|
|
|
|
offset += n;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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);
|
|
|
|
if(key == KEY_UP && ev.key.shift) {
|
|
heditor_move_to(e, 0);
|
|
return true;
|
|
}
|
|
else if(key == KEY_UP) {
|
|
heditor_move_to(e, e->cursor - move_speed * e->bytes_per_line);
|
|
return true;
|
|
}
|
|
|
|
if(key == KEY_DOWN && ev.key.shift) {
|
|
heditor_move_to(e, source_size(e->source) /* auto adjust */);
|
|
return true;
|
|
}
|
|
else if(key == KEY_DOWN) {
|
|
heditor_move_to(e, e->cursor + move_speed * e->bytes_per_line);
|
|
return true;
|
|
}
|
|
|
|
if(key == KEY_LEFT) {
|
|
heditor_move_to(e, e->cursor - 1);
|
|
return true;
|
|
}
|
|
if(key == KEY_RIGHT) {
|
|
heditor_move_to(e, e->cursor + 1);
|
|
return true;
|
|
}
|
|
|
|
// Edition //
|
|
|
|
if(!(e->source->cap & SOURCE_WRITE))
|
|
return false;
|
|
if(e->insert && !(e->source->cap & SOURCE_RESIZE))
|
|
return false;
|
|
|
|
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;
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* heditor type definiton */
|
|
static jwidget_poly type_heditor = {
|
|
.name = "heditor",
|
|
.csize = heditor_poly_csize,
|
|
.layout = heditor_poly_layout,
|
|
.render = heditor_poly_render,
|
|
.event = heditor_poly_event,
|
|
};
|
|
|
|
__attribute__((constructor(1004)))
|
|
static void j_register_heditor(void)
|
|
{
|
|
heditor_type_id = j_register_widget(&type_heditor, "jwidget");
|
|
HEDITOR_CHANGED = j_register_event();
|
|
}
|