382 lines
9.6 KiB
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;
|
|
}
|