view_build.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
  2. * See the LICENSE file for information about the license. */
  3. #include "app.h"
  4. extern ProtoViewDecoder* Decoders[]; // Defined in signal.c.
  5. /* Our view private data. */
  6. #define USER_VALUE_LEN 64
  7. typedef struct {
  8. ProtoViewDecoder* decoder; /* Decoder we are using to create a
  9. message. */
  10. uint32_t cur_decoder; /* Decoder index when we are yet selecting
  11. a decoder. Used when decoder is NULL. */
  12. ProtoViewFieldSet* fieldset; /* The fields to populate. */
  13. uint32_t cur_field; /* Field we are editing right now. This
  14. is the index inside the 'fieldset'
  15. fields. */
  16. char* user_value; /* Keyboard input to replace the current
  17. field value goes here. */
  18. } BuildViewPrivData;
  19. /* Not all the decoders support message bulding, so we can't just
  20. * increment / decrement the cur_decoder index here. */
  21. static void select_next_decoder(ProtoViewApp* app) {
  22. BuildViewPrivData* privdata = app->view_privdata;
  23. do {
  24. privdata->cur_decoder++;
  25. if(Decoders[privdata->cur_decoder] == NULL) privdata->cur_decoder = 0;
  26. } while(Decoders[privdata->cur_decoder]->get_fields == NULL);
  27. }
  28. /* Like select_next_decoder() but goes backward. */
  29. static void select_prev_decoder(ProtoViewApp* app) {
  30. BuildViewPrivData* privdata = app->view_privdata;
  31. do {
  32. if(privdata->cur_decoder == 0) {
  33. /* Go one after the last one to wrap around. */
  34. while(Decoders[privdata->cur_decoder]) privdata->cur_decoder++;
  35. }
  36. privdata->cur_decoder--;
  37. } while(Decoders[privdata->cur_decoder]->get_fields == NULL);
  38. }
  39. /* Render the view to select the decoder, among the ones that
  40. * support message building. */
  41. static void render_view_select_decoder(Canvas* const canvas, ProtoViewApp* app) {
  42. BuildViewPrivData* privdata = app->view_privdata;
  43. canvas_set_font(canvas, FontPrimary);
  44. canvas_draw_str(canvas, 0, 9, "Signal creator");
  45. canvas_set_font(canvas, FontSecondary);
  46. canvas_draw_str(canvas, 0, 19, "up/down: select, ok: choose");
  47. canvas_set_font(canvas, FontPrimary);
  48. canvas_draw_str_aligned(
  49. canvas, 64, 38, AlignCenter, AlignCenter, Decoders[privdata->cur_decoder]->name);
  50. }
  51. /* Render the view that allows the user to populate the fields needed
  52. * for the selected decoder to build a message. */
  53. static void render_view_set_fields(Canvas* const canvas, ProtoViewApp* app) {
  54. BuildViewPrivData* privdata = app->view_privdata;
  55. char buf[32];
  56. snprintf(
  57. buf,
  58. sizeof(buf),
  59. "%s field %d/%d",
  60. privdata->decoder->name,
  61. (int)privdata->cur_field + 1,
  62. (int)privdata->fieldset->numfields);
  63. canvas_set_color(canvas, ColorBlack);
  64. canvas_draw_box(canvas, 0, 0, 128, 21);
  65. canvas_set_color(canvas, ColorWhite);
  66. canvas_set_font(canvas, FontPrimary);
  67. canvas_draw_str(canvas, 1, 9, buf);
  68. canvas_set_font(canvas, FontSecondary);
  69. canvas_draw_str(canvas, 1, 19, "up/down: next field, ok: edit");
  70. /* Write the field name, type, current content. */
  71. canvas_set_color(canvas, ColorBlack);
  72. ProtoViewField* field = privdata->fieldset->fields[privdata->cur_field];
  73. snprintf(
  74. buf, sizeof(buf), "%s %s:%d", field->name, field_get_type_name(field), (int)field->len);
  75. buf[0] = toupper(buf[0]);
  76. canvas_set_font(canvas, FontPrimary);
  77. canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, buf);
  78. canvas_set_font(canvas, FontSecondary);
  79. /* Render the current value between "" */
  80. unsigned int written = (unsigned int)field_to_string(buf + 1, sizeof(buf) - 1, field);
  81. buf[0] = '"';
  82. if(written + 3 < sizeof(buf)) memcpy(buf + written + 1, "\"\x00", 2);
  83. canvas_draw_str_aligned(canvas, 63, 45, AlignCenter, AlignCenter, buf);
  84. /* Footer instructions. */
  85. canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr");
  86. }
  87. /* Render the build message view. */
  88. void render_view_build_message(Canvas* const canvas, ProtoViewApp* app) {
  89. BuildViewPrivData* privdata = app->view_privdata;
  90. if(privdata->decoder)
  91. render_view_set_fields(canvas, app);
  92. else
  93. render_view_select_decoder(canvas, app);
  94. }
  95. /* Handle input for the decoder selection. */
  96. static void process_input_select_decoder(ProtoViewApp* app, InputEvent input) {
  97. BuildViewPrivData* privdata = app->view_privdata;
  98. if(input.type == InputTypeShort) {
  99. if(input.key == InputKeyOk) {
  100. privdata->decoder = Decoders[privdata->cur_decoder];
  101. privdata->fieldset = fieldset_new();
  102. privdata->decoder->get_fields(privdata->fieldset);
  103. /* If the currently decoded message was produced with the
  104. * same decoder the user selected, let's populate the
  105. * defaults with the current values. So the user will
  106. * actaully edit the current message. */
  107. if(app->signal_decoded && app->msg_info->decoder == privdata->decoder) {
  108. fieldset_copy_matching_fields(privdata->fieldset, app->msg_info->fieldset);
  109. }
  110. /* Now we use the subview system in order to protect the
  111. message editing mode from accidental < or > presses.
  112. Since we are technically into a subview now, we'll have
  113. control of < and >. */
  114. InputEvent ii = {.type = InputTypePress, .key = InputKeyDown};
  115. ui_process_subview_updown(app, ii, 2);
  116. } else if(input.key == InputKeyDown) {
  117. select_next_decoder(app);
  118. } else if(input.key == InputKeyUp) {
  119. select_prev_decoder(app);
  120. }
  121. }
  122. }
  123. /* Called after the user typed the new field value in the keyboard.
  124. * Let's save it and remove the keyboard view. */
  125. static void text_input_done_callback(void* context) {
  126. ProtoViewApp* app = context;
  127. BuildViewPrivData* privdata = app->view_privdata;
  128. if(field_set_from_string(
  129. privdata->fieldset->fields[privdata->cur_field],
  130. privdata->user_value,
  131. strlen(privdata->user_value)) == false) {
  132. ui_show_alert(app, "Invalid value", 1500);
  133. }
  134. free(privdata->user_value);
  135. privdata->user_value = NULL;
  136. ui_dismiss_keyboard(app);
  137. }
  138. /* Handles the effects of < and > keys in field editing mode.
  139. * Instead of force the user to enter the text input mode, delete
  140. * the old value, enter the one, we allow to increment and
  141. * decrement the current field in a much simpler way.
  142. *
  143. * The current filed is changed by 'incr' amount. */
  144. static bool increment_current_field(ProtoViewApp* app, int incr) {
  145. BuildViewPrivData* privdata = app->view_privdata;
  146. ProtoViewFieldSet* fs = privdata->fieldset;
  147. ProtoViewField* f = fs->fields[privdata->cur_field];
  148. return field_incr_value(f, incr);
  149. }
  150. /* Handle input for fields editing mode. */
  151. static void process_input_set_fields(ProtoViewApp* app, InputEvent input) {
  152. BuildViewPrivData* privdata = app->view_privdata;
  153. ProtoViewFieldSet* fs = privdata->fieldset;
  154. if(input.type == InputTypeShort && input.key == InputKeyOk) {
  155. /* Show the keyboard to let the user type the new
  156. * value. */
  157. if(privdata->user_value == NULL) privdata->user_value = malloc(USER_VALUE_LEN);
  158. field_to_string(privdata->user_value, USER_VALUE_LEN, fs->fields[privdata->cur_field]);
  159. ui_show_keyboard(app, privdata->user_value, USER_VALUE_LEN, text_input_done_callback);
  160. } else if(input.type == InputTypeShort && input.key == InputKeyDown) {
  161. privdata->cur_field = (privdata->cur_field + 1) % fs->numfields;
  162. } else if(input.type == InputTypeShort && input.key == InputKeyUp) {
  163. if(privdata->cur_field == 0)
  164. privdata->cur_field = fs->numfields - 1;
  165. else
  166. privdata->cur_field--;
  167. } else if(input.type == InputTypeShort && input.key == InputKeyRight) {
  168. increment_current_field(app, 1);
  169. } else if(input.type == InputTypeShort && input.key == InputKeyLeft) {
  170. increment_current_field(app, -1);
  171. } else if(input.type == InputTypeRepeat && input.key == InputKeyRight) {
  172. // The reason why we don't use a large increment directly
  173. // is that certain field types only support +1 -1 increments.
  174. int times = 10;
  175. while(times--) increment_current_field(app, 1);
  176. } else if(input.type == InputTypeRepeat && input.key == InputKeyLeft) {
  177. int times = 10;
  178. while(times--) increment_current_field(app, -1);
  179. } else if(input.type == InputTypeLong && input.key == InputKeyOk) {
  180. // Build the message in a fresh raw buffer.
  181. if(privdata->decoder->build_message) {
  182. RawSamplesBuffer* rs = raw_samples_alloc();
  183. privdata->decoder->build_message(rs, privdata->fieldset);
  184. app->signal_decoded = false; // So that the new signal will be
  185. // accepted as the current signal.
  186. scan_for_signal(app, rs, 5);
  187. raw_samples_free(rs);
  188. ui_show_alert(app, "Done: press back key", 3000);
  189. }
  190. }
  191. }
  192. /* Handle input for the build message view. */
  193. void process_input_build_message(ProtoViewApp* app, InputEvent input) {
  194. BuildViewPrivData* privdata = app->view_privdata;
  195. if(privdata->decoder)
  196. process_input_set_fields(app, input);
  197. else
  198. process_input_select_decoder(app, input);
  199. }
  200. /* Enter view callback. */
  201. void view_enter_build_message(ProtoViewApp* app) {
  202. BuildViewPrivData* privdata = app->view_privdata;
  203. // When we enter the view, the current decoder is just set to zero.
  204. // Seek the next valid if needed.
  205. if(Decoders[privdata->cur_decoder]->get_fields == NULL) {
  206. select_next_decoder(app);
  207. }
  208. // However if there is currently a decoded message, and the
  209. // decoder of such message supports message building, let's
  210. // select it.
  211. if(app->signal_decoded && app->msg_info->decoder->get_fields &&
  212. app->msg_info->decoder->build_message) {
  213. while(Decoders[privdata->cur_decoder] != app->msg_info->decoder) select_next_decoder(app);
  214. }
  215. }
  216. /* Called on exit for cleanup. */
  217. void view_exit_build_message(ProtoViewApp* app) {
  218. BuildViewPrivData* privdata = app->view_privdata;
  219. if(privdata->fieldset) fieldset_free(privdata->fieldset);
  220. if(privdata->user_value) free(privdata->user_value);
  221. }