GCC Code Coverage Report


Directory: ./
File: oled/OLEDPages.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 241 425 56.7%
Functions: 34 46 73.9%
Branches: 161 446 36.1%

Line Branch Exec Source
1 #include "fpp-pch.h"
2 #include <memory>
3
4 #include <fstream>
5
6 #include "OLEDPages.h"
7 #include "../common.h"
8
9 static std::unique_ptr<DisplayDriver> displayDriver{ nullptr };
10
11 OLEDPage::OLEDType OLEDPage::oledType = OLEDPage::OLEDType::SINGLE_COLOR;
12 bool OLEDPage::oledFlipped = false;
13 OLEDPage* OLEDPage::currentPage = nullptr;
14 bool OLEDPage::oledForcedOff = false;
15 bool OLEDPage::has4DirectionControls = false;
16
17 int OLEDPage::_capeImageWidth = 0;
18 int OLEDPage::_capeImageHeight = 0;
19 std::vector<uint8_t> OLEDPage::_capeImage;
20
21 static int DISPLAY_I2CBUS = 0;
22
23 16 bool OLEDPage::InitializeDisplay(int ledType) {
24
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
16 if (ledType == 0) {
25 4 SetOLEDType(OLEDPage::OLEDType::NONE);
26 4 return false;
27 }
28
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12 if (ledType <= 10 || ledType >= 32) {
29
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 displayDriver = std::make_unique<SSD1306DisplayDriver>(ledType);
30
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!displayDriver->initialize(DISPLAY_I2CBUS)) {
31 ✗ displayDriver.reset(nullptr);
32 ✗ SetOLEDType(OLEDPage::OLEDType::NONE);
33 ✗ return false;
34 }
35
36
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 if (ledType == 3 || ledType == 4) {
37 ✗ SetOLEDType(OLEDPage::OLEDType::SMALL);
38
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 } else if (ledType == 7 || ledType == 8) {
39 ✗ SetOLEDType(OLEDPage::OLEDType::TWO_COLOR);
40 } else {
41 12 SetOLEDType(OLEDPage::OLEDType::SINGLE_COLOR);
42 }
43
6/12
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 12 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 12 times.
12 if (ledType == 2 || ledType == 4 || ledType == 6 || ledType == 8 || ledType == 10 || ledType == 35) {
44 ✗ SetOLEDOrientationFlipped(true);
45 }
46
47 12 return true;
48 ✗ } else if (ledType <= 20) {
49 ✗ displayDriver = std::make_unique<I2C1602_2004_DisplayDriver>(ledType);
50 ✗ if (!displayDriver->initialize(DISPLAY_I2CBUS)) {
51 ✗ displayDriver.reset(nullptr);
52 ✗ SetOLEDType(OLEDPage::OLEDType::NONE);
53 ✗ return false;
54 }
55 ✗ SetOLEDType(OLEDPage::OLEDType::TEXT_ONLY);
56 ✗ return true;
57 }
58 ✗ return false;
59 }
60
61 12 int OLEDPage::GetI2CBus() {
62 12 return DISPLAY_I2CBUS;
63 }
64
65 16 void OLEDPage::SetOLEDType(OLEDType tp) {
66 16 oledType = tp;
67 16 }
68
69 12 void OLEDPage::displayBootingNotice(const std::string& msg) {
70
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (displayDriver) {
71
3/10
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 12 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
12 std::string m = msg.empty() ? "FPP Booting..." : msg;
72
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 readCapeImage();
73
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 displayDriver->clearDisplay();
74
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 displayDriver->printString(0, 0, m, true);
75
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 drawBitmap(0, 20, &_capeImage[0], _capeImageWidth, _capeImageHeight);
76
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 displayDriver->flushDisplay();
77 12 }
78 12 }
79
80 59 void OLEDPage::flushDisplay() {
81
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 if (displayDriver) {
82 59 displayDriver->flushDisplay();
83 }
84 59 }
85 59 void OLEDPage::clearDisplay() {
86
1/2
✓ Branch 0 taken 59 times.
✗ Branch 1 not taken.
59 if (displayDriver) {
87 59 displayDriver->clearDisplay();
88 }
89 59 }
90 7 void OLEDPage::fillTriangle(short x0, short y0, short x1, short y1, short x2, short y2, bool white) {
91
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (displayDriver) {
92 7 displayDriver->fillTriangle(x0, y0, x1, y1, x2, y2, white);
93 }
94 7 }
95 52 void OLEDPage::drawLine(short x0, short y0, short x1, short y1, bool white) {
96
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 if (displayDriver) {
97 52 displayDriver->drawLine(x0, y0, x1, y1, white);
98 }
99 52 }
100 56 void OLEDPage::drawBitmap(short x, short y, const unsigned char bitmap[], short w, short h, bool white) {
101
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 if (displayDriver) {
102 56 displayDriver->drawBitmap(x, y, bitmap, w, h, white);
103 }
104 56 }
105 7 void OLEDPage::drawRect(short x0, short y0, short x1, short y1, bool white) {
106
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (displayDriver) {
107 7 displayDriver->drawRect(x0, y0, x1, y1, white);
108 }
109 7 }
110 14 void OLEDPage::fillRect(short x0, short y0, short x1, short y1, bool white) {
111
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (displayDriver) {
112 14 displayDriver->fillRect(x0, y0, x1, y1, white);
113 }
114 14 }
115
116 139 void OLEDPage::printString(int x, int y, const std::string& str, bool white) {
117
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 139 times.
139 if (oledForcedOff)
118 ✗ return;
119
1/2
✓ Branch 0 taken 139 times.
✗ Branch 1 not taken.
139 if (displayDriver) {
120 139 displayDriver->printString(x, y, str, white);
121 }
122 }
123 7 void OLEDPage::printStringCentered(int y, const std::string& str, bool white) {
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (oledForcedOff)
125 ✗ return;
126
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (displayDriver) {
127 7 displayDriver->printStringCentered(y, str, white);
128 }
129 }
130 156 int OLEDPage::GetLEDDisplayWidth() {
131
1/2
✓ Branch 0 taken 156 times.
✗ Branch 1 not taken.
156 if (displayDriver) {
132 156 return displayDriver->getWidth();
133 }
134 ✗ return 128;
135 }
136 172 int OLEDPage::GetLEDDisplayHeight() {
137
1/2
✓ Branch 0 taken 172 times.
✗ Branch 1 not taken.
172 if (displayDriver) {
138 172 return displayDriver->getHeight();
139 }
140 ✗ return 32;
141 }
142 12 void OLEDPage::displayOff() {
143
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 if (displayDriver) {
144 12 return displayDriver->displayOff();
145 }
146 }
147 ✗ int OLEDPage::getMinimumRefresh() {
148 ✗ if (displayDriver) {
149 ✗ return displayDriver->getMinimumRefresh();
150 }
151 ✗ return 1000;
152 }
153
154 24 OLEDPage::OLEDPage() :
155 24 autoDeleteOnHide(false) {
156 24 }
157
158 17 void OLEDPage::SetCurrentPage(OLEDPage* p) {
159
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 12 times.
17 if (currentPage) {
160 5 currentPage->hiding();
161
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (currentPage->autoDeleteOnHide) {
162 ✗ delete currentPage;
163 }
164 }
165 17 currentPage = p;
166
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 if (currentPage) {
167 17 currentPage->displaying();
168 }
169 17 }
170
171 // trim from start (in place)
172 14320 static inline void ltrim(std::string& s) {
173
2/4
✓ Branch 0 taken 14320 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14320 times.
✗ Branch 3 not taken.
14320 s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
174 26380 return !std::isspace(ch);
175 }));
176 14320 }
177 // trim from end (in place)
178 14320 static inline void rtrim(std::string& s) {
179
2/4
✓ Branch 0 taken 14320 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 14320 times.
✗ Branch 3 not taken.
42960 s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
180 14296 return !std::isspace(ch);
181 14320 }).base(),
182 14320 s.end());
183 14320 }
184 // trim from both ends (in place)
185 14320 static inline void trim(std::string& s) {
186 14320 ltrim(s);
187 14320 rtrim(s);
188 14320 }
189 1096 static std::vector<std::string> splitAndTrim(const std::string& s, char seperator) {
190 1096 std::vector<std::string> output;
191 1096 std::string::size_type prev_pos = 0, pos = 0;
192
2/2
✓ Branch 0 taken 12128 times.
✓ Branch 1 taken 1096 times.
13224 while ((pos = s.find(seperator, pos)) != std::string::npos) {
193
1/2
✓ Branch 0 taken 12128 times.
✗ Branch 1 not taken.
12128 std::string substring(s.substr(prev_pos, pos - prev_pos));
194
1/2
✓ Branch 0 taken 12128 times.
✗ Branch 1 not taken.
12128 trim(substring);
195
2/4
✓ Branch 0 taken 12128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12128 times.
✗ Branch 3 not taken.
12128 if (substring != "") {
196
1/2
✓ Branch 0 taken 12128 times.
✗ Branch 1 not taken.
12128 output.push_back(substring);
197 }
198 12128 prev_pos = ++pos;
199 12128 }
200
1/2
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
1096 std::string substring = s.substr(prev_pos, pos - prev_pos);
201
1/2
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
1096 trim(substring);
202
3/4
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
✓ Branch 3 taken 1024 times.
1096 if (substring != "") {
203
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 output.push_back(substring); // Last word
204 }
205 1096 return output;
206 1096 }
207 static const unsigned char reverselookup[16] = {
208 0x0,
209 0x8,
210 0x4,
211 0xc,
212 0x2,
213 0xa,
214 0x6,
215 0xe,
216 0x1,
217 0x9,
218 0x5,
219 0xd,
220 0x3,
221 0xb,
222 0x7,
223 0xf,
224 };
225
226 12012 static inline uint8_t reverse(uint8_t n) {
227 // Reverse the top and bottom nibble then swap them.
228 12012 return (reverselookup[n & 0b1111] << 4) | reverselookup[n >> 4];
229 }
230
231 24 void OLEDPage::readCapeImage() {
232 // The image comes from the cape (its EEPROM, via fppcapedetect), so it is
233 // parsed as untrusted: a short "#define" line indexed past the split, a
234 // byte that wasn't hex threw out of stoi and ended fppoled, and nothing
235 // checked the bytes covered width x height before drawBitmap() read them.
236 // Anything off leaves no image.
237
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 if (FileExists("/home/fpp/media/tmp/cape-image.xbm") || FileExists("/var/tmp/cape-image.xbm")) {
238
3/6
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
48 std::string fn = FileExists("/home/fpp/media/tmp/cape-image.xbm") ? "/home/fpp/media/tmp/cape-image.xbm" : "/var/tmp/cape-image.xbm";
239
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 std::ifstream file(fn);
240
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 if (file.is_open()) {
241 24 std::string line;
242 24 bool readingBytes = false;
243 24 bool ok = true;
244 24 int width = 0;
245 24 int height = 0;
246 24 std::vector<uint8_t> image;
247
8/10
✓ Branch 0 taken 1116 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 1116 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1116 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1096 times.
✓ Branch 7 taken 20 times.
✓ Branch 8 taken 1096 times.
✓ Branch 9 taken 24 times.
1120 while (ok && std::getline(file, line)) {
248
1/2
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
1096 trim(line);
249
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 1024 times.
1096 if (!readingBytes) {
250
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 48 times.
72 if (line.find("_width") != std::string::npos) {
251
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 std::vector<std::string> v = splitAndTrim(line, ' ');
252
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 width = v.size() > 2 ? std::atoi(v[2].c_str()) : 0;
253
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 24 times.
72 } else if (line.find("_height") != std::string::npos) {
254
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 std::vector<std::string> v = splitAndTrim(line, ' ');
255
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 height = v.size() > 2 ? std::atoi(v[2].c_str()) : 0;
256
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
48 } else if (line.find("{") != std::string::npos) {
257 24 readingBytes = true;
258
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 line = line.substr(line.find("{") + 1);
259 }
260 }
261
2/2
✓ Branch 0 taken 1048 times.
✓ Branch 1 taken 48 times.
1096 if (readingBytes) {
262
1/2
✓ Branch 0 taken 1048 times.
✗ Branch 1 not taken.
1048 std::vector<std::string> v = splitAndTrim(line, ',');
263
2/2
✓ Branch 0 taken 12036 times.
✓ Branch 1 taken 1024 times.
13060 for (auto& s : v) {
264 // The closing brace can share a token with the last
265 // byte ("0x00 };", no trailing comma): keep that byte.
266 12036 bool last = false;
267 12036 size_t brace = s.find("}");
268
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 12016 times.
12036 if (brace != std::string::npos) {
269 20 readingBytes = false;
270 20 last = true;
271
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 s.erase(brace);
272
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
20 while (!s.empty() && isspace((unsigned char)s.back())) {
273 ✗ s.pop_back();
274 }
275 }
276
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 12016 times.
12036 if (s.empty()) {
277
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (last) {
278 24 break;
279 }
280 ✗ continue;
281 }
282 12016 char* end = nullptr;
283 12016 long i = strtol(s.c_str(), &end, 16);
284
8/12
✓ Branch 0 taken 12012 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 12012 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 12012 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 12012 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 12012 times.
✓ Branch 10 taken 4 times.
✓ Branch 11 taken 12012 times.
12016 if (end == s.c_str() || *end || i < 0 || i > 255 || image.size() >= 65536) {
285 4 ok = false;
286 4 break;
287 }
288 12012 uint8_t t8 = (uint8_t)i;
289 12012 t8 = ~t8;
290 12012 t8 = reverse(t8);
291
1/2
✓ Branch 0 taken 12012 times.
✗ Branch 1 not taken.
12012 image.push_back((uint8_t)t8);
292
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12012 times.
12012 if (last) {
293 ✗ break;
294 }
295 }
296 1048 }
297 }
298
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 file.close();
299
8/12
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 20 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 20 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 20 times.
✓ Branch 11 taken 4 times.
44 if (ok && width > 0 && width <= 256 && height > 0 && height <= 256 &&
300
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 image.size() >= (size_t)((width + 7) / 8) * height) {
301 20 _capeImageWidth = width;
302 20 _capeImageHeight = height;
303 20 _capeImage = std::move(image);
304 }
305 24 }
306 24 }
307 24 }
308
309 12 TitledOLEDPage::TitledOLEDPage(const std::string& t) :
310
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 title(t) {
311
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
12 numRows = GetLEDDisplayHeight() == 128 ? 11 : 5;
312
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
12 if (oledType == OLEDPage::OLEDType::TWO_COLOR && oledFlipped) {
313 ✗ numRows = 4;
314
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 } else if (oledType == OLEDPage::OLEDType::SMALL) {
315 ✗ numRows = 3;
316
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 } else if (oledType == OLEDPage::OLEDType::TEXT_ONLY) {
317 ✗ numRows = GetLEDDisplayHeight() / 8 - 1;
318 }
319 12 }
320 7 int TitledOLEDPage::displayTitle() {
321
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (oledForcedOff)
322 ✗ return 0;
323
324 7 int startY = 0;
325 7 int maxY = 63;
326
1/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
7 switch (numRows) {
327 ✗ case 4:
328 ✗ maxY = 47;
329 ✗ break;
330 ✗ case 3:
331 ✗ maxY = 31;
332 ✗ break;
333 ✗ case 2:
334 ✗ maxY = 23;
335 ✗ break;
336 ✗ case 1:
337 ✗ maxY = 15;
338 ✗ break;
339 ✗ case 11:
340 ✗ maxY = 127;
341 ✗ break;
342 }
343
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
7 if (oledType == OLEDPage::OLEDType::TWO_COLOR && !oledFlipped) {
344 ✗ if (displayDriver && displayDriver->setFont("Roboto_Medium_14")) {
345 ✗ printStringCentered(startY, title);
346 ✗ displayDriver->setFont("");
347 } else {
348 ✗ printStringCentered(startY + 4, title);
349 }
350 ✗ startY += 16;
351
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 } else if (oledType == OLEDPage::OLEDType::TEXT_ONLY) {
352 ✗ int mw = GetLEDDisplayWidth() / 6;
353 ✗ mw -= title.length();
354 ✗ std::string s = title;
355 ✗ if (mw > 4) {
356 ✗ s = " " + s + " ";
357 ✗ mw -= 2;
358 }
359 ✗ for (int x = 0; x < mw; x += 2) {
360 ✗ if (x != (mw - 1)) {
361 ✗ s = "=" + s + "=";
362 } else {
363 ✗ s += "=";
364 }
365 }
366 ✗ printString(0, startY, s);
367 ✗ startY += 8;
368 ✗ } else {
369 7 fillRect(0, 0, 128, 8);
370 7 drawRect(0, 0, 128, maxY + 1);
371 7 printStringCentered(startY, title, false);
372
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (maxY > 50) {
373 7 startY += 12;
374 ✗ } else if (maxY > 32) {
375 ✗ startY += 11;
376 } else {
377 ✗ startY += 8;
378 }
379 }
380 7 return startY;
381 }
382
383 ✗ PromptOLEDPage::PromptOLEDPage(const std::string& t, const std::string& m1, const std::string& m2,
384 ✗ const std::vector<std::string>& i) :
385 TitledOLEDPage(t),
386 ✗ msg1(m1),
387 ✗ msg2(m2),
388 ✗ items(i),
389 ✗ curSelected(0),
390 ✗ itemSelectedCallback(nullptr) {
391 ✗ }
392 ✗ PromptOLEDPage::PromptOLEDPage(const std::string& t, const std::string& m1, const std::string& m2,
393 const std::vector<std::string>& i,
394 ✗ const std::function<void(const std::string&)>& cb) :
395 TitledOLEDPage(t),
396 ✗ msg1(m1),
397 ✗ msg2(m2),
398 ✗ items(i),
399 ✗ curSelected(0),
400 ✗ itemSelectedCallback(cb) {
401 ✗ }
402 ✗ void PromptOLEDPage::displaying() {
403 ✗ curSelected = 0;
404 ✗ display();
405 ✗ }
406 ✗ void PromptOLEDPage::display() {
407 ✗ if (oledForcedOff)
408 ✗ return;
409 ✗ clearDisplay();
410 ✗ int startY = displayTitle();
411 ✗ int skipY = numRows <= 3 ? 8 : (numRows == 4 ? 9 : 10);
412
413 ✗ if (numRows > 4) {
414 ✗ startY += skipY;
415 }
416 ✗ if (numRows != 1) {
417 ✗ printStringCentered(startY, msg1);
418 ✗ startY += skipY;
419 ✗ if (numRows > 2) {
420 ✗ printStringCentered(startY, msg2);
421 ✗ startY += skipY;
422 }
423 }
424
425 ✗ if (!items.empty()) {
426 ✗ int skipX = 122 / items.size();
427 ✗ for (int x = 0; x < items.size(); x++) {
428 ✗ if (x == curSelected) {
429 ✗ fillRect(x * skipX + 4, startY, skipX, 8);
430 }
431 ✗ int l = items[x].length() * 6 / 2;
432 ✗ printString(x * skipX + 4 + skipX / 2 - l, startY, items[x], x != curSelected);
433 }
434 }
435 ✗ flushDisplay();
436 }
437 ✗ bool PromptOLEDPage::doAction(const std::string& action) {
438 ✗ if (action == "Down" || action == "Test/Down") {
439 ✗ curSelected++;
440 ✗ if (curSelected == items.size()) {
441 ✗ curSelected = 0;
442 }
443 ✗ display();
444 ✗ } else if (action == "Up") {
445 ✗ if (curSelected) {
446 ✗ curSelected--;
447 } else {
448 ✗ curSelected = items.size() - 1;
449 }
450 ✗ display();
451 ✗ } else if (action == "Enter") {
452 ✗ ItemSelected(items[curSelected]);
453 }
454 ✗ return false;
455 }
456 ✗ void PromptOLEDPage::ItemSelected(const std::string& item) {
457 ✗ if (itemSelectedCallback) {
458 ✗ itemSelectedCallback(item);
459 }
460 ✗ }
461
462 12 ListOLEDPage::ListOLEDPage(const std::string& t, const std::vector<std::string>& i, OLEDPage* p) :
463 TitledOLEDPage(t),
464 12 items(i),
465 12 curTop(0),
466
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 parent(p) {
467 12 }
468 ✗ void ListOLEDPage::displaying() {
469 ✗ curTop = 0;
470 ✗ display();
471 ✗ }
472 7 void ListOLEDPage::displayScrollArrows(int startY) {
473
2/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
7 int maxY = numRows == 11 ? 127 : (numRows == 5 ? 63 : (numRows == 4 ? 47 : 31));
474
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if (items.size() > numRows) {
475
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (curTop != 0) {
476 ✗ fillTriangle(116, startY + 8, 125, startY + 8, 120, startY);
477 }
478
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 if ((curTop + numRows) < items.size()) {
479 7 fillTriangle(116, maxY - 10, 125, maxY - 10, 120, maxY - 2);
480 }
481 }
482 7 }
483
484 ✗ void ListOLEDPage::display() {
485 ✗ if (oledForcedOff)
486 ✗ return;
487 ✗ clearDisplay();
488 ✗ int startY = displayTitle();
489 ✗ displayScrollArrows(startY);
490 ✗ int skipY = numRows <= 3 ? 8 : (numRows == 4 ? 9 : 10);
491 ✗ int posX = 6;
492 ✗ if (oledType == OLEDPage::OLEDType::TEXT_ONLY) {
493 ✗ posX = 0;
494 }
495 ✗ for (int x = curTop; (x < items.size()) && (x < (curTop + numRows)); ++x) {
496 ✗ printString(posX, startY, items[x]);
497 ✗ startY += skipY;
498 }
499 ✗ flushDisplay();
500 }
501
502 ✗ bool ListOLEDPage::doAction(const std::string& action) {
503 ✗ if (action == "Down") {
504 ✗ if ((items.size() - curTop) < numRows) {
505 ✗ curTop++;
506 }
507 ✗ display();
508 ✗ } else if (action == "Test/Down") {
509 ✗ curTop++;
510 ✗ if ((items.size() - curTop) < numRows) {
511 ✗ curTop = 0;
512 }
513 ✗ display();
514 ✗ } else if (action == "Up") {
515 ✗ if (curTop) {
516 ✗ curTop--;
517 }
518 ✗ display();
519 ✗ } else if (action == "Enter" || (action == "Back" && parent)) {
520 ✗ SetCurrentPage(parent);
521 }
522 ✗ return true;
523 }
524
525 12 MenuOLEDPage::MenuOLEDPage(const std::string& t, const std::vector<std::string>& i, OLEDPage* p) :
526 ListOLEDPage(t, i, p),
527 12 curSelected(0),
528 12 itemSelectedCallback(nullptr) {
529 12 }
530 ✗ MenuOLEDPage::MenuOLEDPage(const std::string& t, const std::vector<std::string>& i,
531 const std::function<void(const std::string&)>& cb,
532 ✗ OLEDPage* p) :
533 ListOLEDPage(t, i, p),
534 ✗ curSelected(0),
535 ✗ itemSelectedCallback(cb) {
536 ✗ }
537 5 void MenuOLEDPage::displaying() {
538 5 curSelected = 0;
539 5 curTop = 0;
540 5 display();
541 5 }
542 7 void MenuOLEDPage::display() {
543
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (oledForcedOff)
544 ✗ return;
545 7 clearDisplay();
546 7 int startY = displayTitle();
547 7 displayScrollArrows(startY);
548
2/4
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
7 int skipY = numRows <= 3 ? 8 : (numRows == 4 ? 9 : 10);
549
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 int width = items.size() > numRows ? 110 : 120;
550 7 int posX = 6;
551
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (oledType == OLEDPage::OLEDType::TEXT_ONLY) {
552 ✗ posX = 0;
553 }
554
5/6
✓ Branch 0 taken 42 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 35 times.
✓ Branch 5 taken 7 times.
42 for (int x = curTop; (x < items.size()) && (x < (curTop + numRows)); ++x) {
555
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 28 times.
35 if (curSelected == x) {
556 7 fillRect(4, startY, width, 8);
557 }
558 35 printString(posX, startY, items[x], curSelected != x);
559 35 startY += skipY;
560 }
561 7 flushDisplay();
562 }
563 6 bool MenuOLEDPage::doAction(const std::string& action) {
564 // printf("In menu action %s\n", action.c_str());
565
5/6
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 5 times.
6 if (action == "Down" || action == "Test/Down") {
566 1 curSelected++;
567
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (curSelected >= items.size()) {
568 ✗ curSelected = 0;
569 ✗ curTop = 0;
570 }
571
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 while ((curSelected - curTop) >= numRows) {
572 ✗ curTop++;
573 }
574 1 display();
575
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
5 } else if (action == "Up") {
576
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (curSelected) {
577 1 curSelected--;
578
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (curSelected < curTop) {
579 ✗ curTop = curSelected;
580 }
581 } else {
582 ✗ curSelected = items.size() - 1;
583 ✗ curTop = curSelected - numRows + 1;
584 ✗ if (curTop < 0) {
585 ✗ curTop = 0;
586 }
587 }
588 1 display();
589
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 } else if (action == "Enter") {
590 4 itemSelected(items[curSelected]);
591 ✗ } else if (action == "Back" && parent) {
592 ✗ SetCurrentPage(parent);
593 }
594 6 return true;
595 }
596
597 ✗ void MenuOLEDPage::itemSelected(const std::string& item) {
598 ✗ if (itemSelectedCallback) {
599 ✗ itemSelectedCallback(item);
600 }
601 ✗ }
602