Adding text editor functionality to text viewer.

This commit is contained in:
Nicholaos Mouzourakis 2024-06-27 23:17:31 -04:00 committed by d0k3
parent 1bbd8193d1
commit d65a5b6771
10 changed files with 498 additions and 185 deletions

View File

@ -116,7 +116,7 @@ With the possibilites GodMode9 provides, not everything may be obvious at first
* __Search drives and folders__: Just press R+A on the drive / folder you want to search.
* __Compare and verify files__: Press the A button on the first file, select `Calculate SHA-256`. Do the same for the second file. If the two files are identical, you will get a message about them being identical. On the SDCARD drive (`0:`) you can also write an SHA file, so you can check for any modifications at a later point.
* __Hexview and hexedit any file__: Press the A button on a file and select `Show in Hexeditor`. A button again enables edit mode, hold the A button and press arrow buttons to edit bytes. You will get an additional confirmation prompt to take over changes. Take note that for certain files, write permissions can't be enabled.
* __View text files in a text viewer__: Press the A button on a file and select `Show in Textviewer` (only shows up for actual text files). You can enable wordwrapped mode via R+Y, and navigate around the file via R+X and the dpad.
* __View/edit text files in a text editor__: Press the A button on a file and select `Show in Text Editor` (only shows up for actual text files). You can enable wordwrapped mode via R+Y, and navigate around the file via R+X and the dpad.
* __Chainload FIRM payloads__: Press the A button on a FIRM file, select `FIRM options` -> `Boot FIRM`. Keep in mind you should not run FIRMs from dubious sources and that the write permissions system is no longer in place after booting a payload.
* __Chainload FIRM payloads from a neat menu__: The `payloads` menu is found inside the HOME button menu. It provides any FIRM found in `0:/gm9/payloads` for quick chainloading.
* __Inject a file to another file__: Put exactly one file (the file to be injected from) into the clipboard (via the Y button). Press A on the file to be injected to. There will be an option to inject the first file into it.
@ -208,6 +208,7 @@ This tool would not have been possible without the help of numerous people. Than
* **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator)
* **Amazingmax fonts** for the Amazdoom font
* **TakWolf** for [fusion-pixel-font](https://github.com/TakWolf/fusion-pixel-font) used for Chinese and Korean
* **nevumx** for turning the text viewer into a text editor with UTF-8 and LF/CRLF support
* The fine folks on **the official GodMode9 IRC channel and Discord server**
* The fine folks on **freenode #Cakey**
* All **[3dbrew.org](https://www.3dbrew.org/wiki/Main_Page) editors**

View File

@ -3,7 +3,6 @@
#include "language.h"
#include "swkbd.h"
#include "timer.h"
#include "hid.h"
#include "utf.h"
@ -12,7 +11,7 @@ static inline char to_uppercase(char c) {
return c;
}
static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout, bool multi_line) {
// count # of rows
u32 n_rows = 0;
for (u32 i = 0;; i++) {
@ -26,16 +25,18 @@ static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
u32 height = (n_rows) ? (n_rows * SWKBD_STDKEY_HEIGHT) + ((n_rows-1) * SWKDB_KEY_SPACING) : 0;
u32 p_y = SCREEN_HEIGHT - height - SWKBD_STDKEY_HEIGHT - SWKDB_KEY_SPACING;
// set up the textbox
TouchBox* txtbox = swkbd;
txtbox->x = (SCREEN_WIDTH_BOT - SWKBD_TEXTBOX_WIDTH) / 2;
txtbox->y = p_y - 30;
txtbox->w = SWKBD_TEXTBOX_WIDTH;
txtbox->h = 30;
txtbox->id = KEY_TXTBOX;
// set button positions
TouchBox* tb = swkbd + 1;
TouchBox* tb = swkbd;
if (!multi_line) {
// set up the textbox
TouchBox* txtbox = tb++;
txtbox->x = (SCREEN_WIDTH_BOT - SWKBD_TEXTBOX_WIDTH) / 2;
txtbox->y = p_y - 30;
txtbox->w = SWKBD_TEXTBOX_WIDTH;
txtbox->h = 30;
txtbox->id = KEY_TXTBOX;
}
for (u32 l = 0, k = 0; layout[l] != 0; ) {
// calculate width of current row
u32 n_keys = layout[l++];
@ -70,8 +71,9 @@ static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
return true;
}
static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase) {
static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase, const bool multi_line) {
const char* keystrs[] = { SWKBD_KEYSTR };
const char* ml_keystrs[] = { SWKBD_ML_KEYSTR };
const u32 color = (pressed) ? COLOR_SWKBD_PRESSED :
(key->id == KEY_ENTER) ? COLOR_SWKBD_ENTER :
((key->id == KEY_CAPS) && (uppercase > 1)) ? COLOR_SWKBD_CAPS :
@ -81,7 +83,7 @@ static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase
if (key->id == KEY_TXTBOX) return;
char keystr[16];
if (key->id >= 0x80) snprintf(keystr, sizeof(keystr), "%s", keystrs[key->id - 0x80]);
if (key->id >= 0x80) snprintf(keystr, sizeof(keystr), "%s", (multi_line ? ml_keystrs : keystrs)[key->id - 0x80]);
else {
keystr[0] = (uppercase) ? to_uppercase(key->id) : key->id;
keystr[1] = 0;
@ -111,12 +113,12 @@ static void DrawKeyBoardBox(TouchBox* swkbd, u32 color) {
DrawRectangle(BOT_SCREEN, x0-1, y0-1, x1-x0+2, y1-y0+2, color);
}
static void DrawKeyBoard(TouchBox* swkbd, const u32 uppercase) {
static void DrawKeyBoard(TouchBox* swkbd, const u32 uppercase, const bool multi_line) {
// we need to make sure to skip the textbox here(first entry)
// draw keyboard
for (TouchBox* tb = swkbd + 1; tb->id != 0; tb++) {
DrawKey(tb, false, uppercase);
for (TouchBox* tb = swkbd + (multi_line ? 0 : 1); tb->id != 0; tb++) {
DrawKey(tb, false, uppercase, multi_line);
}
}
@ -209,21 +211,24 @@ static void MoveTextBoxCursor(const TouchBox* txtbox, const char* inputstr, cons
}
}
static char KeyboardWait(TouchBox* swkbd, bool uppercase) {
static char KeyboardWait(TouchBox* swkbd, bool uppercase, const bool multi_line) {
u32 id = 0;
u16 x, y;
// wait for touch input (handle key input, too)
while (true) {
u32 pressed = InputWait(0);
if (pressed & BUTTON_B) return KEY_ESCAPE;
if (multi_line && pressed & TIMEOUT_HID) return 0;
else if (pressed & BUTTON_B) return KEY_ESCAPE;
else if (pressed & BUTTON_A) return KEY_ENTER;
else if (pressed & BUTTON_X) return KEY_BKSPC;
else if (pressed & BUTTON_Y) return KEY_INSERT;
else if (pressed & BUTTON_R1) return KEY_CAPS;
else if (pressed & BUTTON_Y) return multi_line ? KEY_CAPS : KEY_INSERT;
else if (!multi_line && pressed & BUTTON_R1) return KEY_CAPS;
else if (pressed & BUTTON_RIGHT) return KEY_RIGHT;
else if (pressed & BUTTON_LEFT) return KEY_LEFT;
else if (pressed & BUTTON_SELECT) return KEY_SWITCH;
else if (multi_line && pressed & BUTTON_UP) return KEY_UP;
else if (multi_line && pressed & BUTTON_DOWN) return KEY_DOWN;
else if (!multi_line && pressed & BUTTON_SELECT) return KEY_SWITCH;
else if (pressed & BUTTON_TOUCH) break;
}
@ -232,9 +237,9 @@ static char KeyboardWait(TouchBox* swkbd, bool uppercase) {
const TouchBox* tb = TouchBoxGet(&id, x, y, swkbd, 0);
if (tb) {
if (id == KEY_TXTBOX) break; // immediately break on textbox
DrawKey(tb, true, uppercase);
DrawKey(tb, true, uppercase, multi_line);
while(HID_ReadTouchState(&x, &y) && (tb == TouchBoxGet(NULL, x, y, swkbd, 0)));
DrawKey(tb, false, uppercase);
DrawKey(tb, false, uppercase, multi_line);
}
}
@ -261,9 +266,9 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
}
// generate keyboards
if (!BuildKeyboard(swkbd_alphabet, keys_alphabet, layout_alphabet)) return false;
if (!BuildKeyboard(swkbd_special, keys_special, layout_special)) return false;
if (!BuildKeyboard(swkbd_numpad, keys_numpad, layout_numpad)) return false;
if (!BuildKeyboard(swkbd_alphabet, keys_alphabet, layout_alphabet, false)) return false;
if (!BuildKeyboard(swkbd_special, keys_special, layout_special, false)) return false;
if (!BuildKeyboard(swkbd_numpad, keys_numpad, layout_numpad, false)) return false;
// (instructional) text
char str[512]; // arbitrary limit, should be more than enough
@ -292,19 +297,19 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
// draw keyboard if required
if (swkbd != swkbd_prev) {
DrawKeyBoardBox(swkbd, COLOR_SWKBD_BOX);
DrawKeyBoard(swkbd, uppercase);
DrawKeyBoard(swkbd, uppercase, false);
DrawTextBox(textbox, inputstr, cursor, &scroll);
swkbd_prev = swkbd;
}
// handle user input
char key = KeyboardWait(swkbd, uppercase);
char key = KeyboardWait(swkbd, uppercase, false);
if (key == KEY_INSERT) key = ' '; // impromptu replacement
if (key == KEY_TXTBOX) {
MoveTextBoxCursor(textbox, inputstr, max_size, &cursor, &scroll);
} else if (key == KEY_CAPS) {
uppercase = (uppercase + 1) % 3;
DrawKeyBoard(swkbd, uppercase);
DrawKeyBoard(swkbd, uppercase, false);
continue;
} else if (key == KEY_ENTER) {
ret = true;
@ -375,7 +380,7 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
}
if (uppercase == 1) {
uppercase = 0;
DrawKeyBoard(swkbd, uppercase);
DrawKeyBoard(swkbd, uppercase, false);
}
}
@ -385,4 +390,38 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
ClearScreen(BOT_SCREEN, COLOR_STD_BG);
return ret;
}
char ShowMultiLineKeyboard(TouchBox* swkbd_alphabet, TouchBox* swkbd_special, TouchBox* swkbd_numpad, TouchBox** swkbd, TouchBox** swkbd_prev, u32* uppercase) {
if (!*swkbd) {
u32 str_width = GetDrawStringWidth(STR_TEXTEDITOR_CONTROLS_KEYBOARD);
if (str_width < (24 * FONT_WIDTH_EXT)) str_width = 24 * FONT_WIDTH_EXT;
u32 str_x = (str_width >= SCREEN_WIDTH_BOT) ? 0 : (SCREEN_WIDTH_BOT - str_width) / 2;
ClearScreen(BOT_SCREEN, COLOR_STD_BG);
DrawStringF(BOT_SCREEN, str_x, 20, COLOR_STD_FONT, COLOR_STD_BG, "%s", STR_TEXTEDITOR_CONTROLS_KEYBOARD);
*swkbd = swkbd_alphabet;
}
// handle keyboard
while (true) {
// draw keyboard if required
if (*swkbd != *swkbd_prev) {
DrawKeyBoardBox(*swkbd, COLOR_SWKBD_BOX);
DrawKeyBoard(*swkbd, *uppercase, true);
*swkbd_prev = *swkbd;
}
// handle user input
char key = KeyboardWait(*swkbd, *uppercase, true);
if (key == KEY_ALPHA) {
*swkbd = swkbd_alphabet;
} else if (key == KEY_SPECIAL) {
*swkbd = swkbd_special;
} else if (key == KEY_NUMPAD) {
*swkbd = swkbd_numpad;
} else if (key == KEY_CAPS) {
*uppercase = (*uppercase + 1) % 3;
DrawKeyBoard(*swkbd, *uppercase, true);
} else return key;
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "common.h"
#include "hid.h"
#include "ui.h"
#include "touchcal.h"
@ -20,11 +21,15 @@ enum {
KEY_ESCAPE = 0x8A,
KEY_SWITCH = 0x8B,
KEY_UNICODE = 0x8C,
KEY_UP = 0x8D,
KEY_DOWN = 0x8E,
KEY_TXTBOX = 0xFF
};
// special key strings
#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "←", "→", "ESC", "SWITCH", "U+"
#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "←", "→", "ESC", "SWITCH", "U+", "↑", "↓"
// multiline special key stings
#define SWKBD_ML_KEYSTR "", "DEL", "INS", "ENTER", "CAPS", "#$@", "123", "ABC", "←", "→", "ESC", "SWITCH", "U+", "↑", "↓"
#define COLOR_SWKBD_NORMAL COLOR_GREY
#define COLOR_SWKBD_PRESSED COLOR_LIGHTGREY
@ -45,6 +50,13 @@ enum {
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '_', '#', '!', \
KEY_CAPS, ' ', KEY_NUMPAD, KEY_SPECIAL, KEY_LEFT, KEY_RIGHT
#define SWKBD_KEYS_ML_ALPHABET \
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', KEY_BKSPC, \
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '&', KEY_ENTER, \
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '(', ')', '[', ']', \
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '_', '#', '!', \
KEY_CAPS, ' ', KEY_NUMPAD, KEY_SPECIAL, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN
#define SWKBD_KEYS_SPECIAL \
'(', ')', '{', '}', '[', ']', \
'.', ',', '?', '!', '`', '\'', \
@ -53,9 +65,9 @@ enum {
KEY_ALPHA, ' ', KEY_BKSPC
#define SWKBD_KEYS_NUMPAD \
'7', '8', '9', 'F', 'E', \
'4', '5', '6', 'D', 'C', \
'3', '2', '1', 'B', 'A', \
'7', '8', '9', 'E', 'F', \
'4', '5', '6', 'C', 'D', \
'1', '2', '3', 'A', 'B', \
'0', '.', '_', KEY_LEFT, KEY_RIGHT, \
KEY_ALPHA, KEY_UNICODE, ' ', KEY_BKSPC
@ -68,12 +80,20 @@ enum {
6, 32, 123, 32, 32, 18, 18, 0, \
0
#define SWKBD_LAYOUT_ML_ALPHABET \
13, 32, 0, \
12, 51, 0, \
13, 0, \
12, 0, \
8, 32, 85, 32, 32, 18, 18, 18, 18, 0, \
0
#define SWKBD_LAYOUT_SPECIAL \
6, 0, \
6, 0, \
6, 0, \
6, 0, \
3, 32, 46, 32, 0, \
3, 32, 47, 32, 0, \
0
#define SWKBD_LAYOUT_NUMPAD \
@ -87,3 +107,5 @@ enum {
#define ShowKeyboardOrPrompt (TouchIsCalibrated() ? ShowKeyboard : ShowStringPrompt)
bool PRINTF_ARGS(3) ShowKeyboard(char* inputstr, u32 max_size, const char *format, ...);
bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout, bool multi_line);
char ShowMultiLineKeyboard(TouchBox* swkbd_alphabet, TouchBox* swkbd_special, TouchBox* swkbd_numpad, TouchBox** swkbd, TouchBox** swkbd_prev, u32* uppercase);

View File

@ -489,22 +489,34 @@ u32 GetDrawStringHeight(const char* str) {
return height;
}
u32 GetCharSize(const char* str) {
const char *start = str;
do {
str++;
} while ((*str & 0xC0) == 0x80);
static inline bool IsIntermediateByte(const char* chr) {
return (*chr & 0xC0) == 0x80 || (chr[-1] == '\r' && chr[0] == '\n');
}
return str - start;
const char* GetNextChar(const char* chr) {
do ++chr; while (IsIntermediateByte(chr));
return chr;
}
const char* GetPrevChar(const char* chr) {
do --chr; while (IsIntermediateByte(chr));
return chr;
}
u32 GetCharSize(const char* str) {
return GetNextChar(str) - str;
}
u32 GetPrevCharSize(const char* str) {
const char *start = str;
do {
str--;
} while ((*str & 0xC0) == 0x80);
return str - GetPrevChar(str);
}
return start - str;
void IncChar(const char** chr) {
*chr = GetNextChar(*chr);
}
void DecChar(const char** chr) {
*chr = GetPrevChar(*chr);
}
u32 GetDrawStringWidth(const char* str) {

View File

@ -70,6 +70,10 @@ void PRINTF_ARGS(4) DrawStringCenter(u16 *screen, u32 color, u32 bgcolor, const
u32 GetCharSize(const char* str);
u32 GetPrevCharSize(const char* str);
const char* GetNextChar(const char* chr);
const char* GetPrevChar(const char* chr);
void IncChar(const char** chr);
void DecChar(const char** chr);
u32 GetDrawStringHeight(const char* str);
u32 GetDrawStringWidth(const char* str);

View File

@ -1285,7 +1285,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
int n_opt = 0;
int special = (special_opt) ? ++n_opt : -1;
int hexviewer = ++n_opt;
int textviewer = (filetype & TXT_GENERIC) ? ++n_opt : -1;
int textviewer = (filetype & TXT_GENERIC || FileGetSize(file_path) == 0) ? ++n_opt : -1;
int calcsha256 = ++n_opt;
int calcsha1 = ++n_opt;
int calccmac = (CheckCmacPath(file_path) == 0) ? ++n_opt : -1;
@ -1377,6 +1377,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
}
else if (user_select == textviewer) { // -> show in text viewer
FileTextViewer(file_path, scriptable);
GetDirContents(current_dir, current_path);
return 0;
}
else if (user_select == calcsha256) { // -> calculate SHA-256
@ -2439,7 +2440,7 @@ u32 HomeMoreMenu(char* current_path) {
char* sysinfo_txt = (char*) malloc(STD_BUFFER_SIZE);
if (!sysinfo_txt) return 1;
MyriaSysinfo(sysinfo_txt);
MemTextViewer(sysinfo_txt, strnlen(sysinfo_txt, STD_BUFFER_SIZE), 1, false);
MemTextViewer(sysinfo_txt, strnlen(sysinfo_txt, STD_BUFFER_SIZE), 1, false, 0, NULL);
free(sysinfo_txt);
return 0;
}

View File

@ -19,6 +19,7 @@
#include "bps.h"
#include "pxi.h"
#include "gyro.h"
#include "utf.h"
#define _MAX_ARGS 4
@ -57,6 +58,7 @@
#define TV_NLIN_DISP (SCREEN_HEIGHT / (FONT_HEIGHT_EXT + (2*TV_VPAD)))
#define TV_LLEN_DISP (((SCREEN_WIDTH_TOP - (2*TV_HPAD)) / FONT_WIDTH_EXT) - (TV_LNOS + 1))
#define MAX_CHAR_SIZE 4 // Max number of bytes needed for a UTF-8 character.
// some useful macros
#define IS_WHITESPACE(c) ((c == ' ') || (c == '\t') || (c == '\r') || (c == '\n'))
@ -206,6 +208,10 @@ static const Gm9ScriptCmd cmd_list[] = {
{ CMD_ID_BKPT , "bkpt" , 0, 0 }
};
// off-screen string indicators
static const char al_str[] = "<< ";
static const char ar_str[] = " >>";
// global vars for preview
static u32 preview_mode = 0; // 0 -> off 1 -> quick 2 -> full
static u32 script_color_active = 0;
@ -260,86 +266,125 @@ static inline u32 hexntostr(const u8* hex, char* str, u32 len) {
return len;
}
static inline u32 line_len(const char* text, u32 len, u32 ww, const char* line, char** eol) {
u32 last = ((text + len) - line);
u32 llen = 0;
char* lf = NULL;
char* spc = NULL;
static inline bool is_crlf(const char* str) {
u32 crlf = 0, lf = 0;
do if (str[0] == '\n') ++lf; else if (str[0] == '\r' && str[1] == '\n') ++crlf, ++str;
while (*str++);
return crlf > lf;
}
if (line >= (text + len))
static inline bool is_newline(const char* chr) {
return chr[0] == '\n' || (chr[0] == '\r' && chr[1] == '\n');
}
static inline u32 bytes_in_chars_u32(const char* str, u32 nchars) {
u32 bytes = 0;
for (u32 i = 0; str[bytes] && i < nchars; bytes += GetCharSize(str + bytes), ++i);
return bytes;
}
static inline int bytes_in_chars_int(const char* str, int nchars) {
int bytes = 0;
for (int i = 0; str[bytes] && i < nchars; bytes += GetCharSize(str + bytes), ++i);
return bytes;
}
static inline int chars_in_bytes(const char* str, int nbytes) {
int chars = 0;
for (int i = 0; str[chars] && i < nbytes; i += GetCharSize(str + i), ++chars);
return chars;
}
static inline u32 chars_between_pointers(const char* start, const char* end) {
u32 chars = 0;
for (const char* i = start; *i && i < end; IncChar(&i), ++chars);
return chars;
}
static inline u32 line_len_chars(const char* text, u32 len, u32 ww, const char* line, const char** eol) {
u32 llen = 0;
const char* lf = NULL;
const char* spc = NULL;
u32 spc_len = 0;
const char* lptr = line;
if (line > text + len)
return 0; // early exit
// search line feeds, spaces (only relevant for wordwrapped)
for (llen = 0; !ww || (llen < ww); llen++) {
if (ww && (line[llen] == ' ')) spc = (char*) (line + llen);
if (!line[llen] || (line[llen] == '\n') || (llen >= last)) {
lf = (char*) (line + llen);
for (; !ww || (llen < ww); IncChar(&lptr), ++llen) {
if (ww && (*lptr == ' ')) {
spc = lptr;
spc_len = llen;
}
if (is_newline(lptr) || lptr >= text + len) {
lf = lptr;
break;
}
}
// line feed found, truncate trailing "empty" chars
// for wordwrapped, stop line after last space (if any)
if (lf) for (; (llen > 0) && (line[llen-1] <= ' '); llen--);
else if (ww && spc) llen = (spc - line) + 1;
if (lf) for (; lptr > line && *GetPrevChar(lptr) < ' '; DecChar(&lptr), --llen);
else if (ww && spc) llen = spc_len + 1;
// signal eol if required
if (eol) *eol = lf;
return llen;
}
static inline char* line_seek(const char* text, u32 len, u32 ww, const char* line, int add) {
// safety checks /
if (line < text) return NULL;
if ((line >= (text + len)) && (add >= 0)) return (char*) line;
static inline const char* line_seek_chars(const char* text, u32 len, u32 ww, const char* line, int add) {
// safety check
if ((line <= text && add <= 0) || (line >= text + len && add >= 0)) return line;
const char* l0 = line;
if (!ww) { // non wordwrapped mode
char* lf = ((char*) line - 1);
for (; add < 0 && l0 > text; add++)
for (DecChar(&l0); l0 > text && l0[-1] != '\n'; DecChar(&l0));
// ensure we are at the start of the line
while ((lf > text) && (*lf != '\n')) lf--;
for (; add > 0 && l0 < text + len; add--)
for (IncChar(&l0); l0 < text + len && l0[-1] != '\n'; IncChar(&l0));
// handle backwards search
for (; (add < 0) && (lf >= text); add++)
for (lf--; (lf >= text) && (*lf != '\n'); lf--);
// handle forwards search
for (; (add > 0) && (lf < text + len); add--)
for (lf++; (lf < text + len) && (*lf != '\n'); lf++);
return lf + 1;
return l0;
} else { // wordwrapped mode
char* l0 = (char*) line;
// handle forwards wordwrapped search
for (; (add > 0) && (l0 < text + len); add--) {
char* eol = NULL;
u32 llenww = line_len(text, len, ww, l0, &eol);
if (eol || !llenww) l0 = line_seek(text, len, 0, l0, 1);
else l0 += llenww;
for (; add > 0 && l0 < text + len; add--) {
const char* eol = NULL;
u32 llenww_chars = line_len_chars(text, len, ww, l0, &eol);
if (eol || !llenww_chars) l0 = line_seek_chars(text, len, 0, l0, 1);
else l0 += bytes_in_chars_u32(l0, llenww_chars);
}
// handle backwards wordwrapped search
while ((add < 0) && (l0 > text)) {
char* l1 = line_seek(text, len, 0, l0, -1);
char* l0_minus1 = l1;
while (add < 0 && l0 > text) {
const char* l1 = line_seek_chars(text, len, 0, l0, -1);
if (l0 > text + len) {
l0 = text + len;
if (l1 == l0) ++add;
}
const char* l0_minus1 = l1;
int nlww = 0; // no of wordwrapped lines in paragraph
for (char* ld = l1; ld < l0; ld = line_seek(text, len, ww, ld, 1), nlww++)
for (const char* ld = l1; ld < l0; ld = line_seek_chars(text, len, ww, ld, 1), nlww++)
l0_minus1 = ld;
if (line > text + len && l0 == text + len && chars_in_bytes(l0_minus1, l0 - l0_minus1) == (int) ww) ++add;
if (add + nlww < 0) { // haven't reached the desired line yet
add += nlww;
l0 = l1;
} else { // reached the desired line
l0 = (add == -1) ? l0_minus1 : line_seek(text, len, ww, l1, nlww + add);
l0 = (add == -1) ? l0_minus1 : line_seek_chars(text, len, ww, l1, nlww + add);
add = 0;
}
}
return l0;
}
}
static inline const char* line_start(const char* text, u32 len, u32 ww, const char* ptr) {
return line_seek_chars(text, len, ww, GetNextChar(ptr), -1);
}
static inline u32 get_lno(const char* text, u32 len, const char* line) {
u32 lno = 1;
@ -1616,7 +1661,7 @@ bool run_line(const char* line_start, const char* line_end, u32* flags, char* er
// checks for illegal ASCII symbols
bool ValidateText(const char* text, u32 len) {
if (!len) return false;
if (!len) return true;
for (u32 i = 0; i < len; i++) {
char c = text[i];
if ((c == '\r') && ((i+1) < len) && (text[i+1] != '\n')) return false; // CR without LF
@ -1626,57 +1671,89 @@ bool ValidateText(const char* text, u32 len) {
return true;
}
void MemTextView(const char* text, u32 len, char* line0, int off_disp, int lno, u32 ww, u32 mno, bool is_script) {
void MemTextView(const char* text, u32 len, const char* line0, int off_disp_chars, int lno, u32 ww, u32 mno, bool is_script, const char* cursor) {
// block placements
const char* al_str = "<< ";
const char* ar_str = " >>";
u32 x_txt = (TV_LNOS >= 0) ? TV_HPAD + ((TV_LNOS+1)*FONT_WIDTH_EXT) : TV_HPAD;
u32 x_lno = TV_HPAD;
u32 p_al = 0;
u32 p_ar = TV_LLEN_DISP - strlen(ar_str);
u32 x_al = x_txt + (p_al * FONT_WIDTH_EXT);
u32 x_ar = x_txt + (p_ar * FONT_WIDTH_EXT);
u32 x_al = x_txt;
u32 x_ar = x_txt + ((TV_LLEN_DISP - strlen(ar_str)) * FONT_WIDTH_EXT);
// display text on screen
char txtstr[TV_LLEN_DISP + 1];
char* ptr = line0;
char txtstr[TV_LLEN_DISP * MAX_CHAR_SIZE + 1];
const char* ptr = line0;
u32 nln = lno;
bool last_empty_line_drawn = false;
for (u32 y = TV_VPAD; y < SCREEN_HEIGHT; y += FONT_HEIGHT_EXT + (2*TV_VPAD)) {
char* ptr_next = line_seek(text, len, ww, ptr, 1);
u32 llen = line_len(text, len, ww, ptr, NULL);
u32 ncpy = ((int) llen < off_disp) ? 0 : (llen - off_disp);
if (ncpy > TV_LLEN_DISP) ncpy = TV_LLEN_DISP;
bool al = !ww && off_disp && (ptr != ptr_next);
bool ar = !ww && (llen > off_disp + TV_LLEN_DISP);
int off_disp_bytes = bytes_in_chars_int(ptr, off_disp_chars);
const char* ptr_next = line_seek_chars(text, len, ww, ptr, 1);
u32 llen_chars = line_len_chars(text, len, ww, ptr, NULL);
u32 llen_bytes = bytes_in_chars_u32(ptr, llen_chars);
u32 tv_llen_disp_chars = llen_chars;
if (tv_llen_disp_chars > TV_LLEN_DISP) tv_llen_disp_chars = TV_LLEN_DISP;
u32 tv_llen_disp_bytes = bytes_in_chars_u32(ptr + off_disp_bytes, tv_llen_disp_chars);
u32 ncpy_bytes = ((int) llen_bytes < off_disp_bytes) ? 0 : (llen_bytes - off_disp_bytes);
if (ncpy_bytes > tv_llen_disp_bytes) ncpy_bytes = tv_llen_disp_bytes;
bool al = !ww && off_disp_chars && (ptr != ptr_next);
bool ar = !ww && (llen_chars + 1 > off_disp_chars + TV_LLEN_DISP);
// set text color / find start of comment of scripts
u32 color_text = (nln == mno) ? script_color_active : (is_script) ? script_color_code : (u32) COLOR_TVTEXT;
int cmt_start = TV_LLEN_DISP; // start of comment in current displayed line (may be negative)
int cmt_start_bytes = TV_LLEN_DISP; // start of comment in current displayed line (may be negative)
int cmt_start_chars = 0;
if (is_script && (nln != mno)) {
char* hash = line_seek(text, len, 0, ptr, 0);
for (; *hash != '#' && (hash - ptr < (int) llen); hash++);
cmt_start = (hash - ptr) - off_disp;
const char* hash = line_start(text, len, 0, ptr);
for (; *hash != '#' && hash - ptr < (int) llen_bytes; hash++);
cmt_start_bytes = hash - (ptr + off_disp_bytes);
if (cmt_start_bytes <= 0) color_text = script_color_comment;
else cmt_start_chars = chars_in_bytes(ptr + off_disp_bytes, cmt_start_bytes);
}
if (cmt_start <= 0) color_text = script_color_comment;
// build text string
snprintf(txtstr, sizeof(txtstr), "%-*.*s", (int) TV_LLEN_DISP, (int) TV_LLEN_DISP, "");
if (ncpy) memcpy(txtstr, ptr + off_disp, ncpy);
snprintf(txtstr, sizeof(txtstr), "%-*.*s", (int) (TV_LLEN_DISP * MAX_CHAR_SIZE), (int) (TV_LLEN_DISP * MAX_CHAR_SIZE), "");
if (ncpy_bytes) {
memcpy(txtstr, ptr + off_disp_bytes, ncpy_bytes);
}
for (char* d = txtstr; *d; d++) if (*d < ' ') *d = ' ';
if (al) memcpy(txtstr + p_al, al_str, strlen(al_str));
if (ar) memcpy(txtstr + p_ar, ar_str, strlen(ar_str));
if (ar) {
char* textstr_end = txtstr + ncpy_bytes;
u32 txtstr_ar_bytes = 0;
for (int txtstr_ar_i = 0; txtstr_ar_i < (int) strlen(ar_str); txtstr_ar_bytes += GetPrevCharSize(textstr_end - txtstr_ar_bytes), ++txtstr_ar_i);
memcpy(textstr_end - txtstr_ar_bytes, ar_str, sizeof(ar_str));
}
if (al) {
u32 txtstr_al_bytes = bytes_in_chars_u32(txtstr, strlen(al_str));
memmove(txtstr + strlen(al_str), txtstr + txtstr_al_bytes, sizeof(txtstr) - txtstr_al_bytes);
memcpy(txtstr, al_str, strlen(al_str));
}
// draw line number & text
DrawString(TOP_SCREEN, txtstr, x_txt, y, color_text, COLOR_STD_BG);
if (TV_LNOS > 0) { // line number
if (ptr != ptr_next)
DrawStringF(TOP_SCREEN, x_lno, y, ((ptr == text) || (*(ptr-1) == '\n')) ? COLOR_TVOFFS : COLOR_TVOFFSL, COLOR_STD_BG, "%0*lu", TV_LNOS, nln);
bool prev_ww_line_full = ww && ww == chars_between_pointers(line_seek_chars(text, len, ww, ptr, -1), ptr);
bool last_line_empty = (ptr == text + len && (!len || ptr[-1] == '\n' || prev_ww_line_full) && !last_empty_line_drawn);
if (ptr != ptr_next || last_line_empty) {
DrawStringF(TOP_SCREEN, x_lno, y, ((ptr == text) || (ptr[-1] == '\n')) ? COLOR_TVOFFS : COLOR_TVOFFSL, COLOR_STD_BG, "%0*lu", TV_LNOS, nln);
if (last_line_empty) last_empty_line_drawn = true;
}
else DrawStringF(TOP_SCREEN, x_lno, y, COLOR_TVOFFSL, COLOR_STD_BG, "%*.*s", TV_LNOS, TV_LNOS, " ");
}
if (cursor) {
u32 cursor_line_offset_chars = chars_between_pointers(ptr + off_disp_bytes, cursor);
if (cursor >= ptr + off_disp_bytes && cursor <= ptr + off_disp_bytes + ncpy_bytes && cursor_line_offset_chars < TV_LLEN_DISP
&& (cursor != ptr + off_disp_bytes + ncpy_bytes || is_newline(cursor) || cursor == text + len)) {
DrawRectangle(TOP_SCREEN, x_txt + cursor_line_offset_chars * FONT_WIDTH_EXT, y, FONT_WIDTH_EXT, 1, COLOR_RED);
DrawRectangle(TOP_SCREEN, x_txt + (cursor_line_offset_chars + 1) * FONT_WIDTH_EXT - ((cursor_line_offset_chars == TV_LLEN_DISP - 1) ? 1 : 0), y, 1, FONT_HEIGHT_EXT, COLOR_RED);
DrawRectangle(TOP_SCREEN, x_txt + cursor_line_offset_chars * FONT_WIDTH_EXT, y, 1, FONT_HEIGHT_EXT, COLOR_RED);
DrawRectangle(TOP_SCREEN, x_txt + cursor_line_offset_chars * FONT_WIDTH_EXT, y + FONT_HEIGHT_EXT - 1, FONT_WIDTH_EXT, 1, COLOR_RED);
cursor = NULL; // prevent cursor from being drawn multiple times at the end of a file
}
}
// colorize comment if is_script
if ((cmt_start > 0) && ((u32) cmt_start < TV_LLEN_DISP)) {
memset(txtstr, ' ', cmt_start);
if (cmt_start_chars > 0 && cmt_start_chars < (int) TV_LLEN_DISP) {
memset(txtstr, ' ', cmt_start_bytes);
memmove(txtstr + cmt_start_chars, txtstr + cmt_start_bytes, sizeof(txtstr) - cmt_start_bytes);
DrawString(TOP_SCREEN, txtstr, x_txt, y, script_color_comment, COLOR_TRANSPARENT);
}
@ -1685,12 +1762,12 @@ void MemTextView(const char* text, u32 len, char* line0, int off_disp, int lno,
if (ar) DrawStringF(TOP_SCREEN, x_ar, y, COLOR_TVOFFS, COLOR_TRANSPARENT, "%s", ar_str);
// advance pointer / line number
for (char* c = ptr; c < ptr_next; c++) if (*c == '\n') ++nln;
for (const char* c = ptr; c < ptr_next; c++) if (*c == '\n') ++nln;
ptr = ptr_next;
}
}
bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script, u32 max_len, const char* save_path) {
u32 ww = TV_LLEN_DISP;
// check if this really is text
@ -1699,11 +1776,37 @@ bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
return false;
}
// clear screens
ClearScreenF(true, true, COLOR_STD_BG);
static const char keys_ml_alphabet[] = { SWKBD_KEYS_ML_ALPHABET };
static const char keys_special[] = { SWKBD_KEYS_SPECIAL };
static const char keys_numpad[] = { SWKBD_KEYS_NUMPAD };
static const u8 layout_ml_alphabet[] = { SWKBD_LAYOUT_ML_ALPHABET };
static const u8 layout_special[] = { SWKBD_LAYOUT_SPECIAL };
static const u8 layout_numpad[] = { SWKBD_LAYOUT_NUMPAD };
TouchBox swkbd_alphabet[64];
TouchBox swkbd_special[32];
TouchBox swkbd_numpad[32];
// instructions
ShowString("%s", STR_TEXTVIEWER_CONTROLS_DETAILS);
u32 uppercase = 0;
TouchBox* swkbd = NULL;
TouchBox* swkbd_prev = NULL;
// generate keyboards
if (!BuildKeyboard(swkbd_alphabet, keys_ml_alphabet, layout_ml_alphabet, true)) return false;
if (!BuildKeyboard(swkbd_special, keys_special, layout_special, true)) return false;
if (!BuildKeyboard(swkbd_numpad, keys_numpad, layout_numpad, true)) return false;
char* text_cpy = NULL;
u32 text_cpy_len = 0;
if (max_len) {
// create a copy to check for changes against on exit
text_cpy = malloc(len + 1);
text_cpy_len = len;
if (!text_cpy) return false;
memcpy(text_cpy, text, len + 1);
}
// clear screens
ClearScreen(TOP_SCREEN, COLOR_STD_BG);
// set script colors
if (as_script) {
@ -1712,56 +1815,181 @@ bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
script_color_code = COLOR_TVCMD;
}
// find maximum line len
u32 llen_max = 0;
for (char* ptr = (char*) text; ptr < (text + len); ptr = line_seek(text, len, 0, ptr, 1)) {
u32 llen = line_len(text, len, 0, ptr, NULL);
if (llen > llen_max) llen_max = llen;
}
// find last allowed lines (ww and nonww)
char* llast_nww = line_seek(text, len, 0, text + len, -TV_NLIN_DISP);
char* llast_ww = line_seek(text, len, TV_LLEN_DISP, text + len, -TV_NLIN_DISP);
char* line0 = (char*) text;
int lcurr = 1;
int off_disp = 0;
for (; lcurr < (int) start; line0 = line_seek(text, len, 0, line0, 1), lcurr++);
bool crlf = is_crlf(text);
bool display_view_instructions = true;
const char* cursor = NULL;
const char* line0 = text;
int lcurr = 1; // Current line number
int off_disp_chars = 0; // non-word-wrapped offset
for (; lcurr < (int) start; line0 = line_seek_chars(text, len, 0, line0, 1), lcurr++);
while (true) {
// display text on screen
MemTextView(text, len, line0, off_disp, lcurr, ww, 0, as_script);
MemTextView(text, len, line0, off_disp_chars, lcurr, ww, 0, as_script, cursor);
// handle user input
u32 pad_state = InputWait(0);
char* line0_next = line0;
u32 step_ud = (pad_state & BUTTON_R1) ? TV_NLIN_DISP : 1;
u32 step_lr = (pad_state & BUTTON_R1) ? TV_LLEN_DISP : 1;
bool switched = (pad_state & BUTTON_R1);
if (pad_state & BUTTON_DOWN) line0_next = line_seek(text, len, ww, line0, step_ud);
else if (pad_state & BUTTON_UP) line0_next = line_seek(text, len, ww, line0, -step_ud);
else if (pad_state & BUTTON_RIGHT) off_disp += step_lr;
else if (pad_state & BUTTON_LEFT) off_disp -= step_lr;
else if (switched && (pad_state & BUTTON_X)) {
u64 lnext64 = ShowNumberPrompt(lcurr, STR_CURRENT_LINE_N_ENTER_NEW_LINE_BELOW, lcurr);
if (lnext64 && (lnext64 != (u64) -1)) line0_next = line_seek(text, len, 0, line0, (int) lnext64 - lcurr);
ShowString("%s", STR_TEXTVIEWER_CONTROLS_DETAILS);
} else if (switched && (pad_state & BUTTON_Y)) {
ww = ww ? 0 : TV_LLEN_DISP;
line0_next = line_seek(text, len, ww, line0, 0);
} else if (pad_state & (BUTTON_B|BUTTON_START)) break;
const char* line0_next = line0;
if (!cursor) { // view mode
if (display_view_instructions) {
ClearScreen(BOT_SCREEN, COLOR_STD_BG);
ShowString("%s", max_len ? STR_TEXTEDITOR_CONTROLS_DETAILS : STR_TEXTVIEWER_CONTROLS_DETAILS);
display_view_instructions = false;
}
// handle user input
u32 pad_state = InputWait(0);
u32 step_ud = (pad_state & BUTTON_R1) ? TV_NLIN_DISP : 1;
u32 step_lr = (pad_state & BUTTON_R1) ? TV_LLEN_DISP : 1;
bool switched = (pad_state & BUTTON_R1);
if (pad_state & BUTTON_DOWN) line0_next = line_seek_chars(text, len, ww, line0, step_ud);
else if (pad_state & BUTTON_UP) line0_next = line_seek_chars(text, len, ww, line0, -step_ud);
else if (pad_state & BUTTON_RIGHT) off_disp_chars += step_lr;
else if (pad_state & BUTTON_LEFT) off_disp_chars -= step_lr;
else if (max_len && pad_state & BUTTON_A) {
cursor = line0;
off_disp_chars = 0;
uppercase = 0;
swkbd = NULL;
swkbd_prev = NULL;
}
else if (switched && pad_state & BUTTON_X) {
u64 lnext64 = ShowNumberPrompt(lcurr, STR_CURRENT_LINE_N_ENTER_NEW_LINE_BELOW, lcurr);
if (lnext64 && (lnext64 != (u64) -1))
line0_next = line_seek_chars(text, len, 0, line_start(text, len, 0, line0), (int) lnext64 - lcurr);
ShowString("%s", STR_TEXTVIEWER_CONTROLS_DETAILS);
} else if (switched && pad_state & BUTTON_Y) {
ww = ww ? 0 : TV_LLEN_DISP;
line0_next = line_start(text, len, ww, line0);
} else if (pad_state & (BUTTON_B|BUTTON_START)) break;
} else { // edit mode
char key_pressed = ShowMultiLineKeyboard(swkbd_alphabet, swkbd_special, swkbd_numpad, &swkbd, &swkbd_prev, &uppercase);
char key_character = 0;
bool switched = HID_ReadState() & BUTTON_R1;
if (key_pressed == KEY_ESCAPE) {
cursor = NULL;
display_view_instructions = true;
} else if (key_pressed == KEY_DOWN) {
const char* cursor_line_start = line_start(text, len, ww, cursor);
u32 cursor_chars_from_line_start = chars_between_pointers(cursor_line_start, cursor);
cursor = line_seek_chars(text, len, ww, cursor_line_start, switched ? TV_NLIN_DISP : 1);
cursor = line_start(text, len, ww, cursor);
const char* next_line_start = line_seek_chars(text, len, ww, cursor, 1);
for (u32 i = 0; GetNextChar(cursor) < next_line_start && i < cursor_chars_from_line_start; IncChar(&cursor), ++i);
} else if (key_pressed == KEY_UP) {
const char* cursor_line_start = line_start(text, len, ww, cursor);
u32 cursor_chars_from_line_start = chars_between_pointers(cursor_line_start, cursor);
cursor = line_seek_chars(text, len, ww, cursor_line_start, -(switched ? TV_NLIN_DISP : 1));
const char* next_line_start = line_seek_chars(text, len, ww, cursor, 1);
for (u32 i = 0; GetNextChar(cursor) < next_line_start && i < cursor_chars_from_line_start; IncChar(&cursor), ++i);
} else if (key_pressed == KEY_RIGHT) {
if (switched) {
const char* cursor_line_start = line_start(text, len, ww, cursor);
const char* next_line_start = line_seek_chars(text, len, ww, cursor_line_start, 1);
if (next_line_start == text + len && (!ww || chars_between_pointers(cursor_line_start, next_line_start) != TV_LLEN_DISP)) IncChar(&next_line_start);
while (GetNextChar(cursor) < next_line_start && !is_newline(cursor)) IncChar(&cursor);
}
else if (cursor < text + len) IncChar(&cursor);
} else if (key_pressed == KEY_LEFT) {
if (switched) {
const char* cursor_line_start = line_start(text, len, ww, cursor);
while (cursor > cursor_line_start) DecChar(&cursor);
}
else if (cursor > text) DecChar(&cursor);
} else if (key_pressed == KEY_BKSPC) {
if (cursor > text) {
u32 size = GetPrevCharSize(cursor);
memmove((char *) cursor - size, cursor, text + len - cursor + 1);
len -= size;
cursor -= size;
}
} else if (key_pressed == KEY_UNICODE) {
if (cursor >= text + 4 && cursor <= text + len) {
u16 codepoint = 0;
for (const char *c = cursor - 4; c < cursor; c++) {
if ((*c >= '0' && *c <= '9') || (*c >= 'A' && *c <= 'F') || (*c >= 'a' && *c <= 'f')) {
codepoint <<= 4;
codepoint |= *c - (*c <= '9' ? '0' : ((*c <= 'F' ? 'A' : 'a') - 10));
} else {
codepoint = 0;
break;
}
}
if (codepoint != 0) {
char character[5] = {0};
u16 input[2] = {codepoint, 0};
utf16_to_utf8((u8*) character, input, 4, 1);
u32 char_size = GetCharSize(character);
memmove((char *) cursor - 4 + char_size, cursor, text + len - cursor + 1);
memcpy((char *) cursor - 4, character, char_size);
cursor -= 4 - char_size;
len -= 4 - char_size;
}
}
} else if (key_pressed == KEY_ENTER) key_character = crlf ? '\r' : '\n';
else if (key_pressed < 0x80) key_character = key_pressed;
if (key_character && len + (key_character == '\r' ? 1 : 0) < max_len) {
if (uppercase == 1) {
uppercase = 0;
}
memmove((char *) cursor + 1, cursor, text + len++ - cursor + 1);
*((char *) cursor++) = key_character;
if (key_character == '\r') {
memmove((char *) cursor + 1, cursor, text + len++ - cursor + 1);
*((char *) cursor++) = '\n';
}
}
if (cursor && !ww) {
const char* cursor_line_start = line_start(text, len, ww, cursor);
u32 cursor_chars_from_line_start = chars_between_pointers(cursor_line_start, cursor);
if (cursor_chars_from_line_start < off_disp_chars + strlen(al_str)) off_disp_chars = cursor_chars_from_line_start - strlen(al_str);
if (cursor_chars_from_line_start >= off_disp_chars + TV_LLEN_DISP - strlen(ar_str)) off_disp_chars = cursor_chars_from_line_start + strlen(ar_str) - TV_LLEN_DISP + 1;
}
while (cursor && cursor < line0_next) line0_next = line_seek_chars(text, len, ww, line0_next, -1);
while (cursor && line0_next < line_seek_chars(text, len, ww, GetNextChar(cursor), -TV_NLIN_DISP)) line0_next = line_seek_chars(text, len, ww, line0_next, 1);
}
// find last allowed lines (ww and nonww)
const char* llast_nww = line_seek_chars(text, len, 0, text + len + 1, -TV_NLIN_DISP);
const char* llast_ww = line_seek_chars(text, len, TV_LLEN_DISP, text + len + 1, -TV_NLIN_DISP);
// check for problems, apply changes
if (!ww && (line0_next > llast_nww)) line0_next = llast_nww;
else if (ww && (line0_next > llast_ww)) line0_next = llast_ww;
if (line0_next < line0) { // fix line number for decrease
do if (*(--line0) == '\n') lcurr--;
do {
DecChar(&line0);
if (is_newline(line0)) lcurr--;
}
while (line0 > line0_next);
} else { // fix line number for increase / same
for (; line0_next > line0; line0++)
if (*line0 == '\n') lcurr++;
for (; line0_next > line0; IncChar(&line0)) if (is_newline(line0)) lcurr++;
}
if (off_disp + TV_LLEN_DISP > llen_max) off_disp = llen_max - TV_LLEN_DISP;
if ((off_disp < 0) || ww) off_disp = 0;
// find maximum line length
u32 llen_max = 0;
for (const char* ptr = text; ptr < text + len; ptr = line_seek_chars(text, len, 0, ptr, 1)) {
u32 llen = line_len_chars(text, len, 0, ptr, NULL) + 1;
if (llen > llen_max) llen_max = llen;
}
if (off_disp_chars + TV_LLEN_DISP > llen_max) off_disp_chars = llen_max - TV_LLEN_DISP;
if (off_disp_chars < 0 || ww) off_disp_chars = 0;
}
// check for user edits
if (text_cpy) {
if (save_path) {
bool diffs = false;
if (len != text_cpy_len) diffs = true;
else for (u32 i = 0; i < len; ++i) if (text[i] != text_cpy[i]) { diffs = true; break; }
if (diffs && ShowPrompt(true, "%s", STR_TEXT_EDITS_SAVE_CHANGES) && !FileSetData(save_path, text, len, 0, true))
ShowPrompt(false, "%s", STR_FAILED_WRITING_TO_FILE);
}
free(text_cpy);
}
// clear screens
@ -1774,7 +2002,7 @@ bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script) {
// (misses safety checks for wider compatibility)
bool MemToCViewer(const char* text, u32 len, const char* title) {
const u32 max_captions = 24; // we assume this is enough
char* captions[max_captions];
const char* captions[max_captions];
u32 lineno[max_captions];
u32 ww = TV_LLEN_DISP;
@ -1786,13 +2014,13 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
// clear screens / view start of readme on top
ClearScreenF(true, true, COLOR_STD_BG);
MemTextView(text, len, (char*) text, 0, 1, ww, 0, false);
MemTextView(text, len, text, 0, 1, ww, 0, false, NULL);
// parse text for markdown captions
u32 n_captions = 0;
char* ptr = (char*) text;
const char* ptr = text;
for (u32 lno = 1;; lno++) {
char* ptr_next = line_seek(text, len, 0, ptr, 1);
const char* ptr_next = line_seek_chars(text, len, 0, ptr, 1);
if (ptr == ptr_next) break;
if (*ptr == '#') {
captions[n_captions] = ptr;
@ -1812,7 +2040,7 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
y0 += 2 * (FONT_HEIGHT_EXT + (2*TV_VPAD));
for (u32 i = 0; (i < n_captions) && (y0 < SCREEN_HEIGHT); i++) {
u32 text_color = ((int) i == cursor) ? COLOR_TVRUN : COLOR_TVTEXT;
char* caption = captions[i];
const char* caption = captions[i];
u32 len = 0;
u32 lvl = 0;
for (; *caption == '#'; caption++, lvl++);
@ -1826,16 +2054,16 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
// handle user input
u32 pad_state = InputWait(0);
if ((cursor >= 0) && (pad_state & BUTTON_A)) {
if (!MemTextViewer(text, len, lineno[cursor], false)) return false;
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
if (!MemTextViewer(text, len, lineno[cursor], false, 0, NULL)) return false;
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false, NULL);
} else if (pad_state & BUTTON_B) {
break;
} else if (pad_state & BUTTON_UP) {
cursor = (cursor <= 0) ? ((int) n_captions - 1) : cursor - 1;
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false, NULL);
} else if (pad_state & BUTTON_DOWN) {
if (++cursor >= (int) n_captions) cursor = 0;
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false);
MemTextView(text, len, captions[cursor], 0, lineno[cursor], ww, 0, false, NULL);
}
}
@ -1848,18 +2076,20 @@ bool MemToCViewer(const char* text, u32 len, const char* title) {
bool FileTextViewer(const char* path, bool as_script) {
// load text file (completely into memory)
// text file needs to fit inside the STD_BUFFER_SIZE
u32 flen, len;
size_t fileSize = FileGetSize(path);
if (fileSize >= STD_BUFFER_SIZE) {
ShowPrompt(false, STR_ERROR_TEXT_FILE_TOO_BIG, fileSize, STD_BUFFER_SIZE - 1);
return false;
}
char* text = malloc(STD_BUFFER_SIZE);
if (!text) return false;
flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0);
u32 flen = FileGetData(path, text, STD_BUFFER_SIZE - 1, 0);
text[flen] = '\0';
len = (ptrdiff_t)memchr(text, '\0', flen + 1) - (ptrdiff_t)text;
// let MemTextViewer take over
bool result = MemTextViewer(text, len, 1, as_script);
bool result = MemTextViewer(text, flen, 1, as_script, STD_BUFFER_SIZE - 1, path);
free(text);
return result;
@ -1968,11 +2198,11 @@ bool ExecuteGM9Script(const char* path_script) {
}
if (show_preview) {
if (lno <= (TV_NLIN_DISP/2)) {
MemTextView(script, script_size, script, 0, 1, 0, lno, true);
MemTextView(script, script_size, script, 0, 1, 0, lno, true, NULL);
} else {
char* ptr_view = line_seek(script, script_size, 0, ptr, -(TV_NLIN_DISP/2));
const char* ptr_view = line_seek_chars(script, script_size, 0, ptr, -(TV_NLIN_DISP/2));
u32 lno_view = lno - (TV_NLIN_DISP/2);
MemTextView(script, script_size, ptr_view, 0, lno_view, 0, lno, true);
MemTextView(script, script_size, ptr_view, 0, lno_view, 0, lno, true, NULL);
}
}
}

View File

@ -8,7 +8,7 @@
bool for_handler(char* path, const char* dir, const char* pattern, bool recursive);
bool ValidateText(const char* text, u32 size);
bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script);
bool MemTextViewer(const char* text, u32 len, u32 start, bool as_script, u32 max_len, const char* save_path);
bool MemToCViewer(const char* text, u32 len, const char* title);
bool FileTextViewer(const char* path, bool as_script);
bool ExecuteGM9Script(const char* path_script);

View File

@ -191,7 +191,7 @@
"CALCULATE_SHA256": "Calculate SHA-256",
"CALCULATE_SHA1": "Calculate SHA-1",
"SHOW_FILE_INFO": "Show file info",
"SHOW_IN_TEXTVIEWER": "Show in Textviewer",
"SHOW_IN_TEXTVIEWER": "Show in Text Editor",
"CALCULATE_CMAC": "Calculate CMAC",
"COPY_TO_OUT": "Copy to %s",
"DUMP_TO_OUT": "Dump to %s",
@ -778,7 +778,7 @@
"SCRIPTERR_APPLY_IPS_FAILD": "apply IPS failed",
"SCRIPTERR_APPLY_BPS_FAILED": "apply BPS failed",
"SCRIPTERR_APPLY_BPM_FAILED": "apply BPM failed",
"SCRIPTERR_TEXTVIEWER_FAILED": "textviewer failed",
"SCRIPTERR_TEXTVIEWER_FAILED": "text editor failed",
"SCRIPTERR_BAD_DUMPSIZE": "bad dumpsize",
"SCRIPTERR_CART_INIT_FAIL": "cart init fail",
"SCRIPTERR_CART_DUMP_FAILED": "cart dump failed",
@ -792,7 +792,11 @@
"SCRIPTERR_UNCLOSED_CONDITIONAL": "unclosed conditional",
"SCRIPTERR_ERROR_MESSAGE_FAIL": "error message fail",
"ERROR_INVALID_TEXT_DATA": "Error: Invalid text data",
"ERROR_TEXT_FILE_TOO_BIG": "Error: Text file is too large.\nText file size is %u bytes.\nMax file size is %i bytes.",
"TEXTVIEWER_CONTROLS_DETAILS": "Textviewer Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nB - Exit\n",
"TEXTEDITOR_CONTROLS_DETAILS": "Text Editor Controls:\n \n↑↓→←(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nA - Enter edit mode\nB - Exit\n",
"TEXTEDITOR_CONTROLS_KEYBOARD": "Text Editor Controls:\n \n↑↓→←(+R) - Move cursor\nY - Caps / Capslock\nX - Delete char\nA - Insert newline\nB - Enter view mode\n",
"TEXT_EDITS_SAVE_CHANGES": "You made text edits.\nWrite changes to file?",
"CURRENT_LINE_N_ENTER_NEW_LINE_BELOW": "Current line: %i\nEnter new line below.",
"PREVIEW_DISABLED": "(preview disabled)",
"PATH_LINE_N_ERR_LINE": "%s\nline %lu: %s\n%s",

View File

@ -312,7 +312,7 @@ verify S:/firm1.bin
# applybpm 0:/example/patch.bpm 0:/data/originalfolder 0:/game/moddedfolder
# 'textview' COMMAND
# This will show a text file on screen, in a dedicated text viewer. Size restrictions apply (max 1MiB)
# This will show a text file on screen, in a dedicated text editor. Size restrictions apply (max 1MiB)
# textview 0:/sometext.txt
# 'boot' COMMAND