fix(color): LVGLify themes page and fix several possible string handling issues (#3135)

* Convert theme select, theme edit and color edit pages to LVGL flex.
Add 'Edit Color' string to language files (requires translations).
Simplify theme edit 'onCancel' logic.
Fix size of buttons in ConfirmDialog for portrait layout.
Truncate item strings in ListBox to fit available space.
Set window background color on Page and TabsGroup classes instead of using 'paint' function.
Replace strncpy and strncat with strAppend - avoid possible unterminated strings and string overrun.
Truncate display of long name and author strings in theme pages.

* Revert code to truncate ListBox entry strings as it causes other issues.

* Adjust layout for theme color editor to fit large hex string size.

* Remove unnecessary copying of the ThemeFile object being edited.
Refresh TabsGroup page background color when theme changes.

* Set selected theme in list on long press as well as short press.
Comment out unused 'title' in ListBox.

* Handle default theme like custom themes on SD card:
- show logo when selecting theme
- support background_WxH.png names for background images
Use f_stat( via isFileAvailable) instead of opening and closing files to check for existence.

* Remove debug print.

* Fix rebase.
This commit is contained in:
philmoz
2023-02-27 11:40:02 +10:00
committed by GitHub
parent 9216570d6c
commit f81e140cf3
27 changed files with 435 additions and 396 deletions
@@ -47,14 +47,22 @@ ConfirmDialog::ConfirmDialog(Window* parent, const char* title,
onCancel();
return 0;
});
#if LCD_W > LCD_H
btn->setWidth(LV_DPI_DEF);
#else
btn->setWidth(LV_DPI_DEF * 3 / 4);
#endif
btn = new TextButton(box, rect_t{}, STR_YES, [=]() -> int8_t {
this->deleteLater();
this->confirmHandler();
return 0;
});
#if LCD_W > LCD_H
btn->setWidth(LV_DPI_DEF);
#else
btn->setWidth(LV_DPI_DEF * 3 / 4);
#endif
content->setWidth(LCD_W * 0.8);
content->updateSize();
+2 -2
View File
@@ -97,9 +97,9 @@ class ListBase : public TableField
class ListBox : public ListBase
{
std::string title;
// std::string title;
public:
using ListBase::ListBase;
inline void setTitle(std::string title) { this->title = title; }
// inline void setTitle(std::string title) { this->title = title; }
};
+3 -7
View File
@@ -58,6 +58,8 @@ Page::Page(unsigned icon):
body(this, _get_body_rect(), FORM_FORWARD_FOCUS)
{
Layer::push(this);
lv_obj_set_style_bg_color(lvobj, makeLvColor(COLOR_THEME_SECONDARY3), 0);
}
void Page::deleteLater(bool detach, bool trash)
@@ -69,15 +71,9 @@ void Page::deleteLater(bool detach, bool trash)
Window::deleteLater(detach, trash);
}
void Page::paint(BitmapBuffer * dc)
{
dc->clear(COLOR_THEME_SECONDARY3);
}
void Page::onCancel()
{
if (canCancel())
deleteLater();
deleteLater();
}
void Page::onClicked()
-3
View File
@@ -55,11 +55,8 @@ class Page : public Window
void onCancel() override;
void onClicked() override;
void paint(BitmapBuffer* dc) override;
void deleteLater(bool detach = true, bool trash = true) override;
virtual bool canCancel() { return true; }
protected:
PageHeader header;
FormWindow body;
+339 -301
View File
@@ -21,112 +21,98 @@
#include "radio_theme.h"
#include <algorithm>
#include "bitmapbuffer.h"
#include "libopenui.h"
#include "opentx.h"
#include "file_preview.h"
#include "theme_manager.h"
#include "view_main.h"
#include "widget_settings.h"
#include "sliders.h"
#include "color_list.h"
#include "color_editor.h"
#include "listbox.h"
#include "preview_window.h"
#if (LCD_W > LCD_H)
#define COLOR_PREVIEW_WIDTH 18
#define COLOR_PREVIEW_HEIGHT (LCD_H - TOPBAR_HEIGHT - 30)
#define LEFT_LIST_WIDTH (LCD_W / 2) - COLOR_PREVIEW_WIDTH
#define LEFT_LIST_HEIGHT (LCD_H - TOPBAR_HEIGHT - 37)
#define COLOR_LIST_WIDTH ((LCD_W * 3)/10)
#define COLOR_LIST_HEIGHT (LCD_H - TOPBAR_HEIGHT - 14)
#define TOP_LIST_OFFSET 4
#define LEFT_LIST_OFFSET 3
constexpr int COLOR_PREVIEW_SIZE = 18;
#if LCD_W > LCD_H
constexpr int LIST_WIDTH = ((LCD_W - 12) / 2 - COLOR_PREVIEW_SIZE);
constexpr int COLOR_LIST_WIDTH = ((LCD_W * 3)/10);
#else
#define COLOR_PREVIEW_WIDTH LCD_W
#define COLOR_PREVIEW_HEIGHT 18
#define LEFT_LIST_WIDTH LCD_W - 6
#define LEFT_LIST_HEIGHT (LCD_H / 2 - 38)
#define COLOR_LIST_WIDTH LCD_W - 6
#define COLOR_LIST_HEIGHT (LCD_H / 2 - 24)
#define TOP_LIST_OFFSET 4
#define LEFT_LIST_OFFSET 3
constexpr int LIST_HEIGHT = (LCD_H / 2 - 38);
constexpr int COLOR_LIST_HEIGHT = (LCD_H / 2 - 24);
#endif
#define MARGIN_WIDTH 5
#if (LCD_W > LCD_H)
constexpr int BUTTON_HEIGHT = 30;
#if LCD_W > LCD_H
constexpr int BUTTON_WIDTH = 75;
constexpr rect_t detailsDialogRect = {30, 30, LCD_W - 60, LCD_H - 60};
constexpr int labelWidth = 150;
constexpr int COLOR_BOX_LEFT = 5;
constexpr int COLOR_BOX_WIDTH = 45;
constexpr int COLOR_BOX_HEIGHT = 27;
#else
constexpr int BUTTON_HEIGHT = 30;
constexpr int BUTTON_WIDTH = 65;
constexpr rect_t detailsDialogRect = {10, 50, LCD_W - 20, 220};
constexpr int labelWidth = 120;
constexpr int COLOR_BOX_LEFT = 3;
constexpr int COLOR_BOX_WIDTH = 55;
constexpr int COLOR_BOX_HEIGHT = 32;
constexpr int COLOR_BUTTON_WIDTH = COLOR_BOX_WIDTH;
constexpr int COLOR_BUTTON_HEIGHT = 20;
#endif
constexpr int BUTTON_HEIGHT = 30;
constexpr int COLOR_BOX_HEIGHT = 30;
constexpr int BOX_MARGIN = 2;
constexpr int MAX_BOX_WIDTH = 15;
class ThemeColorPreview : public FormField
class ThemeColorPreview : public FormWindow
{
public:
ThemeColorPreview(Window *parent, const rect_t &rect, std::vector<ColorEntry> colorList) :
FormField(parent, rect, NO_FOCUS),
FormWindow(parent, rect, NO_FOCUS),
colorList(colorList)
{
setBoxWidth();
padAll(0);
#if LCD_W > LCD_H
setFlexLayout(LV_FLEX_FLOW_COLUMN, BOX_MARGIN);
#else
setFlexLayout(LV_FLEX_FLOW_ROW, BOX_MARGIN);
#endif
build();
}
~ThemeColorPreview()
void build()
{
clear();
setBoxWidth();
int size = (boxWidth + BOX_MARGIN) * colorList.size() - BOX_MARGIN;
#if LCD_W > LCD_H
padTop((height() - size) / 2);
#else
padLeft((width() - size) / 2);
#endif
for (auto color: colorList) {
auto c = lv_obj_create(lvobj);
lv_obj_set_size(c, boxWidth, boxWidth);
lv_obj_set_style_border_width(c, 1, 0);
lv_obj_set_style_border_color(c, makeLvColor(COLOR2FLAGS(BLACK)), 0);
lv_obj_set_style_border_opa(c, LV_OPA_100, 0);
lv_obj_set_style_bg_color(c, makeLvColor(COLOR2FLAGS(color.colorValue)), 0);
lv_obj_set_style_bg_opa(c, LV_OPA_100, 0);
}
}
void setColorList(std::vector<ColorEntry> colorList)
{
this->colorList.assign(colorList.begin(), colorList.end());
setBoxWidth();
invalidate();
}
void paint(BitmapBuffer *dc) override
{
int totalNessessarySpace = colorList.size() * (boxWidth + 2);
int axis = rect.w > rect.h ? rect.w : rect.h;
axis = (axis - totalNessessarySpace) / 2;
for (auto color: colorList) {
if (rect.w > rect.h) {
dc->drawSolidFilledRect(axis, 0, boxWidth, boxWidth, COLOR2FLAGS(color.colorValue));
dc->drawSolidRect(axis, 0, boxWidth, boxWidth, 1, COLOR2FLAGS(BLACK));
} else {
dc->drawSolidFilledRect(0, axis, boxWidth, boxWidth, COLOR2FLAGS(color.colorValue));
dc->drawSolidRect(0, axis, boxWidth, boxWidth, 1, COLOR2FLAGS(BLACK));
}
axis += boxWidth + BOX_MARGIN;
}
this->colorList = colorList;
build();
}
protected:
std::vector<ColorEntry> colorList;
int boxWidth = MAX_BOX_WIDTH;
void setBoxWidth()
{
auto winSize = rect.w > rect.h ? rect.w : rect.h;
boxWidth = winSize / (colorList.size() + BOX_MARGIN);
#if LCD_W > LCD_H
boxWidth = (height() - (colorList.size() - 1) * BOX_MARGIN) / colorList.size();
#else
boxWidth = (width() - (colorList.size() - 1) * BOX_MARGIN) / colorList.size();
#endif
boxWidth = min(boxWidth, MAX_BOX_WIDTH);
}
};
@@ -198,169 +184,179 @@ class ThemeDetailsDialog: public Dialog
std::function<void(ThemeFile theme)> saveHandler = nullptr;
};
class ColorSquare : public FormField
class ColorSquare : public Window
{
public:
ColorSquare(Window *window, const rect_t &rect, uint32_t color, WindowFlags windowFlags = 0)
: FormField(window, rect, windowFlags | NO_FOCUS),
ColorSquare(Window *window, const rect_t &rect, uint32_t color, WindowFlags windowFlags = 0) :
Window(window, rect, windowFlags | NO_FOCUS),
_color(color)
{
}
void paint (BitmapBuffer *dc) override
{
dc->drawSolidFilledRect(0, 0, rect.w, rect.h, COLOR2FLAGS(_color));
dc->drawSolidRect(0, 0, rect.w, rect.h, 1, COLOR2FLAGS(BLACK));
swatch = lv_obj_create(lvobj);
lv_obj_set_size(swatch, width(), height());
lv_obj_set_style_border_width(swatch, 1, 0);
lv_obj_set_style_border_color(swatch, makeLvColor(COLOR2FLAGS(BLACK)), 0);
lv_obj_set_style_border_opa(swatch, LV_OPA_100, 0);
lv_obj_set_style_bg_color(swatch, makeLvColor(COLOR2FLAGS(_color)), 0);
lv_obj_set_style_bg_opa(swatch, LV_OPA_100, 0);
}
void setColorToEdit(uint32_t colorEntry)
{
_color = colorEntry;
invalidate();
lv_obj_set_style_bg_color(swatch, makeLvColor(COLOR2FLAGS(_color)), 0);
}
protected:
uint32_t _color;
lv_obj_t *swatch;
};
void setHexStr(StaticText* hexBox, uint32_t rgb)
{
auto r = GET_RED(rgb), g = GET_GREEN(rgb), b = GET_BLUE(rgb);
char hexstr[8];
snprintf(hexstr, sizeof(hexstr), "%02X%02X%02X",
(uint16_t)r, (uint16_t)g, (uint16_t)b);
hexBox->setText(hexstr);
}
class ColorEditPage : public Page
{
public:
ColorEditPage(ThemeFile theme, LcdColorIndex indexOfColor,
std::function<void (uint32_t rgb)> setValue = nullptr) :
Page(ICON_RADIO_EDIT_THEME),
_setValue(std::move(setValue)),
_indexOfColor(indexOfColor),
_theme(theme)
{
buildBody(&body);
buildHead(&header);
}
void setActiveColorBar(int activeTab)
{
if (activeTab >= 0 && _activeTab <= (int)_tabs.size()) {
_activeTab = activeTab;
for (auto i = 0; i < (int) _tabs.size(); i++)
_tabs[i]->check(i == _activeTab);
_colorEditor->setColorEditorType(_activeTab == 1 ? HSV_COLOR_EDITOR : RGB_COLOR_EDITOR);
}
}
protected:
std::function<void (uint32_t rgb)> _setValue;
LcdColorIndex _indexOfColor;
ThemeFile _theme;
TextButton *_cancelButton;
ColorEditor *_colorEditor;
PreviewWindow *_previewWindow = nullptr;
std::vector<Button *> _tabs;
int _activeTab = 0;
ColorSquare *_colorSquare = nullptr;
StaticText *_hexBox = nullptr;
void buildBody(FormWindow* window)
{
rect_t r;
// hexBox
r = { COLOR_BOX_LEFT + COLOR_BOX_WIDTH + 5, TOP_LIST_OFFSET, 90, 30 };
_hexBox = new StaticText(window, r, "", 0, COLOR_THEME_PRIMARY1 | FONT(L) | RIGHT);
setHexStr(_hexBox, _theme.getColorEntryByIndex(_indexOfColor)->colorValue);
if (LCD_W > LCD_H)
r = { COLOR_BOX_LEFT, COLOR_BOX_HEIGHT + 7, COLOR_LIST_WIDTH, window->height() - COLOR_BOX_HEIGHT - 7};
else
r = { COLOR_BOX_LEFT, COLOR_BOX_HEIGHT + 7, COLOR_LIST_WIDTH, LCD_H / 2 - COLOR_BOX_HEIGHT - 20 };
_colorEditor = new ColorEditor(window, r, _theme.getColorEntryByIndex(_indexOfColor)->colorValue,
[=](uint32_t rgb) {
_theme.setColor(_indexOfColor, rgb);
if (_colorSquare != nullptr) {
_colorSquare->setColorToEdit(rgb);
}
if (_previewWindow != nullptr) {
_previewWindow->setColorList(_theme.getColorList());
}
if (_hexBox != nullptr) {
setHexStr(_hexBox, rgb);
}
if (_setValue != nullptr)
_setValue(rgb);
});
_colorEditor->setColorEditorType(HSV_COLOR_EDITOR);
_activeTab = 1;
r = { COLOR_BOX_LEFT, 4, COLOR_BOX_WIDTH, COLOR_BOX_HEIGHT };
_colorSquare = new ColorSquare(window, r, _theme.getColorEntryByIndex(_indexOfColor)->colorValue);
r = LCD_W > LCD_H ?
rect_t {
COLOR_LIST_WIDTH + MARGIN_WIDTH + LEFT_LIST_OFFSET,
TOP_LIST_OFFSET,
LCD_W - COLOR_LIST_WIDTH - LEFT_LIST_OFFSET - MARGIN_WIDTH * 2,
COLOR_LIST_HEIGHT } :
rect_t {
LEFT_LIST_OFFSET,
TOP_LIST_OFFSET + COLOR_LIST_HEIGHT + MARGIN_WIDTH,
LEFT_LIST_WIDTH,
COLOR_LIST_HEIGHT - (MARGIN_WIDTH * 2)
};
_previewWindow = new PreviewWindow(window, r, _theme.getColorList());
}
void buildHead(PageHeader* window)
{
LcdFlags flags = 0;
if (LCD_W < LCD_H) {
flags = FONT(XS);
public:
ColorEditPage(ThemeFile *theme, LcdColorIndex indexOfColor,
std::function<void ()> updateHandler = nullptr) :
Page(ICON_RADIO_EDIT_THEME),
_updateHandler(std::move(updateHandler)),
_indexOfColor(indexOfColor),
_theme(theme)
{
buildBody(&body);
buildHead(&header);
}
// page title
new StaticText(window,
{ PAGE_TITLE_LEFT, PAGE_TITLE_TOP, LCD_W - PAGE_TITLE_LEFT,
PAGE_LINE_HEIGHT },
"Edit Color", 0, COLOR_THEME_PRIMARY2 | flags);
new StaticText(window,
{ PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT,
LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT },
ThemePersistance::getColorNames()[(int)_indexOfColor], 0, COLOR_THEME_PRIMARY2 | flags);
void setActiveColorBar(int activeTab)
{
if (activeTab >= 0 && _activeTab <= (int)_tabs.size()) {
_activeTab = activeTab;
for (auto i = 0; i < (int) _tabs.size(); i++)
_tabs[i]->check(i == _activeTab);
_colorEditor->setColorEditorType(_activeTab == 1 ? HSV_COLOR_EDITOR : RGB_COLOR_EDITOR);
}
}
// page tabs
rect_t r = { LCD_W - 2*(BUTTON_WIDTH + 5), 6, BUTTON_WIDTH, BUTTON_HEIGHT };
_tabs.emplace_back(
new TextButton(window, r, "RGB",
[=] () {
setActiveColorBar(0);
return 1;
}));
r.x += (BUTTON_WIDTH + 5);
_tabs.emplace_back(
new TextButton(window, r, "HSV",
[=] () {
setActiveColorBar(1);
return 1;
}));
_tabs[1]->check(true);
}
protected:
std::function<void ()> _updateHandler;
LcdColorIndex _indexOfColor;
ThemeFile *_theme;
TextButton *_cancelButton;
ColorEditor *_colorEditor;
PreviewWindow *_previewWindow = nullptr;
std::vector<Button *> _tabs;
int _activeTab = 0;
ColorSquare *_colorSquare = nullptr;
StaticText *_hexBox = nullptr;
void deleteLater(bool detach = true, bool trash = true) override
{
if (_updateHandler != nullptr)
_updateHandler();
Page::deleteLater(detach, trash);
}
void setHexStr(uint32_t rgb)
{
if (_hexBox) {
auto r = GET_RED(rgb), g = GET_GREEN(rgb), b = GET_BLUE(rgb);
char hexstr[8];
snprintf(hexstr, sizeof(hexstr), "%02X%02X%02X",
(uint16_t)r, (uint16_t)g, (uint16_t)b);
_hexBox->setText(hexstr);
}
}
void buildBody(FormWindow* window)
{
window->padAll(4);
FormWindow *form = new FormWindow(window, {0, 0, window->width() - 8, window->height() - 8});
form->padAll(0);
#if LCD_W > LCD_H
form->setFlexLayout(LV_FLEX_FLOW_ROW, 4);
rect_t r = {0, 0, COLOR_LIST_WIDTH, form->height()};
#else
form->setFlexLayout(LV_FLEX_FLOW_COLUMN, 4);
rect_t r = {0, 0, form->width(), COLOR_LIST_HEIGHT};
#endif
FormWindow *colForm = new FormWindow(form, r);
colForm->padAll(0);
colForm->setFlexLayout(LV_FLEX_FLOW_COLUMN, 4);
colForm->setWidth(r.w);
#if LCD_W > LCD_H
r.w = form->width() - COLOR_LIST_WIDTH - 4;
#else
r.h = form->height() - COLOR_LIST_HEIGHT - 4;
#endif
_previewWindow = new PreviewWindow(form, r, _theme->getColorList());
r.w = colForm->width();
r.h = COLOR_BOX_HEIGHT;
FormWindow *colBoxForm = new FormWindow(colForm, r);
colBoxForm->padAll(0);
colBoxForm->setFlexLayout(LV_FLEX_FLOW_ROW, 2);
r.h = colForm->height() - COLOR_BOX_HEIGHT - 4;
_colorEditor = new ColorEditor(colForm, r, _theme->getColorEntryByIndex(_indexOfColor)->colorValue,
[=](uint32_t rgb) {
_theme->setColor(_indexOfColor, rgb);
if (_colorSquare != nullptr) {
_colorSquare->setColorToEdit(rgb);
}
if (_previewWindow != nullptr) {
_previewWindow->setColorList(_theme->getColorList());
}
setHexStr(rgb);
});
_colorEditor->setColorEditorType(HSV_COLOR_EDITOR);
_activeTab = 1;
r.w = COLOR_BOX_WIDTH;
r.h = COLOR_BOX_HEIGHT;
_colorSquare = new ColorSquare(colBoxForm, r, _theme->getColorEntryByIndex(_indexOfColor)->colorValue);
// hexBox
r.w = 95;
_hexBox = new StaticText(colBoxForm, r, "", 0, COLOR_THEME_PRIMARY1 | FONT(L) | RIGHT);
setHexStr(_theme->getColorEntryByIndex(_indexOfColor)->colorValue);
}
void buildHead(PageHeader* window)
{
// page title
header.setTitle(STR_EDIT_COLOR);
new StaticText(window,
{ PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT,
LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT },
ThemePersistance::getColorNames()[(int)_indexOfColor], 0, COLOR_THEME_PRIMARY2);
// page tabs
rect_t r = { LCD_W - 2*(BUTTON_WIDTH + 5), 6, BUTTON_WIDTH, BUTTON_HEIGHT };
_tabs.emplace_back(
new TextButton(window, r, "RGB",
[=] () {
setActiveColorBar(0);
return 1;
}));
r.x += (BUTTON_WIDTH + 5);
_tabs.emplace_back(
new TextButton(window, r, "HSV",
[=] () {
setActiveColorBar(1);
return 1;
}));
_tabs[1]->check(true);
}
};
class ThemeEditPage : public Page
{
public:
explicit ThemeEditPage(ThemeFile theme, std::function<void (ThemeFile &theme)> saveHandler = nullptr) :
explicit ThemeEditPage(ThemeFile *theme, std::function<void (ThemeFile &theme)> saveHandler = nullptr) :
Page(ICON_RADIO_EDIT_THEME),
_theme(theme),
_theme(*theme),
page(this),
saveHandler(std::move(saveHandler))
{
@@ -368,7 +364,7 @@ class ThemeEditPage : public Page
buildHeader(&header);
}
bool canCancel() override
void onCancel() override
{
if (_dirty) {
new ConfirmDialog(
@@ -382,40 +378,49 @@ class ThemeEditPage : public Page
[=]() {
deleteLater();
});
return false;
} else {
deleteLater();
}
}
void checkEvents() override
{
if (!started && _themeName) {
started = true;
_themeName->setText(_theme.getName());
}
return true;
}
void editColorPage()
{
auto colorEntry = _cList->getSelectedColor();
new ColorEditPage(_theme, colorEntry.colorNumber,
[=] (uint32_t color) {
_dirty = true;
_theme.setColor(colorEntry.colorNumber, color);
_cList->setColorList(_theme.getColorList());
_previewWindow->setColorList(_theme.getColorList());
});
new ColorEditPage(&_theme, colorEntry.colorNumber,
[=] () {
_dirty = true;
_cList->setColorList(_theme.getColorList());
_previewWindow->setColorList(_theme.getColorList());
});
}
void buildHeader(FormGroup *window)
{
LcdFlags flags = 0;
if (LCD_W < LCD_H) {
flags = FONT(XS);
}
#if LCD_W > LCD_H
LcdFlags flags = 0;
#else
LcdFlags flags = FONT(XS);
#endif
// page title
header.setTitle(STR_EDIT_THEME);
_themeName = new StaticText(window,
{PAGE_TITLE_LEFT, PAGE_TITLE_TOP + PAGE_LINE_HEIGHT,
LCD_W - PAGE_TITLE_LEFT, PAGE_LINE_HEIGHT},
_theme.getName(), 0, COLOR_THEME_PRIMARY2 | flags);
"", 0, COLOR_THEME_PRIMARY2 | flags);
lv_label_set_long_mode(_themeName->getLvObj(), LV_LABEL_LONG_DOT);
// save and cancel
rect_t r = {LCD_W - (BUTTON_WIDTH + 5), 6, BUTTON_WIDTH, BUTTON_HEIGHT };
_detailButton = new TextButton(window, r, STR_DETAILS, [=] () {
new TextButton(window, r, STR_DETAILS, [=] () {
new ThemeDetailsDialog(page, _theme, [=] (ThemeFile t) {
_theme.setAuthor(t.getAuthor());
_theme.setInfo(t.getInfo());
@@ -431,53 +436,60 @@ class ThemeEditPage : public Page
void buildBody(FormGroup *window)
{
rect_t r = { LEFT_LIST_OFFSET, TOP_LIST_OFFSET, COLOR_LIST_WIDTH, COLOR_LIST_HEIGHT};
_cList = new ColorList(window, r, _theme.getColorList());
window->padAll(4);
FormWindow *form = new FormWindow(window, {0, 0, window->width() - 8, window->height() - 8});
form->padAll(0);
#if LCD_W > LCD_H
form->setFlexLayout(LV_FLEX_FLOW_ROW, 4);
rect_t r = {0, 0, COLOR_LIST_WIDTH, form->height()};
#else
form->setFlexLayout(LV_FLEX_FLOW_COLUMN, 4);
rect_t r = {0, 0, form->width(), COLOR_LIST_HEIGHT};
#endif
_cList = new ColorList(form, r, _theme.getColorList());
_cList->setLongPressHandler([=] () { editColorPage(); });
_cList->setPressHandler([=] () { editColorPage(); });
if (LCD_W > LCD_H) {
r = {
COLOR_LIST_WIDTH + MARGIN_WIDTH + LEFT_LIST_OFFSET,
TOP_LIST_OFFSET,
LCD_W - COLOR_LIST_WIDTH - LEFT_LIST_OFFSET - MARGIN_WIDTH * 2,
COLOR_LIST_HEIGHT
};
} else {
r = {
LEFT_LIST_OFFSET,
TOP_LIST_OFFSET + COLOR_LIST_HEIGHT + MARGIN_WIDTH,
LEFT_LIST_WIDTH,
COLOR_LIST_HEIGHT - (MARGIN_WIDTH * 2)
};
}
_previewWindow = new PreviewWindow(window, r, _theme.getColorList());
#if LCD_W > LCD_H
r.w = form->width() - COLOR_LIST_WIDTH - 4;
#else
r.h = form->height() - COLOR_LIST_HEIGHT - 4;
#endif
_previewWindow = new PreviewWindow(form, r, _theme.getColorList());
}
protected:
bool _dirty = false;
bool started = false;
ThemeFile _theme;
Page *page;
std::function<void(ThemeFile &theme)> saveHandler = nullptr;
PreviewWindow *_previewWindow = nullptr;
ColorList *_cList = nullptr;
StaticText *_themeName = nullptr;
TextButton *_saveButton;
TextButton *_detailButton;
};
ThemeSetupPage::ThemeSetupPage() : PageTab(STR_THEME_EDITOR, ICON_RADIO_EDIT_THEME) {}
ThemeSetupPage::~ThemeSetupPage()
{
}
void ThemeSetupPage::setAuthor(ThemeFile *theme)
{
char author[256];
strcpy(author, "By: ");
strcat(author, theme->getAuthor());
char author[256] = "";
if (theme && (strlen(theme->getAuthor()) > 0)) {
strcpy(author, "By: ");
strcat(author, theme->getAuthor());
}
authorText->setText(author);
}
void ThemeSetupPage::setName(ThemeFile *theme)
{
if (theme && (strlen(theme->getName()) > 0)) {
nameText->setText(theme->getName());
} else {
nameText->setText("");
}
}
bool isTopWindow(Window *window)
{
@@ -493,7 +505,15 @@ void ThemeSetupPage::checkEvents()
{
PageTab::checkEvents();
fileCarosell->pause(!isTopWindow(pageWindow));
if (fileCarosell)
fileCarosell->pause(!isTopWindow(pageWindow));
if (!started && nameText && authorText) {
auto theme = ThemePersistance::instance()->getCurrentTheme();
started = true;
setName(theme);
setAuthor(theme);
}
}
void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
@@ -509,6 +529,7 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
nameText->setTextFlags(COLOR_THEME_PRIMARY1);
authorText->setTextFlags(COLOR_THEME_PRIMARY1);
listBox->setActiveItem(idx);
TabsGroup::refreshTheme();
});
}
@@ -521,25 +542,26 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
auto theme = tp->getThemeByIndex(themeIdx);
if (theme == nullptr) return;
new ThemeEditPage(*theme, [=](ThemeFile &theme) {
theme.serialize();
new ThemeEditPage(theme, [=](ThemeFile &editedTheme) {
// update cached theme data
*theme = editedTheme;
theme->serialize();
// if the theme info currently displayed
// were changed, update the UI
if (themeIdx == currentTheme) {
setAuthor(&theme);
nameText->setText(theme.getName());
listBox->setName(currentTheme, theme.getName());
themeColorPreview->setColorList(theme.getColorList());
setAuthor(theme);
setName(theme);
listBox->setName(currentTheme, theme->getName());
themeColorPreview->setColorList(theme->getColorList());
}
// if the active theme changed, re-apply it
if (themeIdx == tp->getThemeIndex()) theme.applyTheme();
// update cached theme data
auto tp_theme = tp->getThemeByIndex(themeIdx);
if (!tp_theme) return;
*tp_theme = theme;
if (themeIdx == tp->getThemeIndex()) {
theme->applyTheme();
TabsGroup::refreshTheme();
}
});
});
}
@@ -586,33 +608,47 @@ void ThemeSetupPage::displayThemeMenu(Window *window, ThemePersistance *tp)
}
}
void ThemeSetupPage::setupListbox(FormWindow *window, rect_t r, ThemePersistance *tp)
void ThemeSetupPage::setSelected(ThemePersistance *tp)
{
auto value = listBox->getSelected();
if (themeColorPreview && authorText && nameText && fileCarosell) {
ThemeFile *theme = tp->getThemeByIndex(value);
if (theme) {
themeColorPreview->setColorList(theme->getColorList());
setAuthor(theme);
setName(theme);
fileCarosell->setFileNames(theme->getThemeImageFileNames());
}
currentTheme = value;
}
}
void ThemeSetupPage::setupListbox(Window *window, rect_t r, ThemePersistance *tp)
{
listBox = new ListBox(window, r, tp->getNames());
listBox->setAutoEdit(true);
listBox->setSelected(currentTheme);
listBox->setActiveItem(tp->getThemeIndex());
listBox->setTitle(STR_THEME + std::string("s")); // TODO: fix this!
listBox->setLongPressHandler([=] () { displayThemeMenu(window, tp); });
listBox->setLongPressHandler([=] () {
setSelected(tp);
displayThemeMenu(window, tp);
});
listBox->setPressHandler([=] () {
auto value = listBox->getSelected();
if (themeColorPreview && authorText && nameText && fileCarosell) {
ThemeFile *theme = tp->getThemeByIndex(value);
if (!theme) return;
themeColorPreview->setColorList(theme->getColorList());
setAuthor(theme);
nameText->setText(theme->getName());
fileCarosell->setFileNames(theme->getThemeImageFileNames());
}
currentTheme = value;
setSelected(tp);
});
}
void ThemeSetupPage::build(FormWindow *window)
{
window->padAll(0);
window->padAll(4);
pageWindow = window;
#if LCD_W > LCD_H
window->setFlexLayout(LV_FLEX_FLOW_ROW, 2);
#else
window->setFlexLayout(LV_FLEX_FLOW_COLUMN, 2);
#endif
auto tp = ThemePersistance::instance();
auto theme = tp->getCurrentTheme();
currentTheme = tp->getThemeIndex();
@@ -622,48 +658,50 @@ void ThemeSetupPage::build(FormWindow *window)
fileCarosell = nullptr;
nameText = nullptr;
authorText = nullptr;
// create listbox and setup menus
rect_t r = { LEFT_LIST_OFFSET, TOP_LIST_OFFSET, LEFT_LIST_WIDTH, LEFT_LIST_HEIGHT };
#if LCD_W > LCD_H
rect_t r = { 0, 0, LIST_WIDTH, window->height() - 8};
#else
rect_t r = { 0, 0, window->width() - 8, LIST_HEIGHT };
#endif
setupListbox(window, r, tp);
rect_t colorPreviewRect;
if (LCD_W > LCD_H) {
r.x = LEFT_LIST_WIDTH + MARGIN_WIDTH + LEFT_LIST_OFFSET;
r.w = LCD_W - r.x;
colorPreviewRect = {LEFT_LIST_WIDTH + 6, 0, COLOR_PREVIEW_WIDTH, COLOR_PREVIEW_HEIGHT};
} else {
r.y += LEFT_LIST_HEIGHT + 4;
r.x = 0;
r.w = LCD_W;
colorPreviewRect = {0, LEFT_LIST_HEIGHT + 6, COLOR_PREVIEW_WIDTH, COLOR_PREVIEW_HEIGHT};
}
#if LCD_W > LCD_H
r.w = COLOR_PREVIEW_SIZE;
#else
r.h = COLOR_PREVIEW_SIZE;
#endif
// setup ThemeColorPreview()
auto colorList = theme != nullptr ? theme->getColorList() : std::vector<ColorEntry>();
themeColorPreview = new ThemeColorPreview(window, colorPreviewRect, colorList);
themeColorPreview = new ThemeColorPreview(window, r, colorList);
themeColorPreview->setWidth(r.w);
#if LCD_W > LCD_H
r.w = window->width() - LIST_WIDTH - COLOR_PREVIEW_SIZE - 12;
r.h = window->height() - 8;
#else
r.w = window->width() - 8;
r.h = window->height() - LIST_HEIGHT - COLOR_PREVIEW_SIZE - 12;
#endif
auto rw = new FormWindow(window, r);
rw->padAll(0);
rw->setFlexLayout(LV_FLEX_FLOW_COLUMN, 2);
rw->setWidth(r.w);
r.h -= 46;
// setup FileCarosell()
if (LCD_W > LCD_H) {
r.w -= COLOR_PREVIEW_WIDTH;
r.h = 130;
r.x += COLOR_PREVIEW_WIDTH;
} else {
r.y += COLOR_PREVIEW_HEIGHT;
r.h = LEFT_LIST_HEIGHT - COLOR_PREVIEW_HEIGHT - 50;
}
auto fileNames = theme != nullptr ? theme->getThemeImageFileNames() : std::vector<std::string>();
fileCarosell = new FileCarosell(window, r, fileNames);
fileCarosell = new FileCarosell(rw, r, fileNames);
r.h = 20;
// author and name of theme on right side of screen
r.x += 7;
r.y += 135;
r.h = 20;
nameText = new StaticText(window, r, theme != nullptr ? theme->getName() : "", 0,
COLOR_THEME_PRIMARY1);
if (theme != nullptr && strlen(theme->getAuthor())) {
r.y += 20;
authorText = new StaticText(window, r, "", 0, COLOR_THEME_PRIMARY1);
setAuthor(theme);
}
nameText = new StaticText(rw, r, "", 0, COLOR_THEME_PRIMARY1);
lv_label_set_long_mode(nameText->getLvObj(), LV_LABEL_LONG_DOT);
authorText = new StaticText(rw, r, "", 0, COLOR_THEME_PRIMARY1);
lv_label_set_long_mode(authorText->getLvObj(), LV_LABEL_LONG_DOT);
}
+5 -2
View File
@@ -26,7 +26,6 @@ class ThemeColorPreview;
class ThemeSetupPage: public PageTab {
public:
ThemeSetupPage();
~ThemeSetupPage();
void build(FormWindow * window) override;
void checkEvents() override;
@@ -40,7 +39,11 @@ class ThemeSetupPage: public PageTab {
StaticText *authorText = nullptr;
StaticText *nameText = nullptr;
int currentTheme = 0;
void setupListbox(FormWindow *window, rect_t r, ThemePersistance *tp);
bool started = false;
void setupListbox(Window *window, rect_t r, ThemePersistance *tp);
void displayThemeMenu(Window *window, ThemePersistance *tp);
void setAuthor(ThemeFile *theme);
void setName(ThemeFile *theme);
void setSelected(ThemePersistance *tp);
};
+15 -5
View File
@@ -139,21 +139,36 @@ static constexpr rect_t _get_body_rect()
return { 0, MENU_BODY_TOP, LCD_W, MENU_BODY_HEIGHT };
}
TabsGroup* TabsGroup::activeTabsGroup = nullptr;
TabsGroup::TabsGroup(uint8_t icon):
Window(Layer::back(), { 0, 0, LCD_W, LCD_H }, OPAQUE),
header(this, icon),
body(this, _get_body_rect(), NO_FOCUS | FORM_FORWARD_FOCUS)
{
Layer::push(this);
lv_obj_set_style_bg_color(lvobj, makeLvColor(COLOR_THEME_SECONDARY3), 0);
activeTabsGroup = this;
}
TabsGroup::~TabsGroup()
{
if (activeTabsGroup == this)
activeTabsGroup = nullptr;
for (auto tab: tabs) {
delete tab;
}
}
void TabsGroup::refreshTheme()
{
if (activeTabsGroup)
lv_obj_set_style_bg_color(activeTabsGroup->getLvObj(), makeLvColor(COLOR_THEME_SECONDARY3), 0);
}
void TabsGroup::deleteLater(bool detach, bool trash)
{
if (_deleted)
@@ -292,11 +307,6 @@ void TabsGroup::onCancel()
deleteLater();
}
void TabsGroup::paint(BitmapBuffer * dc)
{
dc->clear(COLOR_THEME_SECONDARY3);
}
#if defined(HARDWARE_TOUCH)
bool TabsGroup::onTouchEnd(coord_t x, coord_t y)
{
+2 -2
View File
@@ -213,18 +213,18 @@ class TabsGroup: public Window
void onClicked() override;
void onCancel() override;
void paint(BitmapBuffer * dc) override;
#if defined(HARDWARE_TOUCH)
bool onTouchEnd(coord_t x, coord_t y) override;
#endif
static void refreshTheme();
protected:
TabsGroupHeader header;
FormWindow body;
std::vector<PageTab *> tabs;
PageTab * currentTab = nullptr;
static TabsGroup* activeTabsGroup;
void setVisibleTab(PageTab * tab);
};
+38 -64
View File
@@ -47,10 +47,8 @@ ThemeFile::ThemeFile(std::string themePath) :
int n = 0;
while (n < MAX_FILES) {
auto baseFileName(path.substr(0, found + 1) + (n != 0 ? "screenshot" + std::to_string(n) : "logo") + ".png");
FRESULT result = f_open(&file, baseFileName.c_str(), FA_OPEN_EXISTING);
if (result == FR_OK) {
if (isFileAvailable(baseFileName.c_str(), true)) {
_imageFileNames.emplace_back(baseFileName);
f_close(&file);
} else {
break;
}
@@ -66,9 +64,9 @@ ThemeFile& ThemeFile::operator= (const ThemeFile& theme)
return *this;
path = theme.path;
strncpy(name, theme.name, NAME_LENGTH);
strncpy(author, theme.author, AUTHOR_LENGTH);
strncpy(info, theme.info, INFO_LENGTH);
strAppend(name, theme.name, NAME_LENGTH);
strAppend(author, theme.author, AUTHOR_LENGTH);
strAppend(info, theme.info, INFO_LENGTH);
colorList.assign(theme.colorList.begin(), theme.colorList.end());
_imageFileNames.assign(theme._imageFileNames.begin(), theme._imageFileNames.end());
@@ -149,9 +147,8 @@ void ThemeFile::deSerialize()
char *plvalue;
char *prvalue;
strncpy(lvalue, line, min((int)(ptr - line), 63));
lvalue[ptr - line] = '\0';
strncpy(rvalue, ptr + 1, 63);
strAppend(lvalue, line, min((int)(ptr - line), 63));
strAppend(rvalue, ptr + 1, 63);
plvalue = trim(lvalue);
prvalue = trim(rvalue);
@@ -169,14 +166,11 @@ void ThemeFile::deSerialize()
case summary: {
if (strcmp(plvalue, "name") == 0) {
strncpy(name, prvalue, NAME_LENGTH);
name[NAME_LENGTH] = '\0';
strAppend(name, prvalue, NAME_LENGTH);
} else if (strcmp(plvalue, "author") == 0) {
strncpy(author, prvalue, AUTHOR_LENGTH);
author[AUTHOR_LENGTH] = '\0';
strAppend(author, prvalue, AUTHOR_LENGTH);
} else if (strcmp(plvalue, "info") == 0) {
strncpy(info, prvalue, INFO_LENGTH);
info[INFO_LENGTH] = '\0';
strAppend(info, prvalue, INFO_LENGTH);
}
} break;
@@ -347,14 +341,11 @@ void ThemePersistance::clearThemes()
void ThemePersistance::scanThemeFolder(char *fullPath)
{
FIL file;
strncat(fullPath, "/theme.yml", FF_MAX_LFN);
FRESULT result = f_open(&file, fullPath, FA_OPEN_EXISTING | FA_READ);
if (result != FR_OK) return;
f_close(&file);
TRACE("scanForThemes: found file %s", fullPath);
themes.emplace_back(new ThemeFile(fullPath));
if (isFileAvailable(fullPath, true)) {
TRACE("scanForThemes: found file %s", fullPath);
themes.emplace_back(new ThemeFile(fullPath));
}
}
void ThemePersistance::scanForThemes()
@@ -366,8 +357,7 @@ void ThemePersistance::scanForThemes()
char fullPath[FF_MAX_LFN + 1];
strncpy(fullPath, THEMES_PATH, FF_MAX_LFN);
fullPath[FF_MAX_LFN] = '\0';
strAppend(fullPath, THEMES_PATH, FF_MAX_LFN);
TRACE("opening directory: %s", fullPath);
FRESULT res = f_opendir(&dir, fullPath); // Open the directory
@@ -384,9 +374,9 @@ void ThemePersistance::scanForThemes()
if (strlen((const char *)fno.fname) > SD_SCREEN_FILE_LENGTH) continue;
if (fno.fattrib & AM_DIR) {
char themePath[FF_MAX_LFN + 1];
strncpy(themePath, fullPath, FF_MAX_LFN);
strncat(themePath, "/", FF_MAX_LFN);
strncat(themePath, fno.fname, FF_MAX_LFN);
char *s = strAppend(themePath, fullPath, FF_MAX_LFN);
s = strAppend(s, "/", FF_MAX_LFN - (s - themePath));
strAppend(s, fno.fname, FF_MAX_LFN - (s - themePath));
scanThemeFolder(themePath);
}
}
@@ -416,15 +406,23 @@ void ThemePersistance::loadDefaultTheme()
line[len] = '\0';
int index = 0;
bool found = false;
for (auto theme : themes) {
if (theme->getPath() == std::string(line)) {
applyTheme(index);
setThemeIndex(index);
found = true;
break;
}
index++;
}
// Force default if no match for last selected theme
if (!found)
index = 0;
applyTheme(index);
setThemeIndex(index);
}
f_close(&file);
}
@@ -439,8 +437,8 @@ bool ThemePersistance::deleteThemeByIndex(int index)
if (index > 0 && index < (int) themes.size()) {
ThemeFile* theme = themes[index];
char newFile[FF_MAX_LFN + 1];
strncpy(newFile, theme->getPath().c_str(), FF_MAX_LFN);
char newFile[FF_MAX_LFN + 10];
strAppend(newFile, theme->getPath().c_str(), FF_MAX_LFN);
strcat(newFile, ".deleted");
// for now we are just renaming the file so we don't find it
@@ -459,27 +457,19 @@ bool ThemePersistance::deleteThemeByIndex(int index)
bool ThemePersistance::createNewTheme(std::string name, ThemeFile &theme)
{
char fullPath[FF_MAX_LFN + 1];
strncpy(fullPath, THEMES_PATH, FF_MAX_LFN);
fullPath[FF_MAX_LFN] = '\0';
strncat(fullPath, "/", FF_MAX_LFN);
strncat(fullPath, name.c_str(), FF_MAX_LFN);
char *s = strAppend(fullPath, THEMES_PATH, FF_MAX_LFN);
s = strAppend(s, "/", FF_MAX_LFN - (s - fullPath));
s = strAppend(s, name.c_str(), FF_MAX_LFN - (s - fullPath));
FRESULT result = f_mkdir(fullPath);
if (result != FR_OK) return false;
strncat(fullPath, "/", FF_MAX_LFN);
strncat(fullPath, "theme.yml", FF_MAX_LFN);
s = strAppend(s, "/", FF_MAX_LFN - (s - fullPath));
strAppend(s, "theme.yml", FF_MAX_LFN - (s - fullPath));
theme.setPath(fullPath);
theme.serialize();
return true;
}
void ThemePersistance::deleteDefaultTheme()
{
FIL file;
FRESULT status = f_open(&file, SELECTED_THEME_FILE, FA_CREATE_ALWAYS | FA_WRITE);
if (status == FR_OK) f_close(&file);
}
void ThemePersistance::setDefaultTheme(int index)
{
FIL file;
@@ -489,7 +479,8 @@ void ThemePersistance::setDefaultTheme(int index)
if (status != FR_OK) return;
currentTheme = index;
f_printf(&file, theme->getPath().c_str());
if (index > 0)
f_printf(&file, theme->getPath().c_str());
f_close(&file);
}
}
@@ -497,7 +488,7 @@ void ThemePersistance::setDefaultTheme(int index)
class DefaultEdgeTxTheme : public ThemeFile
{
public:
DefaultEdgeTxTheme() : ThemeFile("")
DefaultEdgeTxTheme() : ThemeFile(THEMES_PATH"/EdgeTX/")
{
setName("EdgeTX Default");
setAuthor("EdgeTX Team");
@@ -516,23 +507,6 @@ class DefaultEdgeTxTheme : public ThemeFile
colorList.emplace_back(ColorEntry { COLOR_THEME_WARNING_INDEX, RGB(224, 0, 0) });
colorList.emplace_back(ColorEntry { COLOR_THEME_DISABLED_INDEX, RGB(140, 140, 140) });
}
void applyBackground() override
{
auto instance = OpenTxTheme::instance();
char fileName[FF_MAX_LFN + 1];
fileName[FF_MAX_LFN] = '\0';
strncpy(fileName, THEMES_PATH, FF_MAX_LFN);
strcat(fileName, "/EdgeTX/background.png");
instance->setBackgroundImageFileName(fileName);
}
std::vector<std::string> getThemeImageFileNames() override
{
std::vector<std::string> fileNames;
fileNames.emplace_back("/THEMES/EdgeTX/EdgeTX.png");
return fileNames;
}
};
void ThemePersistance::insertDefaultTheme()
+6 -10
View File
@@ -61,12 +61,9 @@ class ThemeFile
_imageFileNames(theme._imageFileNames)
{
path = theme.path;
strncpy(name, theme.name, NAME_LENGTH);
name[NAME_LENGTH] = '\0';
strncpy(author, theme.author, AUTHOR_LENGTH);
author[AUTHOR_LENGTH] = '\0';
strncpy(info, theme.info, INFO_LENGTH);
info[INFO_LENGTH] = '\0';
strAppend(name, theme.name, NAME_LENGTH);
strAppend(author, theme.author, AUTHOR_LENGTH);
strAppend(info, theme.info, INFO_LENGTH);
}
virtual ~ThemeFile() {}
@@ -101,9 +98,9 @@ class ThemeFile
return 0;
}
void setName(std::string name) { strncpy(this->name, name.c_str(), NAME_LENGTH); }
void setAuthor(std::string author) { strncpy(this->author, author.c_str(), AUTHOR_LENGTH); }
void setInfo(std::string info) { strncpy(this->info, info.c_str(), INFO_LENGTH); }
void setName(std::string name) { strAppend(this->name, name.c_str(), NAME_LENGTH); }
void setAuthor(std::string author) { strAppend(this->author, author.c_str(), AUTHOR_LENGTH); }
void setInfo(std::string info) { strAppend(this->info, info.c_str(), INFO_LENGTH); }
void setPath(std::string path) { this->path = path; }
std::vector<ColorEntry>& getColorList() { return colorList; }
@@ -150,7 +147,6 @@ class ThemePersistance
void loadDefaultTheme();
void setDefaultTheme(int index);
void deleteDefaultTheme();
static char **getColorNames();
bool deleteThemeByIndex(int index);
bool createNewTheme(std::string name, ThemeFile &theme);
+1
View File
@@ -723,6 +723,7 @@ const char STR_MODEL_QUICK_SELECT[] = TR_MODEL_QUICK_SELECT;
const char STR_LOADING[] = TR_LOADING;
const char STR_DELETE_THEME[] = TR_DELETE_THEME;
const char STR_SAVE_THEME[] = TR_SAVE_THEME;
const char STR_EDIT_COLOR[] = TR_EDIT_COLOR;
#endif
#if !defined(COLORLCD)
+1
View File
@@ -744,6 +744,7 @@ extern const char STR_MODEL_QUICK_SELECT[];
extern const char STR_LOADING[];
extern const char STR_DELETE_THEME[];
extern const char STR_SAVE_THEME[];
extern const char STR_EDIT_COLOR[];
#endif
extern const char STR_EXECUTE_FILE[];
extern const char STR_DELETE_FILE[];
+1
View File
@@ -742,6 +742,7 @@
#define TR_LOADING "加载中..."
#define TR_DELETE_THEME "删除主题?"
#define TR_SAVE_THEME "保存主题?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "快速选择模型"
+1
View File
@@ -742,6 +742,7 @@
#define TR_LOADING "Načítání..."
#define TR_DELETE_THEME "Smazat motiv?"
#define TR_SAVE_THEME "Uložit motiv?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Rychlý výběr modelu"
+1
View File
@@ -748,6 +748,7 @@
#define TR_LOADING "Indlæser..."
#define TR_DELETE_THEME "Slet tema?"
#define TR_SAVE_THEME "Gem tema?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Hurtigvalg af model"
+1
View File
@@ -740,6 +740,7 @@
#define TR_LOADING "Wird geladen..."
#define TR_DELETE_THEME "Theme löschen?"
#define TR_SAVE_THEME "Theme speichern?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "schnelle Modellauswahl"
+1
View File
@@ -741,6 +741,7 @@
#define TR_LOADING "Loading..."
#define TR_DELETE_THEME "Delete Theme?"
#define TR_SAVE_THEME "Save Theme?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Model quick select"
+1
View File
@@ -742,6 +742,7 @@
#define TR_LOADING "Loading..."
#define TR_DELETE_THEME "Delete Theme?"
#define TR_SAVE_THEME "Save Theme?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Model quick select"
+1
View File
@@ -754,6 +754,7 @@
#define TR_LOADING "Loading..."
#define TR_DELETE_THEME "Delete Theme?"
#define TR_SAVE_THEME "Save Theme?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Mallin pikavalinta"
+1
View File
@@ -751,6 +751,7 @@
#define TR_LOADING "Loading..."
#define TR_DELETE_THEME "Delete Theme?"
#define TR_SAVE_THEME "Save Theme?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Model quick select"
+1
View File
@@ -743,6 +743,7 @@
#define TR_LOADING "Caricamento..."
#define TR_DELETE_THEME "Cancello Tema?"
#define TR_SAVE_THEME "Salvo Tema?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Selezione veloce modello"
+1
View File
@@ -742,6 +742,7 @@
#define TR_LOADING "読み込み中..."
#define TR_DELETE_THEME "テーマを削除しますか?"
#define TR_SAVE_THEME "テーマを保存しますか?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "モデル クイックセレクト"
+1
View File
@@ -746,6 +746,7 @@
#define TR_LOADING "Loading..."
#define TR_DELETE_THEME "Delete Theme?"
#define TR_SAVE_THEME "Save Theme?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Model quick select"
+1
View File
@@ -739,6 +739,7 @@
#define TR_LOADING "Ładowanie..."
#define TR_DELETE_THEME "Usunąć motyw?"
#define TR_SAVE_THEME "Zapisać motyw?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Szybki wybór modelu"
+1
View File
@@ -746,6 +746,7 @@
#define TR_LOADING "Loading..."
#define TR_DELETE_THEME "Delete Theme?"
#define TR_SAVE_THEME "Save Theme?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Model quick select"
+1
View File
@@ -763,6 +763,7 @@
#define TR_LOADING "Laddar..."
#define TR_DELETE_THEME "Radera tema?"
#define TR_SAVE_THEME "Spara tema?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "Snabbval av modell"
+1
View File
@@ -741,6 +741,7 @@
#define TR_LOADING "加載中..."
#define TR_DELETE_THEME "刪除主題?"
#define TR_SAVE_THEME "保存主題?"
#define TR_EDIT_COLOR "Edit Color"
#if defined(COLORLCD)
#define TR_MODEL_QUICK_SELECT "快速選擇模型"