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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<std::string()> 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
|
||||
@@ -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<std::string()> 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_
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user