From 2700098c0864dd25ae00e4cb554fa7af21d817e4 Mon Sep 17 00:00:00 2001 From: Damien Nicolet Date: Fri, 13 Dec 2019 01:02:14 +0100 Subject: [PATCH] External application launcher app --- apps/external/Makefile | 30 +++++ apps/external/app.cpp | 44 +++++++ apps/external/app.h | 37 ++++++ apps/external/app/sample.c | 6 + apps/external/app/sources.mak | 3 + apps/external/archive.cpp | 141 ++++++++++++++++++++++ apps/external/archive.h | 27 +++++ apps/external/base.de.i18n | 4 + apps/external/base.en.i18n | 4 + apps/external/base.es.i18n | 4 + apps/external/base.fr.i18n | 4 + apps/external/base.pt.i18n | 4 + apps/external/extapp_api.cpp | 85 +++++++++++++ apps/external/extapp_api.h | 76 ++++++++++++ apps/external/external_icon.png | Bin 0 -> 10728 bytes apps/external/main_controller.cpp | 98 +++++++++++++++ apps/external/main_controller.h | 34 ++++++ apps/external/pointer_text_table_cell.cpp | 38 ++++++ apps/external/pointer_text_table_cell.h | 20 +++ build/config.mak | 2 +- 20 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 apps/external/Makefile create mode 100644 apps/external/app.cpp create mode 100644 apps/external/app.h create mode 100644 apps/external/app/sample.c create mode 100644 apps/external/app/sources.mak create mode 100644 apps/external/archive.cpp create mode 100644 apps/external/archive.h create mode 100644 apps/external/base.de.i18n create mode 100644 apps/external/base.en.i18n create mode 100644 apps/external/base.es.i18n create mode 100644 apps/external/base.fr.i18n create mode 100644 apps/external/base.pt.i18n create mode 100644 apps/external/extapp_api.cpp create mode 100644 apps/external/extapp_api.h create mode 100644 apps/external/external_icon.png create mode 100644 apps/external/main_controller.cpp create mode 100644 apps/external/main_controller.h create mode 100644 apps/external/pointer_text_table_cell.cpp create mode 100644 apps/external/pointer_text_table_cell.h diff --git a/apps/external/Makefile b/apps/external/Makefile new file mode 100644 index 000000000..b767dad8d --- /dev/null +++ b/apps/external/Makefile @@ -0,0 +1,30 @@ +apps += External::App +app_headers += apps/external/app.h + +app_external_src = $(addprefix apps/external/,\ + app.cpp \ + extapp_api.cpp \ + archive.cpp \ + main_controller.cpp \ + pointer_text_table_cell.cpp \ +) + +SFLAGS += -Iapps/external/ + +ifeq ($(PLATFORM),device) + SFLAGS += -DDEVICE +else + include apps/external/app/sources.mak +endif + +app_src += $(app_external_src) + +i18n_files += $(addprefix apps/external/,\ + base.de.i18n\ + base.en.i18n\ + base.es.i18n\ + base.fr.i18n\ + base.pt.i18n\ +) + +$(eval $(call depends_on_image,apps/external/app.cpp,apps/external/external_icon.png)) diff --git a/apps/external/app.cpp b/apps/external/app.cpp new file mode 100644 index 000000000..ac0f6141c --- /dev/null +++ b/apps/external/app.cpp @@ -0,0 +1,44 @@ +#include "app.h" +#include "external_icon.h" +#include + +namespace External { + +I18n::Message App::Descriptor::name() { + return I18n::Message::ExternalApp; +} + +I18n::Message App::Descriptor::upperName() { + return I18n::Message::ExternalAppCapital; +} + +const Image * App::Descriptor::icon() { + return ImageStore::ExternalIcon; +} + +App * App::Snapshot::unpack(Container * container) { + return new (container->currentAppBuffer()) App(this); +} + +App::Descriptor * App::Snapshot::descriptor() { + static Descriptor descriptor; + return &descriptor; +} + +void App::didBecomeActive(Window * window) { + ::App::didBecomeActive(window); + m_window = window; +} + +void App::redraw() { + m_window->redraw(true); +} + +App::App(Snapshot * snapshot) : + ::App(snapshot, &m_stackViewController), + m_mainController(&m_stackViewController, this), + m_stackViewController(&m_modalViewController, &m_mainController) +{ +} + +} diff --git a/apps/external/app.h b/apps/external/app.h new file mode 100644 index 000000000..e1dd47043 --- /dev/null +++ b/apps/external/app.h @@ -0,0 +1,37 @@ +#ifndef EXTERNAL_APP_H +#define EXTERNAL_APP_H + +#include +#include "main_controller.h" + +namespace External { + +class App : public ::App { +public: + class Descriptor : public ::App::Descriptor { + public: + I18n::Message name() override; + I18n::Message upperName() override; + const Image * icon() override; + }; + class Snapshot : public ::App::Snapshot { + public: + App * unpack(Container * container) override; + Descriptor * descriptor() override; + }; + void redraw(); + virtual void didBecomeActive(Window * window); + int heapSize() { return k_externalHeapSize; } + char * heap() { return m_externalHeap; } +private: + App(Snapshot * snapshot); + MainController m_mainController; + StackViewController m_stackViewController; + Window * m_window; + static constexpr int k_externalHeapSize = 131072; + char m_externalHeap[k_externalHeapSize]; +}; + +} + +#endif diff --git a/apps/external/app/sample.c b/apps/external/app/sample.c new file mode 100644 index 000000000..dbc13fd95 --- /dev/null +++ b/apps/external/app/sample.c @@ -0,0 +1,6 @@ +#include + +void extapp_main() { + extapp_pushRectUniform(10, 10, LCD_WIDTH-20, LCD_HEIGHT-20, 0); + extapp_msleep(1000); +} diff --git a/apps/external/app/sources.mak b/apps/external/app/sources.mak new file mode 100644 index 000000000..3f285e23a --- /dev/null +++ b/apps/external/app/sources.mak @@ -0,0 +1,3 @@ +app_external_src += $(addprefix apps/external/app/,\ + sample.c \ +) \ No newline at end of file diff --git a/apps/external/archive.cpp b/apps/external/archive.cpp new file mode 100644 index 000000000..aaa89373a --- /dev/null +++ b/apps/external/archive.cpp @@ -0,0 +1,141 @@ +#include "archive.h" +#include "extapp_api.h" + +#include +#include + +namespace External { +namespace Archive { + +#ifdef DEVICE + +struct TarHeader +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[8]; /* 257 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char padding[167]; /* 345 */ +} __attribute__((packed)); + +static_assert(sizeof(TarHeader) == 512); + +bool isSane(const TarHeader* tar) { + return !memcmp(tar->magic, "ustar ", 8) && tar->name[0] != '\x00' && tar->name[0] != '\xFF'; +} + +bool fileAtIndex(size_t index, File &entry) { + const TarHeader* tar = reinterpret_cast(0x90200000); + unsigned size = 0; + + // Sanity check. + if (!isSane(tar)) { + return false; + } + + /** + * TAR files are comprised of a set of records aligned to 512 bytes boundary + * followed by data. + */ + while (index-- > 0) { + size = 0; + for (int i = 0; i < 11; i++) + size = size * 8 + (tar->size[i] - '0'); + + // Move to the next TAR header. + unsigned stride = (sizeof(TarHeader) + size + 511); + stride = (stride >> 9) << 9; + tar = reinterpret_cast(reinterpret_cast(tar) + stride); + + // Sanity check. + if (!isSane(tar)) { + return false; + } + } + + // File entry found, copy data out. + entry.name = tar->name; + entry.data = reinterpret_cast(tar) + sizeof(TarHeader); + entry.dataLength = size; + entry.isExecutable = (tar->mode[4] & 0x01) == 1; + + return true; +} + +extern "C" void (* const apiPointers[])(void); +typedef uint32_t (*entrypoint)(const uint32_t, const void *, void *, const uint32_t); + +uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { + File entry; + if(fileAtIndex(indexFromName(name), entry)) { + if(!entry.isExecutable) { + return 0; + } + uint32_t ep = *reinterpret_cast(entry.data); + if(ep >= 0x90200000 && ep < 0x90800000) { + return ((entrypoint)ep)(API_VERSION, apiPointers, heap, heapSize); + } + } + return -1; +} + +int indexFromName(const char *name) { + File entry; + + for (int i = 0; fileAtIndex(i, entry); i++) { + if (strcmp(name, entry.name) == 0) { + return i; + } + } + + return -1; +} + +size_t numberOfFiles() { + File dummy; + size_t count; + + for (count = 0; fileAtIndex(count, dummy); count++); + + return count; +} + +#else + +bool fileAtIndex(size_t index, File &entry) { + entry.name = "App"; + entry.data = NULL; + entry.dataLength = 0; + entry.isExecutable = true; + return true; +} + +extern "C" void extapp_main(void); + +uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize) { + extapp_main(); + return 0; +} + +int indexFromName(const char *name) { + return 0; +} + +size_t numberOfFiles() { + return 1; +} + +#endif + +} +} diff --git a/apps/external/archive.h b/apps/external/archive.h new file mode 100644 index 000000000..80b71d0e1 --- /dev/null +++ b/apps/external/archive.h @@ -0,0 +1,27 @@ +#ifndef EXTERNAL_ARCHIVE_H +#define EXTERNAL_ARCHIVE_H + +#include +#include + +namespace External { +namespace Archive { + +constexpr int MaxNameLength = 40; + +struct File { + const char *name; + const uint8_t *data; + size_t dataLength; + bool isExecutable; +}; + +bool fileAtIndex(size_t index, File &entry); +int indexFromName(const char *name); +size_t numberOfFiles(); +uint32_t executeFile(const char *name, void * heap, const uint32_t heapSize); + +} +} + +#endif diff --git a/apps/external/base.de.i18n b/apps/external/base.de.i18n new file mode 100644 index 000000000..6072a563a --- /dev/null +++ b/apps/external/base.de.i18n @@ -0,0 +1,4 @@ +ExternalApp = "External" +ExternalAppCapital = "EXTERNAL" +ExternalAppApiMismatch = "API mismatch" +ExternalAppExecError = "Cannot execute file" diff --git a/apps/external/base.en.i18n b/apps/external/base.en.i18n new file mode 100644 index 000000000..6072a563a --- /dev/null +++ b/apps/external/base.en.i18n @@ -0,0 +1,4 @@ +ExternalApp = "External" +ExternalAppCapital = "EXTERNAL" +ExternalAppApiMismatch = "API mismatch" +ExternalAppExecError = "Cannot execute file" diff --git a/apps/external/base.es.i18n b/apps/external/base.es.i18n new file mode 100644 index 000000000..6072a563a --- /dev/null +++ b/apps/external/base.es.i18n @@ -0,0 +1,4 @@ +ExternalApp = "External" +ExternalAppCapital = "EXTERNAL" +ExternalAppApiMismatch = "API mismatch" +ExternalAppExecError = "Cannot execute file" diff --git a/apps/external/base.fr.i18n b/apps/external/base.fr.i18n new file mode 100644 index 000000000..6072a563a --- /dev/null +++ b/apps/external/base.fr.i18n @@ -0,0 +1,4 @@ +ExternalApp = "External" +ExternalAppCapital = "EXTERNAL" +ExternalAppApiMismatch = "API mismatch" +ExternalAppExecError = "Cannot execute file" diff --git a/apps/external/base.pt.i18n b/apps/external/base.pt.i18n new file mode 100644 index 000000000..6072a563a --- /dev/null +++ b/apps/external/base.pt.i18n @@ -0,0 +1,4 @@ +ExternalApp = "External" +ExternalAppCapital = "EXTERNAL" +ExternalAppApiMismatch = "API mismatch" +ExternalAppExecError = "Cannot execute file" diff --git a/apps/external/extapp_api.cpp b/apps/external/extapp_api.cpp new file mode 100644 index 000000000..1e7fd7aeb --- /dev/null +++ b/apps/external/extapp_api.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include +#include +#include "extapp_api.h" + +#include + +extern "C" { + #include +} + +uint64_t extapp_millis() { + return Ion::Timing::millis(); +} + +void extapp_msleep(uint32_t ms) { + Ion::Timing::msleep(ms); +} + +uint64_t extapp_scanKeyboard() { + return Ion::Keyboard::scan(); +} + +void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t * pixels) { + KDRect rect(x, y, w, h); + + Ion::Display::pushRect(rect, reinterpret_cast(pixels)); +} + +void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color) { + KDRect rect(x, y, w, h); + + Ion::Display::pushRectUniform(rect, KDColor::RGB16(color)); +} + +int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg) { + KDPoint point(x, y); + + auto ctx = KDIonContext::sharedContext(); + ctx->setClippingRect(KDRect(0,0,320,240)); + ctx->setOrigin(KDPoint(0,0)); + ctx->drawString(text, point, KDFont::LargeFont, KDColor::RGB16(fg), KDColor::RGB16(bg)); + + return point.x(); +} + +int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg) { + KDPoint point(x, y); + + auto ctx = KDIonContext::sharedContext(); + ctx->setClippingRect(KDRect(0,0,320,240)); + ctx->setOrigin(KDPoint(0,0)); + ctx->drawString(text, point, KDFont::SmallFont, KDColor::RGB16(fg), KDColor::RGB16(bg)); + + return point.x(); +} + +void extapp_waitForVBlank() { + Ion::Display::waitForVBlank(); +} + +void extapp_clipboardStore(const char *text) { + Clipboard::sharedClipboard()->store(text); +} + +const char * extapp_clipboardText() { + return Clipboard::sharedClipboard()->storedText(); +} + +extern "C" void (* const apiPointers[])(void) = { + (void (*)(void)) extapp_millis, + (void (*)(void)) extapp_msleep, + (void (*)(void)) extapp_scanKeyboard, + (void (*)(void)) extapp_pushRect, + (void (*)(void)) extapp_pushRectUniform, + (void (*)(void)) extapp_drawTextLarge, + (void (*)(void)) extapp_drawTextSmall, + (void (*)(void)) extapp_waitForVBlank, + (void (*)(void)) extapp_clipboardStore, + (void (*)(void)) extapp_clipboardText, + (void (*)(void)) nullptr +}; diff --git a/apps/external/extapp_api.h b/apps/external/extapp_api.h new file mode 100644 index 000000000..12ae53ba6 --- /dev/null +++ b/apps/external/extapp_api.h @@ -0,0 +1,76 @@ +#ifndef EXTERNAL_API_H +#define EXTERNAL_API_H + +#include + +#define API_VERSION 1 + +#ifdef __cplusplus +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +#define LCD_WIDTH 320 +#define LCD_HEIGHT 240 + +#define KEY_Left ((uint64_t)1 << 0) +#define KEY_Up ((uint64_t)1 << 1) +#define KEY_Down ((uint64_t)1 << 2) +#define KEY_Right ((uint64_t)1 << 3) +#define KEY_OK ((uint64_t)1 << 4) +#define KEY_Back ((uint64_t)1 << 5) +#define KEY_Home ((uint64_t)1 << 6) +#define KEY_OnOff (((uint64_t)1 << 7) || ((uint64_t)1 << 8)) +#define KEY_Shift ((uint64_t)1 << 12) +#define KEY_Alpha ((uint64_t)1 << 13) +#define KEY_XNT ((uint64_t)1 << 14) +#define KEY_Var ((uint64_t)1 << 15) +#define KEY_Toolbox ((uint64_t)1 << 16) +#define KEY_Backspace ((uint64_t)1 << 17) +#define KEY_Exp ((uint64_t)1 << 18) +#define KEY_Ln ((uint64_t)1 << 19) +#define KEY_Log ((uint64_t)1 << 20) +#define KEY_Imaginary ((uint64_t)1 << 21) +#define KEY_Comma ((uint64_t)1 << 22) +#define KEY_Power ((uint64_t)1 << 23) +#define KEY_Sine ((uint64_t)1 << 24) +#define KEY_Cosine ((uint64_t)1 << 25) +#define KEY_Tangent ((uint64_t)1 << 26) +#define KEY_Pi ((uint64_t)1 << 27) +#define KEY_Sqrt ((uint64_t)1 << 28) +#define KEY_Square ((uint64_t)1 << 29) +#define KEY_Seven ((uint64_t)1 << 30) +#define KEY_Eight ((uint64_t)1 << 31) +#define KEY_Nine ((uint64_t)1 << 32) +#define KEY_LeftParenthesis ((uint64_t)1 << 33) +#define KEY_RightParenthesis ((uint64_t)1 << 34) +#define KEY_Four ((uint64_t)1 << 36) +#define KEY_Five ((uint64_t)1 << 37) +#define KEY_Six ((uint64_t)1 << 38) +#define KEY_Multiplication ((uint64_t)1 << 39) +#define KEY_Division ((uint64_t)1 << 40) +#define KEY_One ((uint64_t)1 << 42) +#define KEY_Two ((uint64_t)1 << 43) +#define KEY_Three ((uint64_t)1 << 44) +#define KEY_Plus ((uint64_t)1 << 45) +#define KEY_Minus ((uint64_t)1 << 46) +#define KEY_Zero ((uint64_t)1 << 48) +#define KEY_Dot ((uint64_t)1 << 49) +#define KEY_EE ((uint64_t)1 << 50) +#define KEY_Ans ((uint64_t)1 << 51) +#define KEY_EXE ((uint64_t)1 << 52) +#define KEY_None ((uint64_t)1 << 54) + +EXTERNC uint64_t extapp_millis(); +EXTERNC void extapp_msleep(uint32_t ms); +EXTERNC uint64_t extapp_scanKeyboard(); +EXTERNC void extapp_pushRect(int16_t x, int16_t y, uint16_t w, uint16_t h, const uint16_t * pixels); +EXTERNC void extapp_pushRectUniform(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t color); +EXTERNC int16_t extapp_drawTextLarge(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg); +EXTERNC int16_t extapp_drawTextSmall(const char *text, int16_t x, int16_t y, uint16_t fg, uint16_t bg); +EXTERNC void extapp_waitForVBlank(); +EXTERNC void extapp_clipboardStore(const char *text); +EXTERNC const char * extapp_clipboardText(); + +#endif diff --git a/apps/external/external_icon.png b/apps/external/external_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..297dc31c6cc743585cee6e411c74e158a91a2949 GIT binary patch literal 10728 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 a@&6BvAL9gKturJ50000 +#include +#include +#include "archive.h" +#include "app.h" + +using namespace Poincare; + +namespace External { + +using namespace Archive; + +MainController::MainController(Responder * parentResponder, ::App * app) : + ViewController(parentResponder), + m_selectableTableView(this) +{ + m_app = app; +} + +View * MainController::view() { + return &m_selectableTableView; +} + +void MainController::didBecomeFirstResponder() { + if (selectedRow() < 0) { + selectCellAtLocation(0, 0); + } + Container::activeApp()->setFirstResponder(&m_selectableTableView); +} + +bool MainController::handleEvent(Ion::Events::Event event) { + if (numberOfRows() > 0 && (event == Ion::Events::OK || event == Ion::Events::EXE)) { + uint32_t res = executeFile(m_cells[selectedRow()].text(), ((App *)m_app)->heap(), ((App *)m_app)->heapSize()); + ((App*)m_app)->redraw(); + switch(res) { + case 0: + break; + case 1: + Container::activeApp()->displayWarning(I18n::Message::ExternalAppApiMismatch); + break; + case 2: + Container::activeApp()->displayWarning(I18n::Message::StorageMemoryFull1); + break; + default: + Container::activeApp()->displayWarning(I18n::Message::ExternalAppExecError); + break; + } + return true; + } + return false; +} + +int MainController::numberOfRows() const { + return k_numberOfCells; +}; + +KDCoordinate MainController::rowHeight(int j) { + return Metric::ParameterCellHeight; +} + +KDCoordinate MainController::cumulatedHeightFromIndex(int j) { + return j*rowHeight(0); +} + +int MainController::indexFromCumulatedHeight(KDCoordinate offsetY) { + return offsetY/rowHeight(0); +} + +HighlightCell * MainController::reusableCell(int index, int type) { + assert(index < k_numberOfCells); + return &m_cells[index]; +} + +int MainController::reusableCellCount(int type) { + return k_numberOfCells; +} + +int MainController::typeAtLocation(int i, int j) { + return 0; +} + +void MainController::willDisplayCellForIndex(HighlightCell * cell, int index) { + PointerTextTableCell * myTextCell = (PointerTextTableCell *)cell; + struct File f; + if(fileAtIndex(index, f)) { + myTextCell->setText(f.name); + myTextCell->setTextColor(f.isExecutable ? KDColorBlack : Palette::GreyDark); + } +} + +void MainController::viewWillAppear() { + int count = numberOfFiles(); + k_numberOfCells = count <= k_maxNumberOfCells ? count : k_maxNumberOfCells; + m_selectableTableView.reloadData(); +} + +} diff --git a/apps/external/main_controller.h b/apps/external/main_controller.h new file mode 100644 index 000000000..bdb6d275b --- /dev/null +++ b/apps/external/main_controller.h @@ -0,0 +1,34 @@ +#ifndef EXTERNAL_MAIN_CONTROLLER_H +#define EXTERNAL_MAIN_CONTROLLER_H + +#include +#include "pointer_text_table_cell.h" + +namespace External { + +class MainController : public ViewController, public ListViewDataSource, public SelectableTableViewDataSource { +public: + MainController(Responder * parentResponder, App * app); + View * view() override; + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; + int numberOfRows() const override; + KDCoordinate rowHeight(int j) override; + KDCoordinate cumulatedHeightFromIndex(int j) override; + int indexFromCumulatedHeight(KDCoordinate offsetY) override; + HighlightCell * reusableCell(int index, int type) override; + int reusableCellCount(int type) override; + int typeAtLocation(int i, int j) override; + void willDisplayCellForIndex(HighlightCell * cell, int index) override; + void viewWillAppear() override; +private: + App * m_app; + SelectableTableView m_selectableTableView; + int k_numberOfCells = 1; + constexpr static int k_maxNumberOfCells = 16; + PointerTextTableCell m_cells[k_maxNumberOfCells]; +}; + +} + +#endif diff --git a/apps/external/pointer_text_table_cell.cpp b/apps/external/pointer_text_table_cell.cpp new file mode 100644 index 000000000..00967fafb --- /dev/null +++ b/apps/external/pointer_text_table_cell.cpp @@ -0,0 +1,38 @@ +#include "pointer_text_table_cell.h" +#include +#include +#include + +PointerTextTableCell::PointerTextTableCell(const char * text, const KDFont * font, Layout layout) : + TableCell(layout), + m_pointerTextView(font, text, 0, 0.5, KDColorBlack, KDColorWhite) +{ +} + +View * PointerTextTableCell::labelView() const { + return (View *)&m_pointerTextView; +} + +const char * PointerTextTableCell::text() const { + return m_pointerTextView.text(); +} + +void PointerTextTableCell::setHighlighted(bool highlight) { + HighlightCell::setHighlighted(highlight); + KDColor backgroundColor = highlight? Palette::ListCellBackgroundSelected : Palette::ListCellBackground; + m_pointerTextView.setBackgroundColor(backgroundColor); +} + +void PointerTextTableCell::setText(const char * text) { + m_pointerTextView.setText(text); + layoutSubviews(); +} + +void PointerTextTableCell::setTextColor(KDColor color) { + m_pointerTextView.setTextColor(color); +} + +void PointerTextTableCell::setTextFont(const KDFont * font) { + m_pointerTextView.setFont(font); + layoutSubviews(); +} diff --git a/apps/external/pointer_text_table_cell.h b/apps/external/pointer_text_table_cell.h new file mode 100644 index 000000000..be011b76e --- /dev/null +++ b/apps/external/pointer_text_table_cell.h @@ -0,0 +1,20 @@ +#ifndef ESCHER_POINTER_TEXT_TABLE_CELL_H +#define ESCHER_POINTER_TEXT_TABLE_CELL_H + +#include +#include + +class PointerTextTableCell : public TableCell { +public: + PointerTextTableCell(const char * text = "", const KDFont * font = KDFont::SmallFont, Layout layout = Layout::Horizontal); + View * labelView() const override; + const char * text() const override; + virtual void setHighlighted(bool highlight) override; + void setText(const char * text); + virtual void setTextColor(KDColor color); + void setTextFont(const KDFont * font); +private: + PointerTextView m_pointerTextView; +}; + +#endif diff --git a/build/config.mak b/build/config.mak index 8de2af89d..a59637cc0 100644 --- a/build/config.mak +++ b/build/config.mak @@ -7,7 +7,7 @@ EPSILON_VERSION ?= 12.0.0 EPSILON_CUSTOM_VERSION ?= 1.18.0-0 # USERNAME ?= N/A # Valid values are "none", "update", "beta" -EPSILON_APPS ?= calculation rpn graph code statistics probability solver atom sequence regression settings +EPSILON_APPS ?= calculation rpn graph code statistics probability solver atom sequence regression settings external EPSILON_I18N ?= en fr es de pt EPSILON_GETOPT ?= 0 ESCHER_LOG_EVENTS_BINARY ?= 0