From e38ad0f7ca46b9eed4d4448ed4c4cbaa11d43573 Mon Sep 17 00:00:00 2001 From: antirez Date: Sun, 22 Jan 2023 11:11:45 +0100 Subject: [PATCH] Message builder: User input progresses. --- app.c | 1 + app.h | 3 ++ fields.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++ view_build.c | 76 ++++++++++++++++++++++--------- view_info.c | 66 ++++++++------------------- 5 files changed, 201 insertions(+), 69 deletions(-) diff --git a/app.c b/app.c index f8eba44b35e..6090f3822b9 100644 --- a/app.c +++ b/app.c @@ -99,6 +99,7 @@ static void app_switch_view(ProtoViewApp *app, ProtoViewCurrentView switchto) { if (old == ViewDirectSampling) view_exit_direct_sampling(app); if (new == ViewDirectSampling) view_enter_direct_sampling(app); if (old == ViewBuildMessage) view_exit_build_message(app); + if (old == ViewInfo) view_exit_info(app); /* The frequency/modulation settings are actually a single view: * as long as the user stays between the two modes of this view we * don't need to call the exit-view callback. */ diff --git a/app.h b/app.h index d3340b04dea..a5262705bcf 100644 --- a/app.h +++ b/app.h @@ -282,6 +282,7 @@ void view_exit_build_message(ProtoViewApp *app); void view_enter_direct_sampling(ProtoViewApp *app); void view_exit_direct_sampling(ProtoViewApp *app); void view_exit_settings(ProtoViewApp *app); +void view_exit_info(ProtoViewApp *app); void adjust_raw_view_scale(ProtoViewApp *app, uint32_t short_pulse_dur); /* ui.c */ @@ -307,6 +308,8 @@ void fieldset_add_str(ProtoViewFieldSet *fs, const char *name, const char *s); void fieldset_add_bytes(ProtoViewFieldSet *fs, const char *name, const uint8_t *bytes, uint32_t count); void fieldset_add_float(ProtoViewFieldSet *fs, const char *name, float val, uint32_t digits_after_dot); const char *field_get_type_name(ProtoViewField *f); +int field_to_string(char *buf, size_t len, ProtoViewField *f); +bool field_set_from_string(ProtoViewField *f, char *buf, size_t len); /* crc.c */ uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly); diff --git a/fields.c b/fields.c index ac18c50edf5..5bdc62a3d80 100644 --- a/fields.c +++ b/fields.c @@ -39,6 +39,130 @@ const char *field_get_type_name(ProtoViewField *f) { return "unknown"; } +/* Set a string representation of the specified field in buf. */ +int field_to_string(char *buf, size_t len, ProtoViewField *f) { + switch(f->type) { + case FieldTypeStr: + return snprintf(buf,len,"%s", f->str); + case FieldTypeSignedInt: + return snprintf(buf,len,"%lld", (long long) f->value); + case FieldTypeUnsignedInt: + return snprintf(buf,len,"%llu", (unsigned long long) f->uvalue); + case FieldTypeBinary: + { + uint64_t test_bit = (1 << (f->len-1)); + uint64_t idx = 0; + while(idx < len-1 && test_bit) { + buf[idx++] = (f->uvalue & test_bit) ? '1' : '0'; + test_bit >>= 1; + } + buf[idx] = 0; + return idx; + } + case FieldTypeHex: + return snprintf(buf, len, "%*llX", (int)(f->len+7)/8, f->uvalue); + case FieldTypeFloat: + return snprintf(buf, len, "%.*f", (int)f->len, (double)f->fvalue); + case FieldTypeBytes: + { + uint64_t idx = 0; + uint32_t nibble_num = 0; + while(idx < len-1 && nibble_num < f->len) { + const char *charset = "0123456789ABCDEF"; + uint32_t nibble = nibble_num & 1 ? + (f->bytes[nibble_num/2] >> 4) : + (f->bytes[nibble_num/2] & 0xf); + buf[idx++] = charset[nibble]; + nibble_num++; + } + buf[idx] = 0; + return idx; + } + } + return 0; +} + +/* Set the field value from its string representation in 'buf'. + * The field type must already be set and the field should be valid. + * The string represenation 'buf' must be null termianted. Note that + * even when representing binary values containing zero, this values + * are taken as representations, so that would be the string "00" as + * the Bytes type representation. + * + * The function returns true if the filed was successfully set to the + * new value, otherwise if the specified value is invalid for the + * field type, false is returned. */ +bool field_set_from_string(ProtoViewField *f, char *buf, size_t len) { + long long val; + unsigned long long uval; + float fval; + + switch(f->type) { + case FieldTypeStr: + free(f->str); + f->len = len; + f->str = malloc(len+1); + memcpy(f->str,buf,len+1); + break; + case FieldTypeSignedInt: + if (!sscanf(buf,"%lld",&val)) return false; + f->value = val; + break; + case FieldTypeUnsignedInt: + if (!sscanf(buf,"%llu",&uval)) return false; + f->uvalue = uval; + break; + case FieldTypeBinary: + { + uint64_t bit_to_set = (1 << (len-1)); + uint64_t idx = 0; + uval = 0; + while(buf[idx]) { + if (buf[idx] == '1') uval |= bit_to_set; + else if (buf[idx] != '0') return false; + bit_to_set >>= 1; + idx++; + } + } + break; + case FieldTypeHex: + if (!sscanf(buf,"%llx",&uval) && + !sscanf(buf,"%llX",&uval)) return false; + f->uvalue = uval; + break; + case FieldTypeFloat: + if (!sscanf(buf,"%f",&fval)) return false; + f->fvalue = fval; + break; + case FieldTypeBytes: + { + if (len > f->len) return false; + uint64_t idx = 0; + uint64_t nibble_idx = len-1; + while(buf[idx]) { + uint8_t nibble = 0; + char c = toupper(buf[idx]); + if (c >= '0' && c <= '9') nibble = c-'0'; + else if (c >= 'A' && c <= 'F') nibble = c-'A'; + else return false; + + if (nibble_idx & 1) { + f->bytes[idx/2] = + (f->bytes[idx/2] & 0x0F) | (nibble<<4); + } else { + f->bytes[idx/2] = + (f->bytes[idx/2] & 0xF0) | nibble; + } + nibble_idx--; + idx++; + } + buf[idx] = 0; + } + break; + } + return true; +} + /* Free a field set and its contained fields. */ void fieldset_free(ProtoViewFieldSet *fs) { for (uint32_t j = 0; j < fs->numfields; j++) diff --git a/view_build.c b/view_build.c index d79400ad490..eba970d0d7c 100644 --- a/view_build.c +++ b/view_build.c @@ -6,14 +6,18 @@ extern ProtoViewDecoder *Decoders[]; // Defined in signal.c. /* Our view private data. */ +#define USER_VALUE_LEN 64 typedef struct { - ProtoViewDecoder *decoder; // Decoder we are using to create a message. - uint32_t cur_decoder; // Decoder index when we are yet selecting - // a decoder. Used when decoder is NULL. - ProtoViewFieldSet *fieldset; // The fields to populate. - uint32_t cur_field; // Field we are editing right now. This - // is the index inside the 'fieldset' - // fields. + ProtoViewDecoder *decoder; /* Decoder we are using to create a + message. */ + uint32_t cur_decoder; /* Decoder index when we are yet selecting + a decoder. Used when decoder is NULL. */ + ProtoViewFieldSet *fieldset; /* The fields to populate. */ + uint32_t cur_field; /* Field we are editing right now. This + is the index inside the 'fieldset' + fields. */ + char *user_value; /* Keyboard input to replace the current + field value goes here. */ } BuildViewPrivData; /* Not all the decoders support message bulding, so we can't just @@ -44,7 +48,7 @@ static void select_prev_decoder(ProtoViewApp *app) { static void render_view_select_decoder(Canvas *const canvas, ProtoViewApp *app) { BuildViewPrivData *privdata = app->view_privdata; canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 0, 9, "Signal builder"); + canvas_draw_str(canvas, 0, 9, "Signal creator"); canvas_set_font(canvas, FontSecondary); canvas_draw_str(canvas, 0, 19, "up/down: select, ok: choose"); @@ -54,9 +58,7 @@ static void render_view_select_decoder(Canvas *const canvas, ProtoViewApp *app) select_next_decoder(app); canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 0, 9, "Signal builder"); - - canvas_draw_str_aligned(canvas,64,36,AlignCenter,AlignCenter, + canvas_draw_str_aligned(canvas,64,38,AlignCenter,AlignCenter, Decoders[privdata->cur_decoder]->name); } @@ -68,26 +70,32 @@ static void render_view_set_fields(Canvas *const canvas, ProtoViewApp *app) { snprintf(buf,sizeof(buf), "%s field %d/%d", privdata->decoder->name, (int)privdata->cur_field+1, (int)privdata->fieldset->numfields); + canvas_set_color(canvas,ColorBlack); + canvas_draw_box(canvas,0,0,128,21); + canvas_set_color(canvas,ColorWhite); canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 0, 9, buf); + canvas_draw_str(canvas, 1, 9, buf); canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas, 0, 19, "up/down: next field, ok: edit"); - canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr"); + canvas_draw_str(canvas, 1, 19, "up/down: next field, ok: edit"); - /* Write the field name, type, current content. For this part we - * write white text on black screen, to visually separate the UI - * description part from the UI current field editing part. */ + /* Write the field name, type, current content. */ canvas_set_color(canvas,ColorBlack); - canvas_draw_box(canvas,0,21,128,32); - - canvas_set_color(canvas,ColorWhite); ProtoViewField *field = privdata->fieldset->fields[privdata->cur_field]; snprintf(buf,sizeof(buf), "%s %s:%d", field->name, field_get_type_name(field), (int)field->len); + buf[0] = toupper(buf[0]); canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas,64,30,AlignCenter,AlignCenter,buf); canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas,63,45,AlignCenter,AlignCenter,"\"foobar\""); + + /* Render the current value between "" */ + unsigned int written = (unsigned int) field_to_string(buf+1,sizeof(buf)-1,field); + buf[0] = '"'; + if (written+3 < sizeof(buf)) memcpy(buf+written+1,"\"\x00",2); + canvas_draw_str_aligned(canvas,63,45,AlignCenter,AlignCenter,buf); + + /* Footer instructions. */ + canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr"); } /* Render the build message view. */ @@ -122,12 +130,37 @@ static void process_input_select_decoder(ProtoViewApp *app, InputEvent input) { } } +/* Called after the user typed the new field value in the keyboard. + * Let's save it and remove the keyboard view. */ +static void text_input_done_callback(void* context) { + ProtoViewApp *app = context; + BuildViewPrivData *privdata = app->view_privdata; + + if (field_set_from_string(privdata->fieldset->fields[privdata->cur_field], + privdata->user_value, strlen(privdata->user_value)) == false) + { + ui_show_alert(app, "Invalid value", 1500); + } + + free(privdata->user_value); + privdata->user_value = NULL; + ui_dismiss_keyboard(app); +} + /* Handle input for fields editing mode. */ static void process_input_set_fields(ProtoViewApp *app, InputEvent input) { BuildViewPrivData *privdata = app->view_privdata; ProtoViewFieldSet *fs = privdata->fieldset; if (input.type == InputTypeShort) { if (input.key == InputKeyOk) { + /* Show the keyboard to let the user type the new + * value. */ + if (privdata->user_value == NULL) + privdata->user_value = malloc(USER_VALUE_LEN); + field_to_string(privdata->user_value, USER_VALUE_LEN, + fs->fields[privdata->cur_field]); + ui_show_keyboard(app, privdata->user_value, USER_VALUE_LEN, + text_input_done_callback); } else if (input.key == InputKeyDown) { privdata->cur_field = (privdata->cur_field+1) % fs->numfields; } else if (input.key == InputKeyUp) { @@ -152,4 +185,5 @@ void process_input_build_message(ProtoViewApp *app, InputEvent input) { void view_exit_build_message(ProtoViewApp *app) { BuildViewPrivData *privdata = app->view_privdata; if (privdata->fieldset) fieldset_free(privdata->fieldset); + if (privdata->user_value) free(privdata->user_value); } diff --git a/view_info.c b/view_info.c index f368b378a5e..6aa69739c53 100644 --- a/view_info.c +++ b/view_info.c @@ -31,53 +31,11 @@ static void render_info_field(Canvas *const canvas, ProtoViewField *f, uint8_t x, uint8_t y) { char buf[64]; + char strval[32]; + + field_to_string(strval,sizeof(strval),f); + snprintf(buf,sizeof(buf),"%s: %s", f->name, strval); canvas_set_font(canvas, FontSecondary); - switch(f->type) { - case FieldTypeStr: - snprintf(buf,sizeof(buf),"%s: %s", f->name, f->str); - break; - case FieldTypeSignedInt: - snprintf(buf,sizeof(buf),"%s: %lld", f->name, (long long) f->value); - break; - case FieldTypeUnsignedInt: - snprintf(buf,sizeof(buf),"%s: %llu", f->name, - (unsigned long long) f->uvalue); - break; - case FieldTypeBinary: - { - uint64_t test_bit = (1 << (f->len-1)); - uint64_t idx = snprintf(buf,sizeof(buf),"%s: ", f->name); - while(idx < sizeof(buf)-1 && test_bit) { - buf[idx++] = (f->uvalue & test_bit) ? '1' : '0'; - test_bit >>= 1; - } - buf[idx] = 0; - } - break; - case FieldTypeHex: - snprintf(buf, sizeof(buf), "%s: 0x%*llX", f->name, - (int)(f->len+7)/8, f->uvalue); - break; - case FieldTypeFloat: - snprintf(buf, sizeof(buf), "%s: 0x%.*f", f->name, - (int)f->len, (double)f->fvalue); - break; - case FieldTypeBytes: - { - uint64_t idx = snprintf(buf,sizeof(buf),"%s: ", f->name); - uint32_t nibble_num = 0; - while(idx < sizeof(buf)-1 && nibble_num < f->len) { - const char *charset = "0123456789ABCDEF"; - uint32_t nibble = nibble_num & 1 ? - (f->bytes[nibble_num/2] >> 4) : - (f->bytes[nibble_num/2] & 0xf); - buf[idx++] = charset[nibble]; - nibble_num++; - } - buf[idx] = 0; - } - break; - } canvas_draw_str(canvas, x, y, buf); } @@ -176,7 +134,7 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) { /* The user typed the file name. Let's save it and remove the keyboard * view. */ -void text_input_done_callback(void* context) { +static void text_input_done_callback(void* context) { ProtoViewApp *app = context; InfoViewPrivData *privdata = app->view_privdata; @@ -186,6 +144,7 @@ void text_input_done_callback(void* context) { furi_string_free(save_path); free(privdata->filename); + privdata->filename = NULL; // Don't free it again on view exit ui_dismiss_keyboard(app); ui_show_alert(app, "Signal saved", 1500); } @@ -351,7 +310,10 @@ void process_input_info(ProtoViewApp *app, InputEvent input) { privdata->signal_display_start_row--; } else if (input.type == InputTypeLong && input.key == InputKeyOk) { - privdata->filename = malloc(SAVE_FILENAME_LEN); + // We have have the buffer already allocated, in case the + // user aborted with BACK a previous saving. + if (privdata->filename == NULL) + privdata->filename = malloc(SAVE_FILENAME_LEN); set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN); ui_show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, text_input_done_callback); @@ -363,3 +325,11 @@ void process_input_info(ProtoViewApp *app, InputEvent input) { } } } + +/* Called on view exit. */ +void view_exit_info(ProtoViewApp *app) { + InfoViewPrivData *privdata = app->view_privdata; + // When the user aborts the keyboard input, we are left with the + // filename buffer allocated. + if (privdata->filename) free(privdata->filename); +}