6 ex__gui__hexview_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

examples/ex_gui_hexview.c

Functions

Name
void send_key(N_GUI_CTX * gui, int keycode, int unichar, int modifiers)
int process_args(int argc, char ** argv)
int main(int argc, char ** argv)
int hex_equals(N_GUI_CTX * gui, int hv, const unsigned char * want, size_t want_len)
void expect_true(const char * label, int cond)

Attributes

Name
int log_level
int failures

Defines

Name
ALLEGRO_UNSTABLE

Functions Documentation

function send_key

static void send_key(
    N_GUI_CTX * gui,
    int keycode,
    int unichar,
    int modifiers
)

function process_args

static int process_args(
    int argc,
    char ** argv
)

function main

int main(
    int argc,
    char ** argv
)

function hex_equals

static int hex_equals(
    N_GUI_CTX * gui,
    int hv,
    const unsigned char * want,
    size_t want_len
)

function expect_true

static void expect_true(
    const char * label,
    int cond
)

Attributes Documentation

variable log_level

static int log_level = 3;

variable failures

static int failures = 0;

Macros Documentation

define ALLEGRO_UNSTABLE

#define ALLEGRO_UNSTABLE 1

Source code

/*
 * 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_common.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))
                    log_level = LOG_NULL;
                else if (!strcmp("LOG_NOTICE", optarg))
                    log_level = LOG_NOTICE;
                else if (!strcmp("LOG_INFO", optarg))
                    log_level = LOG_INFO;
                else if (!strcmp("LOG_ERR", optarg))
                    log_level = LOG_ERR;
                else if (!strcmp("LOG_DEBUG", optarg))
                    log_level = LOG_DEBUG;
                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);
        failures++;
    }
}

/* 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;
    n_gui_process_event(gui, ev);
}

/* 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) {
    set_log_level(LOG_ERR);
    if (process_args(argc, argv) != 0) {
        fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
        return 2;
    }
    set_log_level(log_level);

    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;
    }
    N_GUI_CTX* gui = n_gui_new_ctx(font);
    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 */
        n_gui_set_focus(gui, hv);
        send_key(gui, ALLEGRO_KEY_1, '1', 0);
        expect_true("read-only view is unchanged", hex_equals(gui, hv, start, sizeof(start)));

        n_gui_hexview_set_editable(gui, hv, 1);
        n_gui_set_focus(gui, hv);

        /* 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 */
        n_gui_hexview_set_cursor(gui, hv, 0);
        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 */
        n_gui_hexview_set_cursor(gui, hv, 4);
        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);
        n_gui_hexview_set_editable(gui, hv, 1);
        n_gui_set_focus(gui, hv);
        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);
        n_gui_hexview_set_cursor(gui, hv, 99999);
        expect_true("cursor clamps to the length", n_gui_hexview_get_cursor(gui, hv) == n_gui_hexview_get_length(gui, hv));
    }

    n_gui_destroy_ctx(&gui);
    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;
}