mirror of
https://github.com/d0k3/GodMode9.git
synced 2026-05-30 22:36:55 +00:00
Adding text editor functionality to text viewer.
This commit is contained in:
parent
1bbd8193d1
commit
d65a5b6771
@ -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**
|
||||
|
||||
@ -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 button positions
|
||||
TouchBox* tb = swkbd;
|
||||
if (!multi_line) {
|
||||
// set up the textbox
|
||||
TouchBox* txtbox = swkbd;
|
||||
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;
|
||||
}
|
||||
|
||||
// set button positions
|
||||
TouchBox* tb = swkbd + 1;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -386,3 +391,37 @@ 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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
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);
|
||||
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)) {
|
||||
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(text, len, 0, line0, (int) lnext64 - 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)) {
|
||||
} else if (switched && pad_state & BUTTON_Y) {
|
||||
ww = ww ? 0 : TV_LLEN_DISP;
|
||||
line0_next = line_seek(text, len, ww, line0, 0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user