text-viewer/src/main.c

382 lines
9.6 KiB
C

#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/gint.h>
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#include <justui/jinput.h>
#include <justui/jfileselect.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "vtext.h"
#define ENABLE_USB 1
#ifdef ENABLE_USB
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#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;
}