Skip to content

Commit ab681aa

Browse files
committed
Adding text editor functionality to text viewer.
1 parent 7b6b478 commit ab681aa

File tree

11 files changed

+503
-186
lines changed

11 files changed

+503
-186
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ With the possibilites GodMode9 provides, not everything may be obvious at first
116116
* __Search drives and folders__: Just press R+A on the drive / folder you want to search.
117117
* __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.
118118
* __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.
119-
* __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.
119+
* __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.
120120
* __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.
121121
* __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.
122122
* __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.
@@ -203,6 +203,7 @@ This tool would not have been possible without the help of numerous people. Than
203203
* **Lilith Valentine** for testing and helpful advice
204204
* **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator)
205205
* **Amazingmax fonts** for the Amazdoom font
206+
* **nevumx** for turning the text viewer into a text editor with UTF-8 and LF/CRLF support
206207
* The fine folks on **the official GodMode9 IRC channel and Discord server**
207208
* The fine folks on **freenode #Cakey**
208209
* All **[3dbrew.org](https://www.3dbrew.org/wiki/Main_Page) editors**

arm9/source/common/swkbd.c

+69-30
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
#include "language.h"
44
#include "swkbd.h"
55
#include "timer.h"
6-
#include "hid.h"
76
#include "utf.h"
87

98

@@ -12,7 +11,7 @@ static inline char to_uppercase(char c) {
1211
return c;
1312
}
1413

15-
static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
14+
bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout, bool multi_line) {
1615
// count # of rows
1716
u32 n_rows = 0;
1817
for (u32 i = 0;; i++) {
@@ -26,16 +25,18 @@ static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
2625
u32 height = (n_rows) ? (n_rows * SWKBD_STDKEY_HEIGHT) + ((n_rows-1) * SWKDB_KEY_SPACING) : 0;
2726
u32 p_y = SCREEN_HEIGHT - height - SWKBD_STDKEY_HEIGHT - SWKDB_KEY_SPACING;
2827

29-
// set up the textbox
30-
TouchBox* txtbox = swkbd;
31-
txtbox->x = (SCREEN_WIDTH_BOT - SWKBD_TEXTBOX_WIDTH) / 2;
32-
txtbox->y = p_y - 30;
33-
txtbox->w = SWKBD_TEXTBOX_WIDTH;
34-
txtbox->h = 30;
35-
txtbox->id = KEY_TXTBOX;
36-
3728
// set button positions
38-
TouchBox* tb = swkbd + 1;
29+
TouchBox* tb = swkbd;
30+
if (!multi_line) {
31+
// set up the textbox
32+
TouchBox* txtbox = tb++;
33+
txtbox->x = (SCREEN_WIDTH_BOT - SWKBD_TEXTBOX_WIDTH) / 2;
34+
txtbox->y = p_y - 30;
35+
txtbox->w = SWKBD_TEXTBOX_WIDTH;
36+
txtbox->h = 30;
37+
txtbox->id = KEY_TXTBOX;
38+
}
39+
3940
for (u32 l = 0, k = 0; layout[l] != 0; ) {
4041
// calculate width of current row
4142
u32 n_keys = layout[l++];
@@ -70,8 +71,9 @@ static bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout) {
7071
return true;
7172
}
7273

73-
static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase) {
74+
static void DrawKey(const TouchBox* key, const bool pressed, const u32 uppercase, const bool multi_line) {
7475
const char* keystrs[] = { SWKBD_KEYSTR };
76+
const char* ml_keystrs[] = { SWKBD_ML_KEYSTR };
7577
const u32 color = (pressed) ? COLOR_SWKBD_PRESSED :
7678
(key->id == KEY_ENTER) ? COLOR_SWKBD_ENTER :
7779
((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
8183
if (key->id == KEY_TXTBOX) return;
8284

8385
char keystr[16];
84-
if (key->id >= 0x80) snprintf(keystr, sizeof(keystr), "%s", keystrs[key->id - 0x80]);
86+
if (key->id >= 0x80) snprintf(keystr, sizeof(keystr), "%s", (multi_line ? ml_keystrs : keystrs)[key->id - 0x80]);
8587
else {
8688
keystr[0] = (uppercase) ? to_uppercase(key->id) : key->id;
8789
keystr[1] = 0;
@@ -111,12 +113,12 @@ static void DrawKeyBoardBox(TouchBox* swkbd, u32 color) {
111113
DrawRectangle(BOT_SCREEN, x0-1, y0-1, x1-x0+2, y1-y0+2, color);
112114
}
113115

114-
static void DrawKeyBoard(TouchBox* swkbd, const u32 uppercase) {
116+
static void DrawKeyBoard(TouchBox* swkbd, const u32 uppercase, const bool multi_line) {
115117
// we need to make sure to skip the textbox here(first entry)
116118

117119
// draw keyboard
118-
for (TouchBox* tb = swkbd + 1; tb->id != 0; tb++) {
119-
DrawKey(tb, false, uppercase);
120+
for (TouchBox* tb = swkbd + (multi_line ? 0 : 1); tb->id != 0; tb++) {
121+
DrawKey(tb, false, uppercase, multi_line);
120122
}
121123
}
122124

@@ -209,21 +211,24 @@ static void MoveTextBoxCursor(const TouchBox* txtbox, const char* inputstr, cons
209211
}
210212
}
211213

212-
static char KeyboardWait(TouchBox* swkbd, bool uppercase) {
214+
static char KeyboardWait(TouchBox* swkbd, bool uppercase, const bool multi_line) {
213215
u32 id = 0;
214216
u16 x, y;
215217

216218
// wait for touch input (handle key input, too)
217219
while (true) {
218220
u32 pressed = InputWait(0);
219-
if (pressed & BUTTON_B) return KEY_ESCAPE;
221+
if (multi_line && pressed & TIMEOUT_HID) return 0;
222+
else if (pressed & BUTTON_B) return KEY_ESCAPE;
220223
else if (pressed & BUTTON_A) return KEY_ENTER;
221224
else if (pressed & BUTTON_X) return KEY_BKSPC;
222-
else if (pressed & BUTTON_Y) return KEY_INSERT;
223-
else if (pressed & BUTTON_R1) return KEY_CAPS;
225+
else if (pressed & BUTTON_Y) return multi_line ? KEY_CAPS : KEY_INSERT;
226+
else if (!multi_line && pressed & BUTTON_R1) return KEY_CAPS;
224227
else if (pressed & BUTTON_RIGHT) return KEY_RIGHT;
225228
else if (pressed & BUTTON_LEFT) return KEY_LEFT;
226-
else if (pressed & BUTTON_SELECT) return KEY_SWITCH;
229+
else if (multi_line && pressed & BUTTON_UP) return KEY_UP;
230+
else if (multi_line && pressed & BUTTON_DOWN) return KEY_DOWN;
231+
else if (!multi_line && pressed & BUTTON_SELECT) return KEY_SWITCH;
227232
else if (pressed & BUTTON_TOUCH) break;
228233
}
229234

@@ -232,9 +237,9 @@ static char KeyboardWait(TouchBox* swkbd, bool uppercase) {
232237
const TouchBox* tb = TouchBoxGet(&id, x, y, swkbd, 0);
233238
if (tb) {
234239
if (id == KEY_TXTBOX) break; // immediately break on textbox
235-
DrawKey(tb, true, uppercase);
240+
DrawKey(tb, true, uppercase, multi_line);
236241
while(HID_ReadTouchState(&x, &y) && (tb == TouchBoxGet(NULL, x, y, swkbd, 0)));
237-
DrawKey(tb, false, uppercase);
242+
DrawKey(tb, false, uppercase, multi_line);
238243
}
239244
}
240245

@@ -261,9 +266,9 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
261266
}
262267

263268
// generate keyboards
264-
if (!BuildKeyboard(swkbd_alphabet, keys_alphabet, layout_alphabet)) return false;
265-
if (!BuildKeyboard(swkbd_special, keys_special, layout_special)) return false;
266-
if (!BuildKeyboard(swkbd_numpad, keys_numpad, layout_numpad)) return false;
269+
if (!BuildKeyboard(swkbd_alphabet, keys_alphabet, layout_alphabet, false)) return false;
270+
if (!BuildKeyboard(swkbd_special, keys_special, layout_special, false)) return false;
271+
if (!BuildKeyboard(swkbd_numpad, keys_numpad, layout_numpad, false)) return false;
267272

268273
// (instructional) text
269274
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, ...) {
292297
// draw keyboard if required
293298
if (swkbd != swkbd_prev) {
294299
DrawKeyBoardBox(swkbd, COLOR_SWKBD_BOX);
295-
DrawKeyBoard(swkbd, uppercase);
300+
DrawKeyBoard(swkbd, uppercase, false);
296301
DrawTextBox(textbox, inputstr, cursor, &scroll);
297302
swkbd_prev = swkbd;
298303
}
299304

300305
// handle user input
301-
char key = KeyboardWait(swkbd, uppercase);
306+
char key = KeyboardWait(swkbd, uppercase, false);
302307
if (key == KEY_INSERT) key = ' '; // impromptu replacement
303308
if (key == KEY_TXTBOX) {
304309
MoveTextBoxCursor(textbox, inputstr, max_size, &cursor, &scroll);
305310
} else if (key == KEY_CAPS) {
306311
uppercase = (uppercase + 1) % 3;
307-
DrawKeyBoard(swkbd, uppercase);
312+
DrawKeyBoard(swkbd, uppercase, false);
308313
continue;
309314
} else if (key == KEY_ENTER) {
310315
ret = true;
@@ -375,7 +380,7 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
375380
}
376381
if (uppercase == 1) {
377382
uppercase = 0;
378-
DrawKeyBoard(swkbd, uppercase);
383+
DrawKeyBoard(swkbd, uppercase, false);
379384
}
380385
}
381386

@@ -385,4 +390,38 @@ bool ShowKeyboard(char* inputstr, const u32 max_size, const char *format, ...) {
385390

386391
ClearScreen(BOT_SCREEN, COLOR_STD_BG);
387392
return ret;
393+
}
394+
395+
char ShowMultiLineKeyboard(TouchBox* swkbd_alphabet, TouchBox* swkbd_special, TouchBox* swkbd_numpad, TouchBox** swkbd, TouchBox** swkbd_prev, u32* uppercase) {
396+
if (!*swkbd) {
397+
u32 str_width = GetDrawStringWidth(STR_TEXTEDITOR_CONTROLS_KEYBOARD);
398+
if (str_width < (24 * FONT_WIDTH_EXT)) str_width = 24 * FONT_WIDTH_EXT;
399+
u32 str_x = (str_width >= SCREEN_WIDTH_BOT) ? 0 : (SCREEN_WIDTH_BOT - str_width) / 2;
400+
ClearScreen(BOT_SCREEN, COLOR_STD_BG);
401+
DrawStringF(BOT_SCREEN, str_x, 20, COLOR_STD_FONT, COLOR_STD_BG, "%s", STR_TEXTEDITOR_CONTROLS_KEYBOARD);
402+
*swkbd = swkbd_alphabet;
403+
}
404+
405+
// handle keyboard
406+
while (true) {
407+
// draw keyboard if required
408+
if (*swkbd != *swkbd_prev) {
409+
DrawKeyBoardBox(*swkbd, COLOR_SWKBD_BOX);
410+
DrawKeyBoard(*swkbd, *uppercase, true);
411+
*swkbd_prev = *swkbd;
412+
}
413+
414+
// handle user input
415+
char key = KeyboardWait(*swkbd, *uppercase, true);
416+
if (key == KEY_ALPHA) {
417+
*swkbd = swkbd_alphabet;
418+
} else if (key == KEY_SPECIAL) {
419+
*swkbd = swkbd_special;
420+
} else if (key == KEY_NUMPAD) {
421+
*swkbd = swkbd_numpad;
422+
} else if (key == KEY_CAPS) {
423+
*uppercase = (*uppercase + 1) % 3;
424+
DrawKeyBoard(*swkbd, *uppercase, true);
425+
} else return key;
426+
}
388427
}

arm9/source/common/swkbd.h

+27-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include "common.h"
4+
#include "hid.h"
45
#include "ui.h"
56
#include "touchcal.h"
67

@@ -20,11 +21,15 @@ enum {
2021
KEY_ESCAPE = 0x8A,
2122
KEY_SWITCH = 0x8B,
2223
KEY_UNICODE = 0x8C,
24+
KEY_UP = 0x8D,
25+
KEY_DOWN = 0x8E,
2326
KEY_TXTBOX = 0xFF
2427
};
2528

2629
// special key strings
27-
#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "←", "→", "ESC", "SWITCH", "U+"
30+
#define SWKBD_KEYSTR "", "DEL", "INS", "SUBMIT", "CAPS", "#$@", "123", "ABC", "←", "→", "ESC", "SWITCH", "U+", "↑", "↓"
31+
// multiline special key stings
32+
#define SWKBD_ML_KEYSTR "", "DEL", "INS", "ENTER", "CAPS", "#$@", "123", "ABC", "←", "→", "ESC", "SWITCH", "U+", "↑", "↓"
2833

2934
#define COLOR_SWKBD_NORMAL COLOR_GREY
3035
#define COLOR_SWKBD_PRESSED COLOR_LIGHTGREY
@@ -45,6 +50,13 @@ enum {
4550
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '_', '#', '!', \
4651
KEY_CAPS, ' ', KEY_NUMPAD, KEY_SPECIAL, KEY_LEFT, KEY_RIGHT
4752

53+
#define SWKBD_KEYS_ML_ALPHABET \
54+
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '+', '-', KEY_BKSPC, \
55+
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '&', KEY_ENTER, \
56+
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '(', ')', '[', ']', \
57+
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '_', '#', '!', \
58+
KEY_CAPS, ' ', KEY_NUMPAD, KEY_SPECIAL, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN
59+
4860
#define SWKBD_KEYS_SPECIAL \
4961
'(', ')', '{', '}', '[', ']', \
5062
'.', ',', '?', '!', '`', '\'', \
@@ -53,9 +65,9 @@ enum {
5365
KEY_ALPHA, ' ', KEY_BKSPC
5466

5567
#define SWKBD_KEYS_NUMPAD \
56-
'7', '8', '9', 'F', 'E', \
57-
'4', '5', '6', 'D', 'C', \
58-
'3', '2', '1', 'B', 'A', \
68+
'7', '8', '9', 'E', 'F', \
69+
'4', '5', '6', 'C', 'D', \
70+
'1', '2', '3', 'A', 'B', \
5971
'0', '.', '_', KEY_LEFT, KEY_RIGHT, \
6072
KEY_ALPHA, KEY_UNICODE, ' ', KEY_BKSPC
6173

@@ -68,12 +80,20 @@ enum {
6880
6, 32, 123, 32, 32, 18, 18, 0, \
6981
0
7082

83+
#define SWKBD_LAYOUT_ML_ALPHABET \
84+
13, 32, 0, \
85+
12, 51, 0, \
86+
13, 0, \
87+
12, 0, \
88+
8, 32, 85, 32, 32, 18, 18, 18, 18, 0, \
89+
0
90+
7191
#define SWKBD_LAYOUT_SPECIAL \
7292
6, 0, \
7393
6, 0, \
7494
6, 0, \
7595
6, 0, \
76-
3, 32, 46, 32, 0, \
96+
3, 32, 47, 32, 0, \
7797
0
7898

7999
#define SWKBD_LAYOUT_NUMPAD \
@@ -87,3 +107,5 @@ enum {
87107

88108
#define ShowKeyboardOrPrompt (TouchIsCalibrated() ? ShowKeyboard : ShowStringPrompt)
89109
bool PRINTF_ARGS(3) ShowKeyboard(char* inputstr, u32 max_size, const char *format, ...);
110+
bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout, bool multi_line);
111+
char ShowMultiLineKeyboard(TouchBox* swkbd_alphabet, TouchBox* swkbd_special, TouchBox* swkbd_numpad, TouchBox** swkbd, TouchBox** swkbd_prev, u32* uppercase);

arm9/source/common/ui.c

+23-11
Original file line numberDiff line numberDiff line change
@@ -489,22 +489,34 @@ u32 GetDrawStringHeight(const char* str) {
489489
return height;
490490
}
491491

492-
u32 GetCharSize(const char* str) {
493-
const char *start = str;
494-
do {
495-
str++;
496-
} while ((*str & 0xC0) == 0x80);
492+
static inline bool IsIntermediateByte(const char* chr) {
493+
return (*chr & 0xC0) == 0x80 || (chr[-1] == '\r' && chr[0] == '\n');
494+
}
495+
496+
const char* GetNextChar(const char* chr) {
497+
do ++chr; while (IsIntermediateByte(chr));
498+
return chr;
499+
}
500+
501+
const char* GetPrevChar(const char* chr) {
502+
do --chr; while (IsIntermediateByte(chr));
503+
return chr;
504+
}
497505

498-
return str - start;
506+
u32 GetCharSize(const char* str) {
507+
return GetNextChar(str) - str;
499508
}
500509

501510
u32 GetPrevCharSize(const char* str) {
502-
const char *start = str;
503-
do {
504-
str--;
505-
} while ((*str & 0xC0) == 0x80);
511+
return str - GetPrevChar(str);
512+
}
513+
514+
void IncChar(const char** chr) {
515+
*chr = GetNextChar(*chr);
516+
}
506517

507-
return start - str;
518+
void DecChar(const char** chr) {
519+
*chr = GetPrevChar(*chr);
508520
}
509521

510522
u32 GetDrawStringWidth(const char* str) {

arm9/source/common/ui.h

+4
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ void PRINTF_ARGS(4) DrawStringCenter(u16 *screen, u32 color, u32 bgcolor, const
7070

7171
u32 GetCharSize(const char* str);
7272
u32 GetPrevCharSize(const char* str);
73+
const char* GetNextChar(const char* chr);
74+
const char* GetPrevChar(const char* chr);
75+
void IncChar(const char** chr);
76+
void DecChar(const char** chr);
7377

7478
u32 GetDrawStringHeight(const char* str);
7579
u32 GetDrawStringWidth(const char* str);

arm9/source/godmode.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -1225,7 +1225,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
12251225
int n_opt = 0;
12261226
int special = (special_opt) ? ++n_opt : -1;
12271227
int hexviewer = ++n_opt;
1228-
int textviewer = (filetype & TXT_GENERIC) ? ++n_opt : -1;
1228+
int textviewer = (filetype & TXT_GENERIC || FileGetSize(file_path) == 0) ? ++n_opt : -1;
12291229
int calcsha256 = ++n_opt;
12301230
int calcsha1 = ++n_opt;
12311231
int calccmac = (CheckCmacPath(file_path) == 0) ? ++n_opt : -1;
@@ -1316,6 +1316,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
13161316
}
13171317
else if (user_select == textviewer) { // -> show in text viewer
13181318
FileTextViewer(file_path, scriptable);
1319+
GetDirContents(current_dir, current_path);
13191320
return 0;
13201321
}
13211322
else if (user_select == calcsha256) { // -> calculate SHA-256
@@ -2340,7 +2341,7 @@ u32 HomeMoreMenu(char* current_path) {
23402341
char* sysinfo_txt = (char*) malloc(STD_BUFFER_SIZE);
23412342
if (!sysinfo_txt) return 1;
23422343
MyriaSysinfo(sysinfo_txt);
2343-
MemTextViewer(sysinfo_txt, strnlen(sysinfo_txt, STD_BUFFER_SIZE), 1, false);
2344+
MemTextViewer(sysinfo_txt, strnlen(sysinfo_txt, STD_BUFFER_SIZE), 1, false, 0, NULL);
23442345
free(sysinfo_txt);
23452346
return 0;
23462347
}

0 commit comments

Comments
 (0)