diff --git a/source/common/common.h b/source/common/common.h index 3993dd3..4adb905 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -49,7 +49,7 @@ #endif // GodMode9 version -#define VERSION "1.2.5" +#define VERSION "1.2.6" // input / output paths #define INPUT_PATHS "0:/gm9/support", "0:", "0:/files9" // legacy paths diff --git a/source/godmode.c b/source/godmode.c index 6c861b5..8d3e804 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -42,6 +42,7 @@ #define COLOR_HVHEX(i) ((i % 2) ? RGB(0x30, 0x90, 0x30) : RGB(0x30, 0x80, 0x30)) #define COLOR_TVOFFS RGB(0x40, 0x60, 0x50) +#define COLOR_TVOFFSL RGB(0x20, 0x40, 0x30) #define COLOR_TVTEXT RGB(0x30, 0x85, 0x30) typedef struct { @@ -50,32 +51,76 @@ typedef struct { u32 scroll; } PaneData; -static inline u32 LineLen(const char* text, u32 len, const char* line) { +static inline u32 LineLen(const char* text, u32 len, u32 ww, const char* line) { char* line0 = (char*) line; char* line1 = (char*) line; + u32 llen = 0; + + // non wordwrapped length while ((line1 < (text + len)) && (*line1 != '\n') && *line1) line1++; while ((line1 > line0) && (*(line1-1) <= ' ')) line1--; - return line1 - line0; + llen = line1 - line0; + if (ww && (llen > ww)) { // wordwrapped length + for (llen = ww; (llen > 0) && (line[llen] != ' '); llen--); + if (!llen) llen = ww; // workaround for long strings + } + return llen; } -static inline char* LineSeek(const char* text, u32 len, const char* line, int off) { - char* lf = ((char*) line - 1); - - // safety checks / ensure we are at the start of the line +static inline char* LineSeek(const char* text, u32 len, u32 ww, const char* line, int add) { + // safety checks / if (line < text) return NULL; - if ((line >= (text + len)) && (off >= 0)) return (char*) line; - while ((lf >= text) && (*lf != '\n')) lf--; + if ((line >= (text + len)) && (add >= 0)) return (char*) line; - // handle backwards search - for (; (off < 0) && (lf >= text); off++) - for (lf--; (lf >= text) && (*lf != '\n'); lf--); + if (!ww) { // non wordwrapped mode + char* lf = ((char*) line - 1); - // handle forwards search - for (; (off > 0) && (lf < text + len); off--) - for (lf++; (lf < text + len) && (*lf != '\n'); lf++); - - return lf + 1; -} + // ensure we are at the start of the line + while ((lf >= text) && (*lf != '\n')) lf--; + + // 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; + } else { // wordwrapped mode + char* l0 = (char*) line; + + // handle forwards wordwrapped search + while ((add > 0) && (l0 < text + len)) { + u32 llen = LineLen(text, len, 0, l0); + for (; (add > 0) && (llen > ww); add--) { + u32 llenww = LineLen(text, len, ww, l0); + llen -= llenww; + l0 += llenww; + } + if (add > 0) { + l0 = LineSeek(text, len, 0, l0, 1); + add--; + } + } + + // handle backwards wordwrapped search + while ((add < 0) && (l0 > text)) { + char* l1 = LineSeek(text, len, 0, l0, -1); + int nlww = 0; // count wordwrapped lines in paragraph + for (char* ld = l1; ld < l0; ld = LineSeek(text, len, ww, ld, 1), nlww++); + if (add + nlww < 0) { + add += nlww; + l0 = l1; + } else { + l0 = LineSeek(text, len, ww, l1, nlww + add); + add = 0; + } + } + + return l0; + } +} void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* clipboard, u32 curr_pane) { const u32 n_cb_show = 8; @@ -297,63 +342,89 @@ u32 SdFormatMenu(void) { return 0; } -u32 MemTextViewer(const char* text, u32 len) { +u32 FileTextViewer(const char* path) { const u32 vpad = 1; const u32 hpad = 0; - // bool wordwrap = false; + const u32 lnos = 4; + u32 ww = 0; - char* line0 = (char*) text; - int lcurr = 0; - int off_x = 0; + // load text file (completely into memory) + char* text = (char*) TEMP_BUFFER; + u32 flen = FileGetData(path, text, TEMP_BUFFER_SIZE, 0); + u32 len = 0; // actual length may be shorter due to zero symbol + for (len = 0; (len < flen) && text[len]; len++); - u32 nlin_disp = SCREEN_HEIGHT / (FONT_HEIGHT_EXT + (2*vpad)); - u32 llen_disp = (SCREEN_WIDTH_TOP - (2*hpad)) / FONT_WIDTH_EXT; - u32 llen_max = 0; - u32 nlines = 0; + // check if this really is a text file + if (!ValidateText(text, len)) { + ShowPrompt(false, "Error: Not a valid text file"); + return 1; + } // clear screens ClearScreenF(true, true, COLOR_STD_BG); - static const char* instr = "Textviewer Controls:\n \n\x18\x19\x1A\x1B(+R) - Scroll\nR+Y - Wordwrap on/off\nX - Search / goto...\nB - Exit\n"; + // instructions + static const char* instr = "Textviewer Controls:\n \n\x18\x19\x1A\x1B(+R) - Scroll\nR+Y - Toggle wordwrap\nR+X - Goto line #\nB - Exit\n"; ShowString(instr); - // find maximum line len and # of lines - for (char* ptr = (char*) text; ptr < (text + len); ptr = LineSeek(text, len, ptr, 1)) { - u32 llen = LineLen(text, len, ptr); - if (llen > llen_max) llen_max = llen; - if (!*(ptr + llen)) len = (ptr + llen) - text; // zero found - nlines++; - } - // make room for line numbers - llen_disp -= 5; + // no of lines and length to display + u32 nlin_disp = SCREEN_HEIGHT / (FONT_HEIGHT_EXT + (2*vpad)); + u32 llen_disp = (SCREEN_WIDTH_TOP - (2*hpad)) / FONT_WIDTH_EXT; + // block placements + const char* al_str = "<< "; + const char* ar_str = " >>"; + if (lnos) llen_disp -= (lnos + 1); // make room for line numbers + u32 x_txt = (lnos) ? hpad + ((lnos+1)*FONT_WIDTH_EXT) : hpad; + u32 x_lno = hpad; + u32 p_al = 0; + u32 p_ar = llen_disp - strnlen(ar_str, 16); + u32 x_al = x_txt + (p_al * FONT_WIDTH_EXT); + u32 x_ar = x_txt + (p_ar * FONT_WIDTH_EXT); + + // find maximum line len + u32 llen_max = 0; + for (char* ptr = (char*) text; ptr < (text + len); ptr = LineSeek(text, len, 0, ptr, 1)) { + u32 llen = LineLen(text, len, 0, ptr); + if (llen > llen_max) llen_max = llen; + } + + // find last allowed lines (ww and nonww) + char* llast_nww = LineSeek(text, len, 0, text + len, -nlin_disp); + char* llast_ww = LineSeek(text, len, llen_disp, text + len, -nlin_disp); + + char* line0 = (char*) text; + int lcurr = 1; + int off_disp = 0; while (true) { // display text on screen - char dispstr[128]; // should be more than enough - char* arrow_l = dispstr; - char* arrow_r = dispstr + llen_disp - 1; + char txtstr[128]; // should be more than enough char* ptr = line0; u32 nln = lcurr; for (u32 y = vpad; y < SCREEN_HEIGHT; y += FONT_HEIGHT_EXT + (2*vpad)) { - char* ptr_next = LineSeek(text, len, ptr, 1); - u32 llen = LineLen(text, len, ptr); - u32 ncpy = ((int) llen < off_x) ? 0 : (llen - off_x); + char* ptr_next = LineSeek(text, len, ww, ptr, 1); + u32 llen = LineLen(text, len, ww, ptr); + u32 ncpy = ((int) llen < off_disp) ? 0 : (llen - off_disp); if (ncpy > llen_disp) ncpy = llen_disp; - snprintf(dispstr, llen_disp + 1, "%-*.*s", (int) llen_disp, (int) llen_disp, ""); - if (ncpy) memcpy(dispstr, ptr + off_x, ncpy); - for (char* d = dispstr; *d; d++) if (*d < ' ') *d = ' '; + bool al = !ww && off_disp; + bool ar = !ww && (llen > off_disp + llen_disp); - // line number / text / arrows (pt.1) - if (llen > off_x + llen_disp) *(arrow_r-1) = *arrow_r = '>'; - if (off_x) *arrow_l = *(arrow_l+1) = '<'; - DrawStringF(TOP_SCREEN, hpad + (5*FONT_WIDTH_EXT), y, COLOR_TVTEXT, COLOR_STD_BG, dispstr); - if (ptr != ptr_next) DrawStringF(TOP_SCREEN, hpad, y, COLOR_TVOFFS, COLOR_STD_BG, "%04lu", nln + 1); + // build text string + snprintf(txtstr, llen_disp + 1, "%-*.*s", (int) llen_disp, (int) llen_disp, ""); + if (ncpy) memcpy(txtstr, ptr + off_disp, ncpy); + for (char* d = txtstr; *d; d++) if (*d < ' ') *d = ' '; + if (al) memcpy(txtstr + p_al, al_str, strnlen(al_str, 16)); + if (ar) memcpy(txtstr + p_ar, ar_str, strnlen(ar_str, 16)); - // arrows (pt.2) - snprintf(dispstr, llen_disp + 1, "%-*.*s", (int) llen_disp, (int) llen_disp, ""); - if (llen > off_x + llen_disp) *(arrow_r-1) = *arrow_r = '>'; - if (off_x) *arrow_l = *(arrow_l+1) = '<'; - DrawStringF(TOP_SCREEN, hpad + (5*FONT_WIDTH_EXT), y, COLOR_TVOFFS, COLOR_TRANSPARENT, dispstr); + // draw line number & text + DrawStringF(TOP_SCREEN, x_txt, y, COLOR_TVTEXT, COLOR_STD_BG, txtstr); + if (lnos && (ptr != ptr_next)) DrawStringF(TOP_SCREEN, x_lno, y, + ((ptr == text) || (*(ptr-1) == '\n')) ? COLOR_TVOFFS : COLOR_TVOFFSL, COLOR_STD_BG, "%0*lu", lnos, nln); + else DrawStringF(TOP_SCREEN, x_lno, y, COLOR_TVOFFSL, COLOR_STD_BG, "%*.*s", lnos, lnos, " "); + + // colorize arrows + if (al) DrawStringF(TOP_SCREEN, x_al, y, COLOR_TVOFFS, COLOR_TRANSPARENT, al_str); + if (ar) DrawStringF(TOP_SCREEN, x_ar, y, COLOR_TVOFFS, COLOR_TRANSPARENT, ar_str); // advance pointer / line number for (char* c = ptr; c < ptr_next; c++) if (*c == '\n') ++nln; @@ -364,24 +435,30 @@ u32 MemTextViewer(const char* text, u32 len) { u32 pad_state = InputWait(); if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_L1)) CreateScreenshot(); else { // standard viewer mode - int lcurr_next = lcurr; + char* line0_next = line0; u32 step_ud = (pad_state & BUTTON_R1) ? nlin_disp : 1; u32 step_lr = (pad_state & BUTTON_R1) ? llen_disp : 1; - if (pad_state & BUTTON_DOWN) lcurr_next += step_ud; - else if (pad_state & BUTTON_RIGHT) off_x += step_lr; - else if (pad_state & BUTTON_UP) lcurr_next -= step_ud; - else if (pad_state & BUTTON_LEFT) off_x -= step_lr; - else if (pad_state & (BUTTON_B|BUTTON_START)) break; + bool switched = (pad_state & BUTTON_R1); + if (pad_state & BUTTON_DOWN) line0_next = LineSeek(text, len, ww, line0, step_ud); + else if (pad_state & BUTTON_UP) line0_next = LineSeek(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, "Current line: %i\nEnter new line below.", lcurr); + if (lnext64 && (lnext64 != (u64) -1)) line0_next = LineSeek(text, len, 0, line0, (int) lnext64 - lcurr); + ShowString(instr); + } else if (switched && (pad_state & BUTTON_Y)) { + ww = ww ? 0 : llen_disp; + line0_next = LineSeek(text, len, ww, line0, 0); + } else if (pad_state & (BUTTON_B|BUTTON_START)) break; // check for problems, apply changes - if (lcurr_next + nlin_disp > nlines) lcurr_next = nlines - nlin_disp; - if (lcurr_next < 0) lcurr_next = 0; - if (lcurr_next != lcurr) { - line0 = LineSeek(text, len, line0, lcurr_next - lcurr); - lcurr = lcurr_next; - } - if (off_x + llen_disp > llen_max) off_x = llen_max - llen_disp; - if (off_x < 0) off_x = 0; + if (!ww && (line0_next > llast_nww)) line0_next = llast_nww; + else if (ww && (line0_next > llast_ww)) line0_next = llast_ww; + for (int i = (line0_next < line0) ? -1 : 1; line0 != line0_next; line0 += i) + if (*line0 == '\n') lcurr += i; // fix line number + if (off_disp + llen_disp > llen_max) off_disp = llen_max - llen_disp; + if ((off_disp < 0) || ww) off_disp = 0; } } @@ -871,9 +948,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur FileHexViewer(curr_entry->path); return 0; } else if (user_select == textviewer) { // -> show in text viewer - char* text = (char*) TEMP_BUFFER; - u32 len = FileGetData(curr_entry->path, text, TEMP_BUFFER_SIZE, 0); - MemTextViewer(text, len); + FileTextViewer(curr_entry->path); return 0; } else if (user_select == calcsha) { // -> calculate SHA-256 Sha256Calculator(curr_entry->path);