view_build.c 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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)
  26. privdata->cur_decoder = 0;
  27. } while(Decoders[privdata->cur_decoder]->get_fields == NULL);
  28. }
  29. /* Like select_next_decoder() but goes backward. */
  30. static void select_prev_decoder(ProtoViewApp *app) {
  31. BuildViewPrivData *privdata = app->view_privdata;
  32. do {
  33. if (privdata->cur_decoder == 0) {
  34. /* Go one after the last one to wrap around. */
  35. while(Decoders[privdata->cur_decoder]) privdata->cur_decoder++;
  36. }
  37. privdata->cur_decoder--;
  38. } while(Decoders[privdata->cur_decoder]->get_fields == NULL);
  39. }
  40. /* Render the view to select the decoder, among the ones that
  41. * support message building. */
  42. static void render_view_select_decoder(Canvas *const canvas, ProtoViewApp *app) {
  43. BuildViewPrivData *privdata = app->view_privdata;
  44. canvas_set_font(canvas, FontPrimary);
  45. canvas_draw_str(canvas, 0, 9, "Signal creator");
  46. canvas_set_font(canvas, FontSecondary);
  47. canvas_draw_str(canvas, 0, 19, "up/down: select, ok: choose");
  48. // When entering the view, the current decoder is just set to zero.
  49. // Seek the next valid if needed.
  50. if (Decoders[privdata->cur_decoder]->get_fields == NULL)
  51. select_next_decoder(app);
  52. canvas_set_font(canvas, FontPrimary);
  53. canvas_draw_str_aligned(canvas,64,38,AlignCenter,AlignCenter,
  54. Decoders[privdata->cur_decoder]->name);
  55. }
  56. /* Render the view that allows the user to populate the fields needed
  57. * for the selected decoder to build a message. */
  58. static void render_view_set_fields(Canvas *const canvas, ProtoViewApp *app) {
  59. BuildViewPrivData *privdata = app->view_privdata;
  60. char buf[32];
  61. snprintf(buf,sizeof(buf), "%s field %d/%d",
  62. privdata->decoder->name, (int)privdata->cur_field+1,
  63. (int)privdata->fieldset->numfields);
  64. canvas_set_color(canvas,ColorBlack);
  65. canvas_draw_box(canvas,0,0,128,21);
  66. canvas_set_color(canvas,ColorWhite);
  67. canvas_set_font(canvas, FontPrimary);
  68. canvas_draw_str(canvas, 1, 9, buf);
  69. canvas_set_font(canvas, FontSecondary);
  70. canvas_draw_str(canvas, 1, 19, "up/down: next field, ok: edit");
  71. /* Write the field name, type, current content. */
  72. canvas_set_color(canvas,ColorBlack);
  73. ProtoViewField *field = privdata->fieldset->fields[privdata->cur_field];
  74. snprintf(buf,sizeof(buf), "%s %s:%d", field->name,
  75. field_get_type_name(field), (int)field->len);
  76. buf[0] = toupper(buf[0]);
  77. canvas_set_font(canvas, FontPrimary);
  78. canvas_draw_str_aligned(canvas,64,30,AlignCenter,AlignCenter,buf);
  79. canvas_set_font(canvas, FontSecondary);
  80. /* Render the current value between "" */
  81. unsigned int written = (unsigned int) field_to_string(buf+1,sizeof(buf)-1,field);
  82. buf[0] = '"';
  83. if (written+3 < sizeof(buf)) memcpy(buf+written+1,"\"\x00",2);
  84. canvas_draw_str_aligned(canvas,63,45,AlignCenter,AlignCenter,buf);
  85. /* Footer instructions. */
  86. canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr");
  87. }
  88. /* Render the build message view. */
  89. void render_view_build_message(Canvas *const canvas, ProtoViewApp *app) {
  90. BuildViewPrivData *privdata = app->view_privdata;
  91. if (privdata->decoder)
  92. render_view_set_fields(canvas,app);
  93. else
  94. render_view_select_decoder(canvas,app);
  95. }
  96. /* Handle input for the decoder selection. */
  97. static void process_input_select_decoder(ProtoViewApp *app, InputEvent input) {
  98. BuildViewPrivData *privdata = app->view_privdata;
  99. if (input.type == InputTypeShort) {
  100. if (input.key == InputKeyOk) {
  101. privdata->decoder = Decoders[privdata->cur_decoder];
  102. privdata->fieldset = fieldset_new();
  103. privdata->decoder->get_fields(privdata->fieldset);
  104. // Now we use the subview system in order to protect the
  105. // message editing mode from accidental < or > presses.
  106. // Since we are technically into a subview now, we'll have
  107. // control of < and >.
  108. InputEvent ii = {.type = InputTypePress, .key = InputKeyDown};
  109. ui_process_subview_updown(app,ii,2);
  110. } else if (input.key == InputKeyDown) {
  111. select_next_decoder(app);
  112. } else if (input.key == InputKeyUp) {
  113. select_prev_decoder(app);
  114. }
  115. }
  116. }
  117. /* Called after the user typed the new field value in the keyboard.
  118. * Let's save it and remove the keyboard view. */
  119. static void text_input_done_callback(void* context) {
  120. ProtoViewApp *app = context;
  121. BuildViewPrivData *privdata = app->view_privdata;
  122. if (field_set_from_string(privdata->fieldset->fields[privdata->cur_field],
  123. privdata->user_value, strlen(privdata->user_value)) == false)
  124. {
  125. ui_show_alert(app, "Invalid value", 1500);
  126. }
  127. free(privdata->user_value);
  128. privdata->user_value = NULL;
  129. ui_dismiss_keyboard(app);
  130. }
  131. /* Handles the effects of < and > keys in field editing mode.
  132. * Instead of force the user to enter the text input mode, delete
  133. * the old value, enter the one, we allow to increment and
  134. * decrement the current field in a much simpler way.
  135. *
  136. * The current filed is changed by 'incr' amount. */
  137. static bool increment_current_field(ProtoViewApp *app, int incr) {
  138. BuildViewPrivData *privdata = app->view_privdata;
  139. ProtoViewFieldSet *fs = privdata->fieldset;
  140. ProtoViewField *f = fs->fields[privdata->cur_field];
  141. return field_incr_value(f,incr);
  142. }
  143. /* Handle input for fields editing mode. */
  144. static void process_input_set_fields(ProtoViewApp *app, InputEvent input) {
  145. BuildViewPrivData *privdata = app->view_privdata;
  146. ProtoViewFieldSet *fs = privdata->fieldset;
  147. if (input.type == InputTypeShort && input.key == InputKeyOk) {
  148. /* Show the keyboard to let the user type the new
  149. * value. */
  150. if (privdata->user_value == NULL)
  151. privdata->user_value = malloc(USER_VALUE_LEN);
  152. field_to_string(privdata->user_value, USER_VALUE_LEN,
  153. fs->fields[privdata->cur_field]);
  154. ui_show_keyboard(app, privdata->user_value, USER_VALUE_LEN,
  155. text_input_done_callback);
  156. } else if (input.type == InputTypeShort && input.key == InputKeyDown) {
  157. privdata->cur_field = (privdata->cur_field+1) % fs->numfields;
  158. } else if (input.type == InputTypeShort && input.key == InputKeyUp) {
  159. if (privdata->cur_field == 0)
  160. privdata->cur_field = fs->numfields-1;
  161. else
  162. privdata->cur_field--;
  163. } else if (input.type == InputTypeShort && input.key == InputKeyRight) {
  164. increment_current_field(app,1);
  165. } else if (input.type == InputTypeShort && input.key == InputKeyLeft) {
  166. increment_current_field(app,-1);
  167. } else if (input.type == InputTypeRepeat && input.key == InputKeyRight) {
  168. // The reason why we don't use a large increment directly
  169. // is that certain field types only support +1 -1 increments.
  170. int times = 10;
  171. while(times--) increment_current_field(app,1);
  172. } else if (input.type == InputTypeRepeat && input.key == InputKeyLeft) {
  173. int times = 10;
  174. while(times--) increment_current_field(app,-1);
  175. } else if (input.type == InputTypeLong && input.key == InputKeyOk) {
  176. // Build the message in a fresh raw buffer.
  177. if (privdata->decoder->build_message) {
  178. RawSamplesBuffer *rs = raw_samples_alloc();
  179. privdata->decoder->build_message(rs,privdata->fieldset);
  180. app->signal_decoded = false; // So that the new signal will be
  181. // accepted as the current signal.
  182. scan_for_signal(app,rs);
  183. raw_samples_free(rs);
  184. ui_show_alert(app,"Done: press back key",3000);
  185. }
  186. }
  187. }
  188. /* Handle input for the build message view. */
  189. void process_input_build_message(ProtoViewApp *app, InputEvent input) {
  190. BuildViewPrivData *privdata = app->view_privdata;
  191. if (privdata->decoder)
  192. process_input_set_fields(app,input);
  193. else
  194. process_input_select_decoder(app,input);
  195. }
  196. /* Called on exit for cleanup. */
  197. void view_exit_build_message(ProtoViewApp *app) {
  198. BuildViewPrivData *privdata = app->view_privdata;
  199. if (privdata->fieldset) fieldset_free(privdata->fieldset);
  200. if (privdata->user_value) free(privdata->user_value);
  201. }