hex-editor/src/heditor.c

296 lines
7.3 KiB
C

#include "heditor.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();
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 + bpl - 1) / bpl;
if(max_scroll)
*max_scroll = max(rows - e->visible_lines, 0) * bpl;
if(max_cursor)
*max_cursor = size - 1;
}
/* Adjust positioning to make sure we stay on legal values */
static void shake(heditor *e)
{
heditor_move_to(e, e->cursor);
}
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->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_ensure_loaded(e->source, e->scroll, view_size))
changed = true;
if(changed) {
e->widget.update = true;
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
}
return changed;
}
void heditor_set_insert_mode(heditor *e, bool insert)
{
e->insert = false;
if(e->source && e->source->intf->cap & SOURCE_EXPAND)
e->insert = insert;
e->widget.update = true;
}
void heditor_set_source(heditor *e, source_t *source)
{
e->source = source;
/* Force a change event */
if(!heditor_move_to(e, 0)) {
e->widget.update = true;
jwidget_emit(e, (jevent){ .type = HEDITOR_CHANGED });
}
}
// 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;
// TODO: heditor: Subtler content size estimation
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);
sprintf(byte, "%02X", c);
dprint(ascii_x, line_y, C_BLACK, "%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) {
drect(bytes_x - 2, line_y - 1, bytes_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 += 9;
}
offset += n;
}
}
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)
return false;
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) - 1);
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;
}
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();
}