Add formatted text to textarea

Goal

To be able to add formatted text to a textarea object. Basically a hybrid of lv_label_set_text_fmt and lv_textarea_add_text.

More in-depth description

For a label you can use both lv_label_set_text and lv_label_set_text_fmt for added generic text and formatted text to a label. Currently for a textarea some of the options you have are lv_textarea_set_text, lv_textarea_add_char, and lv_textarea_add_text.

I would like to see the feature added for something like lv_textarea_add_text_fmt. There comes a few times where I want to add formatted text to a textarea and end up having to create a temp string and use either lv_snprintf or snprintf, then lv_textarea_add_text with that now formatted temp string.

I’ve given a go at trying to implement this myself, although I’m not sure if I’ve done it correctly (as in if there’s memory leaks, efficient, etc.), but it appears to work the way I intended it.

First Attempt at the code

I began by just examining lv_textarea_add_text and lv_label_set_text_fmt and trying to merge them.

In lv_textarea.h

/**
 * Insert a formatted text to the current cursor position
 * @param obj           pointer to a text area object
 * @param fmt           `printf`-like format
 * @example lv_textarea_add_text_fmt(ta, "%d user", user_num);
 */
void lv_textarea_add_text_fmt(lv_obj_t * obj, const char * fmt, ...);

In lv_textarea.c

void lv_textarea_add_text_fmt(lv_obj_t * obj, const char * fmt, ...)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(fmt);

    if (fmt == NULL) return;

    lv_textarea_t* ta = (lv_textarea_t*)obj;
    const char* temp;

    va_list args;
    va_start(args, fmt);
    temp = _lv_txt_set_text_vfmt(fmt, args);
    va_end(args);

    lv_label_ins_text(ta->label, ta->cursor.pos, temp);
    lv_textarea_clear_selection(obj);

    lv_textarea_set_cursor_pos(obj, lv_textarea_get_cursor_pos(obj) + _lv_txt_get_encoded_length(temp));

    lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
}

Any thoughts on the matter would be appreciated.

In v9 (already under development) I was thinking about making all lv_..._set_text function fmt. This way e.g.
lv_label_set_text(label, "hello")
and
lv_label_set_text(label, "hello %d", 10)
worked with the same function.

What do you think about it? @embeddedt ?

BTW, in your code don’t forget to free temp as _lv_txt_set_text_vfmt mallocs it.

Wouldn’t that mean a printf implementation is unconditionally being included? Right now we only need it for logging and examples, as far as I know.

Are you worried about the extra flash size?

EDIT:
With -O3 on Stm32F7 _vsnprintf uses 2324 bytes.

I’m nut sure it’s the best idea but if flash is a concern we can add a dummy snprintf implementation too which just uses only the format string and drops the arguments. The users could select/set these in lv_conf.h

Yes. That’s not insignificant for a platform with only 128KB flash, but maybe it’s worth it to simplify the API surface.

True, in fact we could just add a note about this somewhere in the docs as the printf implementation appears to already be configurable.

1 Like

Yes, I did catch this after turning back on the LVGL memory monitor overlay.
What I settled on was a stripped down version of _lv_txt_set_text_vfmt and lv_textarea_add_text. It ended up looking like this.

void lv_textarea_add_text_fmt(lv_obj_t * obj, const char * fmt, ...)
{
    LV_ASSERT_OBJ(obj, MY_CLASS);
    LV_ASSERT_NULL(fmt);

    if (fmt == NULL) return;

    lv_textarea_t* ta = (lv_textarea_t*)obj;
    va_list args;

    va_start(args, fmt);
    uint16_t str_len = lv_vsnprintf(NULL, 0, fmt, args) + 1; // Add one because my \n was getting cut off
    char* buf = lv_mem_alloc(str_len + 1); // Add one for null terminating character
    LV_ASSERT_MALLOC(buf);
    if (!buf) return;
    buf[str_len] = 0; // Ensure null terminator
    lv_vsnprintf(buf, str_len, fmt, args);
    va_end(args);

    lv_label_ins_text(ta->label, ta->cursor.pos, buf);
    lv_textarea_clear_selection(obj);

    lv_textarea_set_cursor_pos(obj, lv_textarea_get_cursor_pos(obj) + _lv_txt_get_encoded_length(buf));
    lv_mem_free(buf);

    lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL);
}

I wanted this functionality because I’m using a textarea for logging. So as my system runs, it needs to report readings and values used in calculations. In order to verify correctness, and troubleshoot errors.

I like this idea, sort of how you can use printf for both regular and formatted text, but @embeddedt raised a good point about memory constrained systems.

1 Like