From 64f277152cd87d8c72e13583c092d3061a44fa8c Mon Sep 17 00:00:00 2001 From: eshifri <46511024+eshifri@users.noreply.github.com> Date: Tue, 13 Jul 2021 17:46:42 -0700 Subject: [PATCH] Implement View Text (#406) * View text and code cleanup * Typo fix * Fixed case generation * check list fixed * Horizontal clipping fixed * Lost letter fixed. * Cleanup * Unified implementation (Rotory button support) * Fixed USB menu issue and RTN button. * Rotary encoder acceleration implemented * Do not close file on every line read * Sliding window implemented. File size limitations practically removed. * scrollbar implemented * Cleanup & scroll bar adjustment * Cleanup * Smoother scrolling for long lines. * Minor cleanup --- radio/src/gui/colorlcd/CMakeLists.txt | 1 + radio/src/gui/colorlcd/message_dialog.cpp | 47 ++- radio/src/gui/colorlcd/message_dialog.h | 42 +- radio/src/gui/colorlcd/model_setup.cpp | 30 +- radio/src/gui/colorlcd/radio_sdmanager.cpp | 27 +- radio/src/gui/colorlcd/view_text.cpp | 435 +++++++++++++++++++++ radio/src/gui/colorlcd/view_text.h | 126 ++++++ radio/src/opentx.cpp | 5 +- 8 files changed, 659 insertions(+), 54 deletions(-) create mode 100644 radio/src/gui/colorlcd/view_text.cpp create mode 100644 radio/src/gui/colorlcd/view_text.h diff --git a/radio/src/gui/colorlcd/CMakeLists.txt b/radio/src/gui/colorlcd/CMakeLists.txt index de3a1d804..367430657 100644 --- a/radio/src/gui/colorlcd/CMakeLists.txt +++ b/radio/src/gui/colorlcd/CMakeLists.txt @@ -62,6 +62,7 @@ set(GUI_SRC view_channels.cpp view_logical_switches.cpp view_statistics.cpp + view_text.cpp menu_screen.cpp screen_setup.cpp switch_warn_dialog.cpp diff --git a/radio/src/gui/colorlcd/message_dialog.cpp b/radio/src/gui/colorlcd/message_dialog.cpp index 067d69500..4e5675f82 100644 --- a/radio/src/gui/colorlcd/message_dialog.cpp +++ b/radio/src/gui/colorlcd/message_dialog.cpp @@ -21,10 +21,38 @@ #include "static.h" MessageDialog::MessageDialog(Window* parent, const char* title, - const char* message, const char* info, - const int lineHeight, - const WindowFlags windowFlags, - const LcdFlags textFlags) : + const char* message, const char* info) : + Dialog(parent, title, {50, 73, LCD_W - 100, LCD_H - 146}) +{ + messageWidget = new StaticText( + this, + {0, coord_t(height() - PAGE_LINE_HEIGHT) / 2, width(), PAGE_LINE_HEIGHT}, + message, 0, CENTERED); + + infoWidget = new StaticText(this, + {0, 30 + coord_t(height() - PAGE_LINE_HEIGHT) / 2, + width(), PAGE_LINE_HEIGHT}, + info, 0, CENTERED); + setCloseWhenClickOutside(true); + setFocus(); +} + +#if defined(HARDWARE_KEYS) +void MessageDialog::onEvent(event_t event) +{ + TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), + event); + + if (event == EVT_KEY_BREAK(KEY_EXIT) || event == EVT_KEY_BREAK(KEY_ENTER)) { + deleteLater(); + } +} +#endif + +DynamicMessageDialog::DynamicMessageDialog( + Window* parent, const char* title, std::function textHandler, + const char* message, const int lineHeight, + const LcdFlags textFlags) : Dialog(parent, title, {50, 73, LCD_W - 100, LCD_H - 146}) { setWindowFlags(windowFlags); @@ -34,21 +62,22 @@ MessageDialog::MessageDialog(Window* parent, const char* title, {0, coord_t(height() - PAGE_LINE_HEIGHT) / 2, width(), PAGE_LINE_HEIGHT}, message, 0, CENTERED); - infoWidget = new StaticText( + infoWidget = new DynamicText( this, {0, 30 + coord_t(height() - PAGE_LINE_HEIGHT) / 2, width(), lineHeight}, - info, 0, textFlags); + textHandler, textFlags); setCloseWhenClickOutside(true); setFocus(); } #if defined(HARDWARE_KEYS) -void MessageDialog::onEvent(event_t event) +void DynamicMessageDialog::onEvent(event_t event) { - TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), event); + TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), + event); if (event == EVT_KEY_BREAK(KEY_EXIT) || event == EVT_KEY_BREAK(KEY_ENTER)) { deleteLater(); } } -#endif +#endif \ No newline at end of file diff --git a/radio/src/gui/colorlcd/message_dialog.h b/radio/src/gui/colorlcd/message_dialog.h index bf0d570ba..a439568e1 100644 --- a/radio/src/gui/colorlcd/message_dialog.h +++ b/radio/src/gui/colorlcd/message_dialog.h @@ -23,23 +23,46 @@ #include "dialog.h" #include "static.h" -class MessageDialog : public Dialog +class MessageDialog: public Dialog { + public: + MessageDialog(Window * parent, const char * title, const char * message, const char * info = ""); + + void setInfoText(std::string text) + { + infoWidget->setText(std::move(text)); + } + + protected: + StaticText * messageWidget; + StaticText * infoWidget; + +#if defined(DEBUG_WINDOWS) + std::string getName() const override + { + return "MessageDialog"; + } +#endif + +#if defined(HARDWARE_KEYS) + void onEvent(event_t event) override; +#endif +}; + +class DynamicMessageDialog : public Dialog { public: - MessageDialog(Window* parent, const char* title, const char* message, - const char* info = "", const int lineHeight = PAGE_LINE_HEIGHT, - const WindowFlags windowFlags = 0, + DynamicMessageDialog(Window* parent, const char* title, + std::function textHandler, const char* message = "", const int lineHeight = PAGE_LINE_HEIGHT, const LcdFlags textFlags = CENTERED); - // Attn.: FONT(XXL) is not supported by MessageDialog + // Attn.: FONT(XXL) is not supported by DynamicMessageDialog - void setInfoText(std::string text) { infoWidget->setText(std::move(text)); } protected: StaticText* messageWidget; - StaticText* infoWidget; - LcdFlags textFlags; + DynamicText* infoWidget; + #if defined(DEBUG_WINDOWS) - std::string getName() const override { return "MessageDialog"; } + std::string getName() const override { return "DynamicMessageDialog"; } #endif #if defined(HARDWARE_KEYS) @@ -47,4 +70,5 @@ class MessageDialog : public Dialog #endif }; + #endif // _MESSAGE_DIALOG_H_ diff --git a/radio/src/gui/colorlcd/model_setup.cpp b/radio/src/gui/colorlcd/model_setup.cpp index a69df8222..75033cc05 100644 --- a/radio/src/gui/colorlcd/model_setup.cpp +++ b/radio/src/gui/colorlcd/model_setup.cpp @@ -41,26 +41,6 @@ std::string switchWarninglabel(swsrc_t index) 1); } -class RssiDialog : public MessageDialog -{ - public: - RssiDialog(Window* parent, const char* title, const char* message, - const char* info = "", const int lineHeight = PAGE_LINE_HEIGHT, - const WindowFlags windowFlags = 0, - const LcdFlags textFlags = CENTERED) : - MessageDialog(parent, title, message, info, lineHeight, windowFlags, - textFlags) - { - } - - virtual void checkEvents() - { - char buf[10]; - sprintf(buf, "%d", (int)TELEMETRY_RSSI()); - setInfoText(buf); - MessageDialog::checkEvents(); - } -}; class ChannelFailsafeBargraph: public Window { public: @@ -871,8 +851,14 @@ class ModuleWindow : public FormGroup { } else { moduleState[moduleIdx].mode = MODULE_MODE_RANGECHECK; - auto rssiDialog = new RssiDialog( - this, "Range Test", "RSSI:", "", 50, REFRESH_ALWAYS, + auto rssiDialog = new DynamicMessageDialog( + this, "Range Test", + [=]() { + char buf[16]; + sprintf(buf, "%d db", (int)TELEMETRY_RSSI()); + return std::string(buf); + }, + "RSSI:", 50, DEFAULT_COLOR | CENTERED | FONT(BOLD) | FONT(XL)); rssiDialog->setCloseHandler([this]() { rangeButton->check(false); diff --git a/radio/src/gui/colorlcd/radio_sdmanager.cpp b/radio/src/gui/colorlcd/radio_sdmanager.cpp index 2eef2e5fa..217d44612 100644 --- a/radio/src/gui/colorlcd/radio_sdmanager.cpp +++ b/radio/src/gui/colorlcd/radio_sdmanager.cpp @@ -26,6 +26,7 @@ #include "io/bootloader_flash.h" #include "standalone_lua.h" #include "sdcard.h" +#include "view_text.h" class FileNameEditWindow : public Page { @@ -232,15 +233,15 @@ void RadioSdManagerPage::build(FormWindow * window) if (res != FR_OK || fno.fname[0] == 0) break; // Break on error or end of dir - if (strlen(fno.fname) > SD_SCREEN_FILE_LENGTH) + if (strlen((const char*)fno.fname) > SD_SCREEN_FILE_LENGTH) continue; if (fno.fname[0] == '.' && fno.fname[1] != '.') continue; // Ignore hidden files under UNIX, but not .. if (fno.fattrib & AM_DIR) { - directories.push_back(fno.fname); + directories.push_back((char*)fno.fname); } else { - files.push_back(fno.fname); + files.push_back((char*)fno.fname); } } @@ -251,7 +252,7 @@ void RadioSdManagerPage::build(FormWindow * window) for (auto name: directories) { auto b = new TextButton(window, grid.getLabelSlot(), name, [=]() -> uint8_t { std::string fullpath = currentPath + "/" + name; - f_chdir(fullpath.c_str()); + f_chdir((TCHAR*)fullpath.c_str()); window->clear(); build(window); return 0; @@ -303,11 +304,18 @@ void RadioSdManagerPage::build(FormWindow * window) // else if (isExtensionMatching(ext, BITMAPS_EXT)) { // // TODO // } - // else if (!strcasecmp(ext, TEXT_EXT)) { - // menu->addLine(STR_VIEW_TEXT, [=]() { - // // TODO - // }); - // } + else if (!strcasecmp(ext, TEXT_EXT)) { + menu->addLine(STR_VIEW_TEXT, [=]() { + static char lfn[FF_MAX_LFN + 1]; // TODO optimize that! + f_getcwd((TCHAR *)lfn, FF_MAX_LFN); + + auto textView = new ViewTextWindow(lfn, name); + textView->setCloseHandler([=]() { + //window->clear(); + rebuild(window); + }); + }); + } if (!READ_ONLY() && !strcasecmp(ext, FIRMWARE_EXT)) { if (isBootloader(name.data())) { menu->addLine(STR_FLASH_BOOTLOADER, [=]() { @@ -378,7 +386,6 @@ void RadioSdManagerPage::build(FormWindow * window) //window->clear(); rebuild(window); }); - // rebuild(window); }); }); menu->addLine(STR_DELETE_FILE, [=]() { f_unlink((const TCHAR*)getFullPath(name)); diff --git a/radio/src/gui/colorlcd/view_text.cpp b/radio/src/gui/colorlcd/view_text.cpp new file mode 100644 index 000000000..c3381508d --- /dev/null +++ b/radio/src/gui/colorlcd/view_text.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "view_text.h" + +#include "gridlayout.h" +#include "menu.h" +#include "opentx.h" +#include "sdcard.h" + +#define CASE_EVT_KEY_NEXT_LINE \ + case EVT_ROTARY_RIGHT: \ + case EVT_KEY_BREAK(KEY_PGDN) +// case EVT_KEY_BREAK(KEY_DOWN) + +#define CASE_EVT_KEY_PREVIOUS_LINE \ + case EVT_ROTARY_LEFT: \ + case EVT_KEY_BREAK(KEY_PGUP): \ + case EVT_KEY_BREAK(KEY_UP) + +#define CASE_EVT_START \ + case EVT_ENTRY: \ + case EVT_KEY_BREAK(KEY_ENTER): \ + case EVT_KEY_BREAK(KEY_TELEM) + +void ViewTextWindow::extractNameSansExt() +{ + uint8_t nameLength; + uint8_t extLength; + + const char *ext = + getFileExtension(name.data(), 0, 0, &nameLength, &extLength); + extension = std::string(ext); + if (nameLength > TEXT_FILENAME_MAXLEN) nameLength = TEXT_FILENAME_MAXLEN; + + nameLength -= extLength; + name.substr(nameLength); +} + +#if READ_FILE_BY_LINE +void ViewTextWindow::buildBody(Window *window) +{ + GridLayout grid(window); + grid.spacer(); + int i; + + // assume average characte is 10 pixels wide, round the string length to tens. + // Font is not fixed width, so this is for the worst case... + const int maxLineLength = int(floor(window->width() / 10 / 10)) * 10 - 2; + window->setFocus(); + + FIL file; + if (FR_OK != + f_open(&file, (TCHAR *)fullPath.c_str(), FA_OPEN_EXISTING | FA_READ)) { + return; + } + readCount = 0; + longestLine = 0; + lastLine = false; + for (i = 0; i < TEXT_FILE_MAXSIZE && !lastLine; i++) { + lastLine = + sdReadTextLine(&file, reusableBuffer.viewText.lines[0], maxLineLength); + + new StaticText(window, grid.getSlot(), reusableBuffer.viewText.lines[0]); + grid.nextLine(); + } + + f_close(&file); + + window->setInnerWidth((longestLine + 4) * 10); + window->setInnerHeight(grid.getWindowHeight()); +} + +void ViewTextWindow::checkEvents() +{ + if (&body == Window::focusWindow) { + const int step = PAGE_LINE_HEIGHT + PAGE_LINE_SPACING; + coord_t currentPos = body.getScrollPositionY(); + coord_t deltaY; + event_t event = getWindowEvent(); + + if (event == EVT_ROTARY_LEFT || event == EVT_ROTARY_RIGHT) { + deltaY = ROTARY_ENCODER_SPEED() * step; + } else { + deltaY = step; + } + + switch (event) { + CASE_EVT_KEY_NEXT_LINE: + currentPos += deltaY; + break; + + CASE_EVT_KEY_PREVIOUS_LINE: + currentPos -= deltaY; + break; + + default: + Page::onEvent(event); + return; + } + body.setScrollPositionY(currentPos); + } + Page::checkEvents(); +} + +bool ViewTextWindow::sdReadTextLine(FIL *file, char line[], + const uint8_t maxLineLength) +{ + char c; + unsigned int sz; + uint8_t line_length = 0; + uint8_t escape = 0; + char escape_chars[4] = {0}; + int current_line = 0; + + memclear(line, maxLineLength + 1); + line[line_length++] = 0x20; + + for (uint8_t i = 0; i < maxLineLength && readCount < (int)TEXT_FILE_MAXSIZE; + ++i) { + if ((f_read(file, &c, 1, &sz) != FR_OK || !sz) && + line_length < maxLineLength) { + return true; + } + readCount++; + + if (c == '\n') { + ++current_line; + escape = 0; + return false; + } else if (c != '\r') { + if (c == '\\' && escape == 0) { + escape = 1; + continue; + } else if (c != '\\' && escape > 0 && escape < sizeof(escape_chars)) { + escape_chars[escape - 1] = c; + if (escape == 2 && !strncmp(escape_chars, "up", 2)) { + c = CHAR_UP; + } else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) { + c = CHAR_DOWN; + } else if (escape == 3) { + int val = atoi(escape_chars); + if (val >= 200 && val < 225) { + c = '\200' + val - 200; + } + } else { + escape++; + continue; + } + } else if (c == '~') { + c = 'z' + 1; + } else if (c == '\t') { + c = 0x1D; // tab + } + escape = 0; + line[line_length++] = c; + if (longestLine < line_length) longestLine = line_length; + } + } + if (c != '\n') { + current_line += 1; + return false; + } + + return false; +} +#else + +void ViewTextWindow::buildBody(Window *window) +{ + GridLayout grid(window); + grid.spacer(); + int i; + + // assume average characte is 10 pixels wide, round the string length to tens. + // Font is not fixed width, so this is for the worst case... + maxLineLength = int(floor(window->width() / 10 / 10)) * 10 - 2; + maxScreenLines = window->height() / (PAGE_LINE_HEIGHT + PAGE_LINE_SPACING); + window->setFocus(); + readLinesCount = 0; + lastLoadedLine = 0; + + lines = new char*[maxScreenLines]; + for(i = 0 ; i < maxScreenLines; i++) + { + lines[i] = new char[maxLineLength + 1]; + memclear(lines[i], maxLineLength + 1); + } + + longestLine = 0; + loadFirstScreen(); + + if(isInSetup == true) + { + textBottom = false; + while(!textBottom) + { + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + textVerticalOffset += 10; + } + maxPos = (maxLines - maxScreenLines) * (PAGE_LINE_HEIGHT + PAGE_LINE_SPACING); + if(maxPos < body.getRect().h) maxPos = body.getRect().h; + } + + isInSetup = false; + loadFirstScreen(); + + for (i = 0; i < maxScreenLines; i++) { + new DynamicText(window, grid.getSlot(), [=]() { + std::string str = (lines[i][0]) ? std::string(lines[i]) : std::string(" "); + return std::string(str); + }); + grid.nextLine(); + } +} + +#if defined(HARDWARE_TOUCH) +bool ViewTextWindow::onTouchSlide(coord_t x, coord_t y, coord_t startX, + coord_t startY, coord_t slideX, + coord_t slideY) +{ + if (&body == Window::focusWindow) { + const int step = PAGE_LINE_HEIGHT + PAGE_LINE_SPACING; + int deltaY = -slideY; + int lineStep = deltaY / step; + + textVerticalOffset += lineStep; + if (textVerticalOffset < 0) textVerticalOffset = 0; + + // if (textBottom && lineStep > 0) textVerticalOffset -= lineStep; + if(textVerticalOffset > maxLines) textVerticalOffset = maxLines; + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + } + return Page::onTouchSlide(x, y, startX, startY, slideX, slideY); +} +#endif + +void ViewTextWindow::checkEvents() +{ + if (&body == Window::focusWindow) { + const int step = PAGE_LINE_HEIGHT + PAGE_LINE_SPACING; + coord_t deltaY; + event_t event = getWindowEvent(); + + if (event == EVT_ROTARY_LEFT || event == EVT_ROTARY_RIGHT) { + deltaY = ROTARY_ENCODER_SPEED() * step; + } else { + deltaY = step; + } + + int lineStep = deltaY / step; + if(lineStep > (maxScreenLines >> 1)) lineStep = maxScreenLines >> 1; +/* + if(event) { + sprintf(lines[0], "Received event %d", event); + if(!readLinesCount) + readLinesCount = 1; + Page::onEvent(event); + return; + } +*/ + switch (event) { + CASE_EVT_START: + textVerticalOffset = 0; + readLinesCount = 0; + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + break; + + CASE_EVT_KEY_NEXT_LINE: + if(textBottom && textVerticalOffset) //(textVerticalOffset + maxScreenLines - 1 >= readLinesCount) + break; + else { + textVerticalOffset += lineStep; + if(textVerticalOffset > maxLines) textVerticalOffset = maxLines; + } + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + break; + + CASE_EVT_KEY_PREVIOUS_LINE: + if (textVerticalOffset == 0) + break; + else { + textVerticalOffset -= lineStep; + if(textVerticalOffset < 0) textVerticalOffset = 0; + } + + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); + break; + + default: + Page::onEvent(event); + break; + } + } + Page::checkEvents(); +} + +void ViewTextWindow::loadFirstScreen() +{ + textVerticalOffset = 0; + readLinesCount = 0; + sdReadTextFileBlock(fullPath.c_str(), readLinesCount); +} + +void ViewTextWindow::sdReadTextFileBlock(const char *filename, int &lines_count) +{ + FIL file; + int result; + char c; + unsigned int sz = 0; + int line_length = 1; + uint8_t escape = 0; + char escape_chars[4] = {0}; + int current_line = 0; + textBottom = false; + + for(int i = 0; i < maxScreenLines; i++) { + memclear(lines[i], maxLineLength + 1); + lines[i][0] = ' '; + } + + result = f_open(&file, (TCHAR *)filename, FA_OPEN_EXISTING | FA_READ); + if (result == FR_OK) { + for (int i = 0; i < TEXT_FILE_MAXSIZE && + f_read(&file, &c, 1, &sz) == FR_OK && sz == 1 && + (lines_count == 0 || + current_line - textVerticalOffset < maxScreenLines); + i++) { + if (c == '\n' || line_length >= maxLineLength) { + ++current_line; + line_length = 1; + escape = 0; + } + if (c != '\r' && c != '\n' && current_line >= textVerticalOffset && + current_line - textVerticalOffset < maxScreenLines && + line_length < maxLineLength) { + if (c == '\\' && escape == 0) { + escape = 1; + continue; + } else if (c != '\\' && escape > 0 && escape < sizeof(escape_chars)) { + escape_chars[escape - 1] = c; + if (escape == 2 && !strncmp(escape_chars, "up", 2)) { + c = CHAR_UP; + } else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) { + c = CHAR_DOWN; + } else if (escape == 3) { + int val = atoi(escape_chars); + if (val >= 200 && val < 225) { + c = '\200' + val - 200; + } + } else { + escape++; + continue; + } + } + else if (c == '~') { + c = 'z' + 1; + } else if (c == '\t') { + c = 0x1D; // tab + } + escape = 0; + + lines[current_line - textVerticalOffset][line_length++] = c; + if (longestLine < line_length) longestLine = line_length; + } else if(current_line < textVerticalOffset) { + ++line_length; + } + } + if (c != '\n') { + ++current_line; + } + + if (f_eof(&file)) { + textBottom = true; + if (isInSetup) maxLines = current_line; + } + + f_close(&file); + } + + if(lastLoadedLine < textVerticalOffset) + lastLoadedLine = textVerticalOffset; + + if (lines_count == 0) { + lines_count = current_line; + } +} + +void ViewTextWindow::drawVerticalScrollbar(BitmapBuffer *dc) +{ + int readPos = textVerticalOffset * (PAGE_LINE_HEIGHT + PAGE_LINE_SPACING); + + if(readPos < header.getRect().h << 1) readPos = header.getRect().h << 1; + + coord_t yofs = divRoundClosest(body.getRect().h * readPos, maxPos); + coord_t yhgt = divRoundClosest(body.getRect().h * body.getRect().h, maxPos); + if (yhgt < 15) yhgt = 15; + if (yhgt + yofs > maxPos) yhgt = maxPos - yofs; + dc->drawSolidFilledRect(body.getRect().w - SCROLLBAR_WIDTH, yofs, + SCROLLBAR_WIDTH, yhgt, SCROLLBAR_COLOR); +} + +#endif + +#include "datastructs.h" + +void readModelNotes() +{ + LED_ERROR_BEGIN(); + + std::string modelNotesName(g_model.header.name); + modelNotesName.append(TEXT_EXT); + const char buf[] = {MODELS_PATH}; + f_chdir((TCHAR*)buf); + new ViewTextWindow(std::string(buf), modelNotesName, ICON_MODEL); + + LED_ERROR_END(); +} \ No newline at end of file diff --git a/radio/src/gui/colorlcd/view_text.h b/radio/src/gui/colorlcd/view_text.h new file mode 100644 index 000000000..e73938ef1 --- /dev/null +++ b/radio/src/gui/colorlcd/view_text.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +#include "page.h" +#include "static.h" +#include "lcd.h" +#include "menus.h" +#include "ff.h" + +#define READ_FILE_BY_LINE 0 + +constexpr uint16_t TEXT_FILE_MAXSIZE = 20480; + +class ViewTextWindow : public Page +{ + public: + ViewTextWindow(const std::string iPath, const std::string iName, unsigned int icon = ICON_RADIO_SD_MANAGER) : + Page(icon), + path(std::move(iPath)), + name(std::move(iName)), + icon(icon) + { + fullPath = path + std::string("/") + name; + extractNameSansExt(); + lines = nullptr; + lastLoadedLine = 0; + maxPos = 0; + maxLines = 0; + isInSetup = true; + //body.setWindowFlags(FORWARD_SCROLL); + header.setWindowFlags(NO_SCROLLBAR); + + buildHeader(&header); + buildBody(&body); + }; + +#if READ_FILE_BY_LINE + bool sdReadTextLine(FIL* file, char lines[], + const uint8_t lineLength = LCD_COLS); +#else + void sdReadTextFileBlock(const char * filename, int& lines_count); + void loadFirstScreen(void); + #if defined(HARDWARE_TOUCH) + bool onTouchSlide(coord_t x, coord_t y, coord_t startX, coord_t startY, coord_t slideX, coord_t slideY) override; +#endif + void drawVerticalScrollbar(BitmapBuffer * dc); + + ~ViewTextWindow() + { + if(lines != nullptr) { + for(int i = 0; i < maxScreenLines; i++) + { + delete[] lines[i]; + } + delete[] lines; + } + } + + void paint(BitmapBuffer* dc) + { + Page::paint(dc); + drawVerticalScrollbar(dc); + } +#endif + virtual void checkEvents(); + +#if defined(DEBUG_WINDOWS) + std::string getName() const override { return "ViewTextWindow"; }; +#endif + + protected: + std::string path; + std::string name; + std::string fullPath; + std::string extension; + unsigned int icon; + + bool lastLine; + uint16_t readCount; + int longestLine; + +#if !READ_FILE_BY_LINE +char** lines = nullptr; +//char lines[15][80]; +int maxScreenLines; +int maxLineLength; +int textVerticalOffset; +int readLinesCount; +int lastLoadedLine; +int maxPos; +int maxLines; +bool textBottom; +bool isInSetup; +#endif + + void extractNameSansExt(void); + void buildBody(Window *window); + void buildHeader(Window *window) + { + new StaticText(window, + {PAGE_TITLE_LEFT, PAGE_TITLE_TOP + 10, + LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT}, + name, 0, MENU_HIGHLIGHT_COLOR); + }; +}; + +void readModelNotes(); \ No newline at end of file diff --git a/radio/src/opentx.cpp b/radio/src/opentx.cpp index 6ea3dec47..dab39d0fc 100644 --- a/radio/src/opentx.cpp +++ b/radio/src/opentx.cpp @@ -26,6 +26,7 @@ // #include "shutdown_animation.h" #include "radio_calibration.h" #include "view_main.h" + #include "view_text.h" #endif #if defined(PCBSKY9X) @@ -745,13 +746,9 @@ void checkAll() disableVBatBridge(); #endif -#if defined(COLORLCD) -#warning "Model notes missing" -#else if (g_model.displayChecklist && modelHasNotes()) { readModelNotes(); } -#endif #if defined(MULTIMODULE) checkMultiLowPower();