Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_gui_hexview.c

N_GUI_HEXVIEW data round-trip and byte-editing regression test (headless).

N_GUI_HEXVIEW data round-trip and byte-editing regression test (headless).

Author
Castagnier Mickael
Version
1.0
/*
* Nilorea Library
* Copyright (C) 2005-2026 Castagnier Mickael
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define ALLEGRO_UNSTABLE 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "nilorea/n_log.h"
#include "nilorea/n_gui.h"
static int log_level = LOG_ERR;
static int failures = 0;
static int process_args(int argc, char** argv) {
int opt;
while ((opt = getopt(argc, argv, "V:")) != -1) {
switch (opt) {
case 'V':
if (!strcmp("LOG_NULL", optarg))
else if (!strcmp("LOG_NOTICE", optarg))
else if (!strcmp("LOG_INFO", optarg))
else if (!strcmp("LOG_ERR", optarg))
else if (!strcmp("LOG_DEBUG", optarg))
else
return -1;
break;
default:
return -1;
}
}
return 0;
}
static void expect_true(const char* label, int cond) {
if (!cond) {
n_log(LOG_ERR, "%s: condition false", label);
}
}
/* Feed one key to the GUI as if the user had pressed it. The editable hex view is
the only widget here that edits arbitrary bytes, so its whole model (nibble
typing, insert, delete, cursor movement) is driven through the real event path
rather than by poking the struct: that is what keeps the dispatch wiring covered
too. */
static void send_key(N_GUI_CTX* gui, int keycode, int unichar, int modifiers) {
ALLEGRO_EVENT ev;
memset(&ev, 0, sizeof(ev));
ev.type = ALLEGRO_EVENT_KEY_CHAR;
ev.keyboard.keycode = keycode;
ev.keyboard.unichar = unichar;
ev.keyboard.modifiers = (unsigned int)modifiers;
}
/* Compare the widget's bytes against an expected buffer. */
static int hex_equals(N_GUI_CTX* gui, int hv, const unsigned char* want, size_t want_len) {
size_t len = 0;
const unsigned char* got = n_gui_hexview_get_data(gui, hv, &len);
if (len != want_len) return 0;
if (want_len == 0) return 1;
return got && memcmp(got, want, want_len) == 0;
}
int main(int argc, char** argv) {
if (process_args(argc, argv) != 0) {
fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
return 2;
}
if (!al_init() || !al_init_font_addon()) {
n_log(LOG_ERR, "allegro init failed");
return 1;
}
ALLEGRO_FONT* font = al_create_builtin_font();
if (!font) {
n_log(LOG_ERR, "al_create_builtin_font failed");
return 1;
}
if (!gui) {
n_log(LOG_ERR, "n_gui_new_ctx failed");
al_destroy_font(font);
return 1;
}
n_gui_set_display_size(gui, 800.0f, 600.0f);
int win = n_gui_add_window(gui, "Panel", 0.0f, 0.0f, 800.0f, 600.0f);
expect_true("window created", win >= 0);
int hv = n_gui_add_hexview(gui, win, 0.0f, 0.0f, 600.0f, 400.0f);
expect_true("hexview created", hv >= 0);
expect_true("empty length is zero", n_gui_hexview_get_length(gui, hv) == 0);
/* binary-safe payload including embedded NUL and high bytes */
unsigned char payload[260];
size_t i;
for (i = 0; i < sizeof(payload); i++)
payload[i] = (unsigned char)(i & 0xFF);
payload[5] = 0x00;
n_gui_hexview_set_data(gui, hv, payload, sizeof(payload));
expect_true("length matches payload", n_gui_hexview_get_length(gui, hv) == sizeof(payload));
/* replacing with NULL clears the buffer */
n_gui_hexview_set_data(gui, hv, NULL, 0);
expect_true("cleared length is zero", n_gui_hexview_get_length(gui, hv) == 0);
/* ---- editing ---- */
{
static const unsigned char start[4] = {0xDE, 0xAD, 0xBE, 0xEF};
unsigned char want[8];
size_t glen = 0;
n_gui_hexview_set_data(gui, hv, start, sizeof(start));
expect_true("data borrows back", hex_equals(gui, hv, start, sizeof(start)));
expect_true("cursor starts at zero", n_gui_hexview_get_cursor(gui, hv) == 0);
/* a read-only view ignores keys: editing is opt-in, so an existing viewer
cannot start mutating its data because the user clicked in it */
send_key(gui, ALLEGRO_KEY_1, '1', 0);
expect_true("read-only view is unchanged", hex_equals(gui, hv, start, sizeof(start)));
/* typing two hex digits fills one byte and advances to the next */
send_key(gui, ALLEGRO_KEY_C, 'c', 0);
send_key(gui, ALLEGRO_KEY_A, 'a', 0);
memcpy(want, start, sizeof(start));
want[0] = 0xCA;
expect_true("two digits write one byte", hex_equals(gui, hv, want, 4));
expect_true("cursor advanced one byte", n_gui_hexview_get_cursor(gui, hv) == 1);
/* a single digit writes the high nibble and leaves the low one alone */
send_key(gui, ALLEGRO_KEY_F, 'f', 0);
want[1] = 0xFD; /* was 0xAD */
expect_true("one digit writes the high nibble", hex_equals(gui, hv, want, 4));
/* Insert adds a zero byte before the cursor, Delete removes one */
send_key(gui, ALLEGRO_KEY_INSERT, 0, 0);
expect_true("insert grew the buffer", n_gui_hexview_get_length(gui, hv) == 5);
{
const unsigned char* got = n_gui_hexview_get_data(gui, hv, &glen);
expect_true("inserted byte is zero", got && got[0] == 0x00);
}
send_key(gui, ALLEGRO_KEY_DELETE, 0, 0);
expect_true("delete restored the buffer", hex_equals(gui, hv, want, 4));
/* Backspace removes the byte before the cursor and moves back onto it */
send_key(gui, ALLEGRO_KEY_BACKSPACE, 0, 0);
expect_true("backspace removed the last byte", n_gui_hexview_get_length(gui, hv) == 3);
expect_true("backspace moved the cursor back", n_gui_hexview_get_cursor(gui, hv) == 3);
/* typing past the end appends, so a buffer can be built from nothing */
n_gui_hexview_set_data(gui, hv, NULL, 0);
send_key(gui, ALLEGRO_KEY_0, '0', 0);
send_key(gui, ALLEGRO_KEY_D, 'd', 0);
send_key(gui, ALLEGRO_KEY_0, '0', 0);
send_key(gui, ALLEGRO_KEY_A, 'a', 0);
{
static const unsigned char crlf[2] = {0x0D, 0x0A};
expect_true("typing from empty appends bytes", hex_equals(gui, hv, crlf, 2));
}
/* every byte value survives, including NUL and the high half: that is the
whole reason this widget exists rather than a textarea */
{
unsigned char all[256];
size_t i2;
for (i2 = 0; i2 < sizeof(all); i2++)
all[i2] = (unsigned char)i2;
n_gui_hexview_set_data(gui, hv, all, sizeof(all));
expect_true("all 256 byte values round-trip", hex_equals(gui, hv, all, sizeof(all)));
}
/* guards */
expect_true("get_data on a bad id is NULL", n_gui_hexview_get_data(gui, -1, &glen) == NULL && glen == 0);
expect_true("cursor clamps to the length", n_gui_hexview_get_cursor(gui, hv) == n_gui_hexview_get_length(gui, hv));
}
al_destroy_font(font);
if (failures) {
n_log(LOG_ERR, "ex_gui_hexview: %d failure(s)", failures);
return 1;
}
n_log(LOG_NOTICE, "ex_gui_hexview: all checks passed");
return 0;
}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static int log_level
static N_GUI_CTX * gui
static int hex_equals(N_GUI_CTX *gui, int hv, const unsigned char *want, size_t want_len)
static void send_key(N_GUI_CTX *gui, int keycode, int unichar, int modifiers)
FONT * font
the default 8x8 font, valid after allegro_init()
void n_gui_set_display_size(N_GUI_CTX *ctx, float w, float h)
set the display (viewport) size so global scrollbars can be computed.
Definition n_gui.c:12348
int n_gui_add_hexview(N_GUI_CTX *ctx, int window_id, float x, float y, float w, float h)
Add a read-only hexadecimal byte viewer.
Definition n_gui.c:3372
void n_gui_hexview_set_data(N_GUI_CTX *ctx, int widget_id, const unsigned char *data, size_t len)
set (copy) the bytes shown by a hex viewer
Definition n_gui.c:4805
void n_gui_hexview_set_cursor(N_GUI_CTX *ctx, int widget_id, size_t offset)
move the edit cursor to a byte offset
Definition n_gui.c:4872
const unsigned char * n_gui_hexview_get_data(N_GUI_CTX *ctx, int widget_id, size_t *len)
borrow the bytes a hex view holds
Definition n_gui.c:4858
int n_gui_process_event(N_GUI_CTX *ctx, ALLEGRO_EVENT event)
Process an allegro event through the GUI system.
Definition n_gui.c:13611
void n_gui_set_focus(N_GUI_CTX *ctx, int widget_id)
Set keyboard focus to a specific widget.
Definition n_gui.c:3908
size_t n_gui_hexview_get_length(N_GUI_CTX *ctx, int widget_id)
get the number of bytes currently held by a hex viewer
Definition n_gui.c:4899
void n_gui_hexview_set_editable(N_GUI_CTX *ctx, int widget_id, int editable)
make a hex view editable, or read-only again
Definition n_gui.c:4831
int n_gui_add_window(N_GUI_CTX *ctx, const char *title, float x, float y, float w, float h)
Add a new pseudo-window to the context.
Definition n_gui.c:1277
N_GUI_CTX * n_gui_new_ctx(ALLEGRO_FONT *default_font)
Create a new GUI context.
Definition n_gui.c:1103
void n_gui_destroy_ctx(N_GUI_CTX **ctx)
Destroy a GUI context and all its windows/widgets.
Definition n_gui.c:1209
size_t n_gui_hexview_get_cursor(N_GUI_CTX *ctx, int widget_id)
the byte offset the edit cursor sits on
Definition n_gui.c:4887
The top-level GUI context that holds all windows.
Definition n_gui.h:1669
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
Common headers and low-level functions & define.
GUI system: buttons, sliders, text areas, checkboxes, scrollbars, dropdown menus, windows.
Generic log system.