#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vtext.h" #define ENABLE_USB 1 #ifdef ENABLE_USB #include #include #endif extern font_t uf5x7; extern font_t uf8x9; // Global application data // struct source { /* Status (0 if everything is fine, error number otherwise) */ int status; /* File descriptor (usually file is closed after loading and this is -1) */ int fd; /* File path (owned by this structure) */ char *path; /* Full data loaded to memory */ // TODO: Load partial files? char *data; /* General file information */ int size; int lines; }; struct search_results { /* Source buffer that this indexes in. */ struct source *source; /* Offset of occurrences, depending on the size of the file */ union { uint16_t *occs16; uint32_t *occs32; }; }; struct { /* GUI elements */ jscene *scene; jlabel *title; jlabel *status; jfkeys *fkeys; jfileselect *fileselect; jinput *search_input; vtext *viewer; /* Current open file. */ struct source source; /* Search results. */ struct search_results *search_results; /* Current view. */ int view; /* Current position in the menus. */ // int fkmenu; } app; enum { VIEW_FILESELECT, VIEW_TEXT, VIEW_SEARCH, }; // General UI update functions // static void fkeys_sprintf(jfkeys *fk, char const *fmt, ...) { static char str[128]; va_list args; va_start(args, fmt); vsnprintf(str, sizeof str, fmt, args); va_end(args); jfkeys_set(fk, str); } static void update_fkeys(void) { char wrap[16]; if(!app.viewer) strcpy(wrap, ""); else if(app.viewer->wrap == VTEXT_WRAP_NONE) strcpy(wrap, "@NOWRAP"); else if(app.viewer->wrap == VTEXT_WRAP_CHAR) strcpy(wrap, "@WRAP(C)"); else if(app.viewer->wrap == VTEXT_WRAP_WORD) strcpy(wrap, "@WRAP(W)"); if(app.view == VIEW_FILESELECT) { jfkeys_set(app.fkeys, "/OPEN;;;;;"); } else if(app.view == VIEW_TEXT) { fkeys_sprintf(app.fkeys, "/OPEN;/SEARCH;%s;@FONT;;", wrap); } else if(app.view == VIEW_SEARCH) { fkeys_sprintf(app.fkeys, "/OPEN;#SEARCH;%s;@FONT;;", wrap); } } static void update_status(void) { struct source *s = &app.source; if(!s->path) { jwidget_set_visible(app.status, false); jlabel_set_text(app.title, "Text Viewer"); return; } jwidget_set_visible(app.status, true); jlabel_asprintf(app.title, "%s - Text Viewer", s->path); if(s->status) { jlabel_asprintf(app.status, "Error %d!", s->status); } else { jlabel_asprintf(app.status, "vlines %d--%d/%d [%d] (%d lines, %d B)", app.viewer->scroll + 1, app.viewer->scroll + app.viewer->visible_lines, app.viewer->virtual_lines, app.viewer->max_scroll, s->lines, s->size); } } static void switch_to_view(int view) { if(view == VIEW_FILESELECT) { jscene_show_and_focus(app.scene, app.fileselect); } if(view == VIEW_TEXT) { jscene_show_and_focus(app.scene, app.viewer); } if(view == VIEW_SEARCH) { jscene_show_and_focus(app.scene, app.search_input); } else { jwidget_set_visible(app.search_input, false); } app.view = view; update_fkeys(); update_status(); } static void source_analyze(struct source *source) { source->lines = 1; for(int i = 0; i < source->size; i++) source->lines += (source->data[i] == '\n'); // TODO: Check if source is valid UTF-8 } static int source_open(struct source *source, char const *path) { memset(source, 0, sizeof *source); int fd = open(path, O_RDONLY); if(fd < 0) return fd; int size = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); char *data = malloc(size + 1); if(!data) { close(fd); return -ENOMEM; } int rc = read(fd, data, size); if(rc < 0) { close(fd); return rc; } if(rc < size) { close(fd); return -999; } data[size] = 0; close(fd); source->fd = -1; source->path = strdup(path); source->data = data; source->size = size; source_analyze(source); return 0; } static void source_close(struct source *source) { free(source->path); free(source->data); memset(source, 0, sizeof *source); } void txt_view(void) { memset(&app, 0, sizeof app); // jscene *scene = jscene_create_fullscreen(NULL); jlabel *title = jlabel_create("Text Viewer", scene); jwidget *stack = jwidget_create(scene); jfkeys *fkeys = jfkeys_create(";;;;;",scene); if(!scene || !title || !stack || !fkeys) { jwidget_destroy(scene); return; } app.scene = scene; app.title = title; app.fkeys = fkeys; jwidget_set_background(title, C_BLACK); jlabel_set_text_color(title, C_WHITE); jwidget_set_stretch(title, 1, 0, false); jwidget_set_padding(title, 3, 6, 3, 6); jlayout_set_vbox(scene)->spacing = 3; jlayout_set_stack(stack); jwidget_set_padding(stack, 0, 6, 0, 6); jwidget_set_stretch(stack, 1, 1, false); // Main tab // jwidget *tab_viewer = jwidget_create(NULL); vtext *viewer = vtext_create(tab_viewer); jinput *search_input = jinput_create("Search: ", 40, tab_viewer); jlabel *status = jlabel_create("", tab_viewer); app.viewer = viewer; app.search_input = search_input; app.status = status; jwidget_set_margin(viewer, 2, 0, 0, 0); jwidget_set_stretch(viewer, 1, 1, false); vtext_set_line_spacing(viewer, 3); vtext_set_font(viewer, &uf8x9); jwidget_set_stretch(status, 1, 0, false); jwidget_set_padding(status, 2, 0, 0, 0); jwidget_set_borders(status, J_BORDER_SOLID, C_BLACK, 1, 0, 0, 0); jwidget_set_stretch(search_input, 1, 0, false); jwidget_set_visible(search_input, false); jlayout_set_vbox(tab_viewer)->spacing = 3; jwidget_add_child(stack, tab_viewer); jwidget_set_stretch(tab_viewer, 1, 1, false); // File selection tab // jfileselect *fileselect = jfileselect_create(stack); jfileselect_set_show_file_size(fileselect, true); jwidget_set_stretch(fileselect, 1, 1, false); app.fileselect = fileselect; // Initial state // switch_to_view(VIEW_TEXT); // Event handling // int key = 0; while(1) { jevent e = jscene_run(scene); if(e.type == JSCENE_PAINT) { dclear(C_WHITE); jscene_render(scene); dupdate(); } if(e.type == JINPUT_VALIDATED) { char const *key = jinput_value(search_input); if(*key) { /* Don't switch to text view so that we can keep searching */ vtext_scroll_to_substring(viewer, key); } else { switch_to_view(VIEW_TEXT); } } if(e.type == JINPUT_CANCELED) { switch_to_view(VIEW_TEXT); } if(e.type == JFILESELECT_LOADED) { char const *path = jfileselect_current_folder(fileselect); jlabel_asprintf(title, "Browsing: %s", path ? path : "(nil)"); } if(e.type == JFILESELECT_CANCELED) { switch_to_view(VIEW_TEXT); } if(e.type == JFILESELECT_VALIDATED) { char const *file = jfileselect_selected_file(fileselect); if(file) { source_close(&app.source); app.source.status = source_open(&app.source, file); if(!app.source.status) { vtext_set_source(viewer, app.source.data, app.source.size); } switch_to_view(VIEW_TEXT); } } if(e.type == VTEXT_CHANGED) { update_status(); } // Fkey menu navigation // if(e.type != JSCENE_KEY || e.key.type == KEYEV_UP) continue; key = e.key.key; #if ENABLE_USB if(key == KEY_OPTN && e.key.shift && e.key.alpha) { if(!usb_is_open()) { usb_interface_t const *intf[] = { &usb_ff_bulk, NULL }; usb_open(intf, GINT_CALL_NULL); usb_open_wait(); } usb_fxlink_screenshot(true); } #endif if(jscene_focused_widget(scene) == search_input) continue; if(key == KEY_F1) { jfileselect_set_saveas(fileselect, false); jfileselect_browse(fileselect, "/"); switch_to_view(VIEW_FILESELECT); } else if(key == KEY_F2 && app.source.data) { jinput_clear(search_input); switch_to_view(VIEW_SEARCH); } else if(key == KEY_F3 && app.source.data) { if(viewer->wrap == VTEXT_WRAP_NONE) vtext_set_word_wrapping(viewer, VTEXT_WRAP_CHAR); else if(viewer->wrap == VTEXT_WRAP_CHAR) vtext_set_word_wrapping(viewer, VTEXT_WRAP_WORD); else if(viewer->wrap == VTEXT_WRAP_WORD) vtext_set_word_wrapping(viewer, VTEXT_WRAP_NONE); update_fkeys(); } else if(key == KEY_F4) { if(viewer->font == &uf8x9) vtext_set_font(viewer, &uf5x7); else vtext_set_font(viewer, &uf8x9); } } jwidget_destroy(scene); } int main(void) { txt_view(); return 1; }