Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding text editor functionality to text viewer. #868

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open to "Text Editor"/"Texteditor"/"Hex Editor"/"Hexeditor" guidance here...

* __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.
Expand Down Expand Up @@ -203,6 +203,7 @@ This tool would not have been possible without the help of numerous people. Than
* **Lilith Valentine** for testing and helpful advice
* **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator)
* **Amazingmax fonts** for the Amazdoom font
* **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**
Expand Down
99 changes: 69 additions & 30 deletions arm9/source/common/swkbd.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include "language.h"
#include "swkbd.h"
#include "timer.h"
#include "hid.h"
#include "utf.h"


Expand All @@ -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++) {
Expand All @@ -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++];
Expand Down Expand Up @@ -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 :
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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_CLIP : 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;
}

Expand All @@ -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);
}
}

Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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(const char* instructions, TouchBox* swkbd_alphabet, TouchBox* swkbd_special, TouchBox* swkbd_numpad, TouchBox** swkbd, TouchBox** swkbd_prev, u32* uppercase) {
if (instructions) {
u32 str_width = GetDrawStringWidth(instructions);
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", instructions);
*swkbd_prev = NULL; // Force keyboard redraw
}

// 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;
}
}
35 changes: 30 additions & 5 deletions arm9/source/common/swkbd.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "common.h"
#include "hid.h"
#include "ui.h"
#include "touchcal.h"

Expand All @@ -20,11 +21,16 @@ enum {
KEY_ESCAPE = 0x8A,
KEY_SWITCH = 0x8B,
KEY_UNICODE = 0x8C,
KEY_UP = 0x8D,
KEY_DOWN = 0x8E,
KEY_CLIP = 0x8F,
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
Expand All @@ -45,6 +51,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 \
'(', ')', '{', '}', '[', ']', \
'.', ',', '?', '!', '`', '\'', \
Expand All @@ -53,9 +66,9 @@ enum {
KEY_ALPHA, ' ', KEY_BKSPC

#define SWKBD_KEYS_NUMPAD \
'7', '8', '9', 'F', 'E', \
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the arrangement of these to be more consistent with e.g. calculators.

'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

Expand All @@ -68,12 +81,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, \
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was bothering me as well.

3, 32, 47, 32, 0, \
0

#define SWKBD_LAYOUT_NUMPAD \
Expand All @@ -87,3 +108,7 @@ enum {

#define ShowKeyboardOrPrompt (TouchIsCalibrated() ? ShowKeyboard : ShowStringPrompt)
bool PRINTF_ARGS(3) ShowKeyboard(char* inputstr, u32 max_size, const char *format, ...);

// Exposing this to prevent rebuilds between keypresses in the ShowMultiLineKeyboard calling functions.
bool BuildKeyboard(TouchBox* swkbd, const char* keys, const u8* layout, bool multi_line);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exposed to prevent rebuilds between keypresses in the MemTextViewer function.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of these Github comments should probably be code comments instead

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BlueRaja done. I went through and added code comments where I felt they were relevant. If you have any other concerns, please let me know. 😁

char ShowMultiLineKeyboard(const char* instructions, TouchBox* swkbd_alphabet, TouchBox* swkbd_special, TouchBox* swkbd_numpad, TouchBox** swkbd, TouchBox** swkbd_prev, u32* uppercase);
34 changes: 23 additions & 11 deletions arm9/source/common/ui.c
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

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;
}

return str - start;
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);
}

void IncChar(const char** chr) {
*chr = GetNextChar(*chr);
}

return start - str;
void DecChar(const char** chr) {
*chr = GetPrevChar(*chr);
}

u32 GetDrawStringWidth(const char* str) {
Expand Down
4 changes: 4 additions & 0 deletions arm9/source/common/ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 3 additions & 2 deletions arm9/source/godmode.c
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,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;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing for editing of empty files.

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;
Expand Down Expand Up @@ -1316,6 +1316,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);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary, I assume, to recalculate file sizes?

return 0;
}
else if (user_select == calcsha256) { // -> calculate SHA-256
Expand Down Expand Up @@ -2340,7 +2341,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;
}
Expand Down
Loading