Cosmetics
[opentx.git] / radio / src / gui / 480x272 / radio_sdmanager.cpp
blobd4da41e3b1c6df90d597b7c2c4b48e8653e80d3a
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <stdio.h>
22 #include "io/frsky_firmware_update.h"
23 #include "io/multi_firmware_update.h"
24 #include "opentx.h"
25 #include "storage/modelslist.h"
27 #define NODE_TYPE(fname) fname[SD_SCREEN_FILE_LENGTH+1]
28 #define IS_DIRECTORY(fname) ((bool)(!NODE_TYPE(fname)))
29 #define IS_FILE(fname) ((bool)(NODE_TYPE(fname)))
31 int currentBitmapIndex = 0;
32 BitmapBuffer * currentBitmap = nullptr;
34 inline void REFRESH_FILES()
36 reusableBuffer.sdManager.offset = 65535;
37 currentBitmapIndex = -1;
40 bool menuRadioSdManagerInfo(event_t event)
42 SIMPLE_SUBMENU(STR_SD_INFO_TITLE, ICON_RADIO_SD_MANAGER, 1);
44 lcdDrawText(MENUS_MARGIN_LEFT, 2*FH, STR_SD_TYPE);
45 lcdDrawText(100, 2*FH, SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD);
47 lcdDrawText(MENUS_MARGIN_LEFT, 3*FH, STR_SD_SIZE);
48 lcdDrawNumber(100, 3*FH, sdGetSize(), LEFT, 0, NULL, "M");
50 lcdDrawText(MENUS_MARGIN_LEFT, 4*FH, STR_SD_SECTORS);
51 #if defined(SD_GET_FREE_BLOCKNR)
52 lcdDrawNumber(100, 4*FH, SD_GET_FREE_BLOCKNR()/1000, LEFT, 0, NULL, "/");
53 lcdDrawNumber(150, 4*FH, sdGetNoSectors()/1000, LEFT);
54 #else
55 lcdDrawNumber(100, 4*FH, sdGetNoSectors()/1000, LEFT, 0, NULL, "k");
56 #endif
58 lcdDrawText(MENUS_MARGIN_LEFT, 5*FH, STR_SD_SPEED);
59 lcdDrawNumber(100, 5*FH, SD_GET_SPEED()/1000, LEFT, 0, NULL, "kb/s");
61 return true;
64 inline bool isFilenameGreater(bool isfile, const char * fn, const char * line)
66 return (isfile && IS_DIRECTORY(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) > 0);
69 inline bool isFilenameLower(bool isfile, const char * fn, const char * line)
71 return (!isfile && IS_FILE(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) < 0);
74 void getSelectionFullPath(char * lfn)
76 f_getcwd(lfn, _MAX_LFN);
77 strcat(lfn, "/");
78 strcat(lfn, reusableBuffer.sdManager.lines[menuVerticalPosition - menuVerticalOffset]);
81 void onSdFormatConfirm(const char * result)
83 if (result == STR_OK) {
84 showMessageBox(STR_FORMATTING);
85 logsClose();
86 audioQueue.stopSD();
87 if(sdCardFormat()) {
88 f_chdir("/");
89 REFRESH_FILES();
94 void onSdManagerMenu(const char * result)
96 TCHAR lfn[_MAX_LFN+1];
98 // TODO possible buffer overflows here!
100 uint8_t index = menuVerticalPosition-menuVerticalOffset;
101 char *line = reusableBuffer.sdManager.lines[index];
103 if (result == STR_SD_INFO) {
104 pushMenu(menuRadioSdManagerInfo);
106 else if (result == STR_SD_FORMAT) {
107 POPUP_CONFIRMATION(STR_CONFIRM_FORMAT, onSdFormatConfirm);
109 else if (result == STR_COPY_FILE) {
110 clipboard.type = CLIPBOARD_TYPE_SD_FILE;
111 f_getcwd(clipboard.data.sd.directory, CLIPBOARD_PATH_LEN);
112 strncpy(clipboard.data.sd.filename, line, CLIPBOARD_PATH_LEN-1);
114 else if (result == STR_PASTE) {
115 f_getcwd(lfn, _MAX_LFN);
116 // if destination is dir, copy into that dir
117 if (IS_DIRECTORY(line)) {
118 strcat(lfn, "/");
119 strcat(lfn, line);
121 if (strcmp(clipboard.data.sd.directory, lfn)) { // prevent copying to the same directory
122 POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, clipboard.data.sd.filename, lfn));
123 REFRESH_FILES();
126 else if (result == STR_RENAME_FILE) {
127 memcpy(reusableBuffer.sdManager.originalName, line, sizeof(reusableBuffer.sdManager.originalName));
128 uint8_t fnlen = 0, extlen = 0;
129 getFileExtension(line, 0, LEN_FILE_EXTENSION_MAX, &fnlen, &extlen);
130 // write spaces to allow extending the length of a filename
131 memset(line + fnlen - extlen, ' ', SD_SCREEN_FILE_LENGTH - fnlen + extlen);
132 line[SD_SCREEN_FILE_LENGTH-extlen] = '\0';
133 s_editMode = EDIT_MODIFY_STRING;
134 editNameCursorPos = 0;
136 else if (result == STR_DELETE_FILE) {
137 getSelectionFullPath(lfn);
138 f_unlink(lfn);
139 menuVerticalOffset = 0;
140 menuVerticalPosition = 0;
141 REFRESH_FILES();
143 else if (result == STR_PLAY_FILE) {
144 getSelectionFullPath(lfn);
145 audioQueue.stopAll();
146 audioQueue.playFile(lfn, 0, ID_PLAY_FROM_SD_MANAGER);
148 else if (result == STR_ASSIGN_BITMAP) {
149 memcpy(g_model.header.bitmap, line, sizeof(g_model.header.bitmap));
150 if(modelslist.getCurrentModel())
151 modelslist.getCurrentModel()->resetBuffer();
152 storageDirty(EE_MODEL);
154 else if (result == STR_ASSIGN_SPLASH) {
155 f_getcwd(lfn, _MAX_LFN);
156 sdCopyFile(line, lfn, SPLASH_FILE, BITMAPS_PATH);
158 else if (result == STR_VIEW_TEXT) {
159 getSelectionFullPath(lfn);
160 pushMenuTextView(lfn);
162 else if (result == STR_FLASH_BOOTLOADER) {
163 getSelectionFullPath(lfn);
164 bootloaderFlash(lfn);
166 else if (result == STR_FLASH_INTERNAL_MODULE) {
167 getSelectionFullPath(lfn);
168 FrskyDeviceFirmwareUpdate device(INTERNAL_MODULE);
169 device.flashFirmware(lfn);
171 else if (result == STR_FLASH_EXTERNAL_MODULE) {
172 getSelectionFullPath(lfn);
173 FrskyDeviceFirmwareUpdate device(EXTERNAL_MODULE);
174 device.flashFirmware(lfn);
176 else if (result == STR_FLASH_EXTERNAL_DEVICE) {
177 getSelectionFullPath(lfn);
178 FrskyDeviceFirmwareUpdate device(SPORT_MODULE);
179 device.flashFirmware(lfn);
181 #if defined(BLUETOOTH)
182 else if (result == STR_FLASH_BLUETOOTH_MODULE) {
183 getSelectionFullPath(lfn);
184 bluetooth.flashFirmware(lfn);
186 #endif
187 #if defined(HARDWARE_POWER_MANAGEMENT_UNIT)
188 else if (result == STR_FLASH_POWER_MANAGEMENT_UNIT) {
189 getSelectionFullPath(lfn);
190 FrskyChipFirmwareUpdate device;
191 device.flashFirmware(lfn);
193 #endif
194 #if defined(MULTIMODULE)
195 #if defined(INTERNAL_MODULE_MULTI)
196 else if (result == STR_FLASH_INTERNAL_MULTI) {
197 getSelectionFullPath(lfn);
198 multiFlashFirmware(INTERNAL_MODULE, lfn);
200 #endif
201 else if (result == STR_FLASH_EXTERNAL_MULTI) {
202 getSelectionFullPath(lfn);
203 multiFlashFirmware(EXTERNAL_MODULE, lfn);
205 #endif
206 #if defined(LUA)
207 else if (result == STR_EXECUTE_FILE) {
208 getSelectionFullPath(lfn);
209 luaExec(lfn);
211 #endif
214 bool menuRadioSdManager(event_t _event)
216 event_t event = (EVT_KEY_MASK(_event) == KEY_ENTER ? 0 : _event);
217 SIMPLE_MENU(SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD, RADIO_ICONS, menuTabGeneral, MENU_RADIO_SD_MANAGER, reusableBuffer.sdManager.count);
219 int index = menuVerticalPosition - menuVerticalOffset;
221 switch(_event) {
222 case EVT_ENTRY:
223 f_chdir(ROOT_PATH);
224 // no break;
226 case EVT_ENTRY_UP:
227 memset(&reusableBuffer.sdManager, 0, sizeof(reusableBuffer.sdManager));
228 menuVerticalPosition = 0;
229 REFRESH_FILES();
230 break;
232 #if 0
233 // TODO: Implement it
234 case EVT_KEY_LONG(KEY_MENU):
235 if (!READ_ONLY() && s_editMode == 0) {
236 killEvents(_event);
237 POPUP_MENU_ADD_ITEM(STR_SD_INFO);
238 POPUP_MENU_ADD_ITEM(STR_SD_FORMAT);
239 POPUP_MENU_START(onSdManagerMenu);
241 break;
242 #endif
244 case EVT_KEY_BREAK(KEY_EXIT):
245 REFRESH_FILES();
246 break;
248 case EVT_KEY_BREAK(KEY_ENTER):
249 if (s_editMode > 0) {
250 break;
252 else {
253 if (IS_DIRECTORY(reusableBuffer.sdManager.lines[index])) {
254 f_chdir(reusableBuffer.sdManager.lines[index]);
255 menuVerticalOffset = 0;
256 menuVerticalPosition = 1;
257 index = 1;
258 REFRESH_FILES();
259 killEvents(_event);
260 return true;
263 // no break
265 case EVT_KEY_LONG(KEY_ENTER):
266 if (s_editMode == 0) {
267 killEvents(_event);
268 char * line = reusableBuffer.sdManager.lines[index];
269 if (!strcmp(line, "..")) {
270 break; // no menu for parent dir
272 const char * ext = getFileExtension(line);
273 if (ext) {
274 if (!strcasecmp(ext, SOUNDS_EXT)) {
275 POPUP_MENU_ADD_ITEM(STR_PLAY_FILE);
277 else if (isExtensionMatching(ext, BITMAPS_EXT)) {
278 TCHAR lfn[_MAX_LFN+1];
279 f_getcwd(lfn, _MAX_LFN);
280 if (!READ_ONLY() && unsigned(strlen(ext)+ext-line) <= sizeof(g_model.header.bitmap) && !strcmp(lfn, BITMAPS_PATH)) {
281 POPUP_MENU_ADD_ITEM(STR_ASSIGN_BITMAP);
283 if (!strcmp(ext, PNG_EXT)) {
284 POPUP_MENU_ADD_ITEM(STR_ASSIGN_SPLASH);
287 else if (!READ_ONLY() && !strcasecmp(ext, SPORT_FIRMWARE_EXT)) {
288 if (HAS_SPORT_UPDATE_CONNECTOR())
289 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_DEVICE);
290 POPUP_MENU_ADD_ITEM(STR_FLASH_INTERNAL_MODULE);
291 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_MODULE);
293 else if (!READ_ONLY() && !strcasecmp(ext, FRSKY_FIRMWARE_EXT)) {
294 FrSkyFirmwareInformation information;
295 if (readFrSkyFirmwareInformation(line, information) == nullptr) {
296 if (information.productFamily == FIRMWARE_FAMILY_INTERNAL_MODULE)
297 POPUP_MENU_ADD_ITEM(STR_FLASH_INTERNAL_MODULE);
298 if (information.productFamily == FIRMWARE_FAMILY_EXTERNAL_MODULE)
299 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_MODULE);
300 if (HAS_SPORT_UPDATE_CONNECTOR() && (information.productFamily == FIRMWARE_FAMILY_RECEIVER || information.productFamily == FIRMWARE_FAMILY_SENSOR))
301 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_DEVICE);
302 #if defined(PXX2)
303 if (information.productFamily == FIRMWARE_FAMILY_RECEIVER)
304 POPUP_MENU_ADD_ITEM(STR_FLASH_RECEIVER_OTA);
305 #endif
306 #if defined(BLUETOOTH)
307 if (information.productFamily == FIRMWARE_FAMILY_BLUETOOTH_CHIP)
308 POPUP_MENU_ADD_ITEM(STR_FLASH_BLUETOOTH_MODULE);
309 #endif
310 #if defined(HARDWARE_POWER_MANAGEMENT_UNIT)
311 if (information.productFamily == FIRMWARE_FAMILY_POWER_MANAGEMENT_UNIT)
312 POPUP_MENU_ADD_ITEM(STR_FLASH_POWER_MANAGEMENT_UNIT);
313 #endif
317 // Warning FIRMWARE_EXT and MULTI_FIRMWARE_EXT are the same
318 if (!READ_ONLY() && !strcasecmp(ext, FIRMWARE_EXT)) {
319 TCHAR lfn[_MAX_LFN+1];
320 getSelectionFullPath(lfn);
321 if (isBootloader(lfn)) {
322 POPUP_MENU_ADD_ITEM(STR_FLASH_BOOTLOADER);
326 #if defined(MULTIMODULE)
327 if (!READ_ONLY() && !strcasecmp(ext, MULTI_FIRMWARE_EXT)) {
328 TCHAR lfn[_MAX_LFN + 1];
329 getSelectionFullPath(lfn);
330 MultiFirmwareInformation information;
331 if (information.readMultiFirmwareInformation(line) == nullptr) {
332 #if defined(INTERNAL_MODULE_MULTI)
333 POPUP_MENU_ADD_ITEM(STR_FLASH_INTERNAL_MULTI);
334 #endif
335 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_MULTI);
338 #endif
340 if (isExtensionMatching(ext, SCRIPTS_EXT)) {
341 POPUP_MENU_ADD_ITEM(STR_EXECUTE_FILE);
344 if (isExtensionMatching(ext, TEXT_EXT) || isExtensionMatching(ext, SCRIPTS_EXT)) {
345 POPUP_MENU_ADD_ITEM(STR_VIEW_TEXT);
349 if (!READ_ONLY()) {
350 if (IS_FILE(line))
351 POPUP_MENU_ADD_ITEM(STR_COPY_FILE);
352 if (clipboard.type == CLIPBOARD_TYPE_SD_FILE)
353 POPUP_MENU_ADD_ITEM(STR_PASTE);
354 POPUP_MENU_ADD_ITEM(STR_RENAME_FILE);
355 if (IS_FILE(line))
356 POPUP_MENU_ADD_ITEM(STR_DELETE_FILE);
358 POPUP_MENU_START(onSdManagerMenu);
360 break;
363 if (reusableBuffer.sdManager.offset != menuVerticalOffset) {
364 FILINFO fno;
365 DIR dir;
367 if (menuVerticalOffset == reusableBuffer.sdManager.offset + 1) {
368 memmove(reusableBuffer.sdManager.lines[0], reusableBuffer.sdManager.lines[1], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdManager.lines[0]));
369 memset(reusableBuffer.sdManager.lines[NUM_BODY_LINES-1], 0xff, SD_SCREEN_FILE_LENGTH);
370 NODE_TYPE(reusableBuffer.sdManager.lines[NUM_BODY_LINES-1]) = 1;
372 else if (menuVerticalOffset == reusableBuffer.sdManager.offset - 1) {
373 memmove(reusableBuffer.sdManager.lines[1], reusableBuffer.sdManager.lines[0], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdManager.lines[0]));
374 memset(reusableBuffer.sdManager.lines[0], 0, sizeof(reusableBuffer.sdManager.lines[0]));
376 else {
377 reusableBuffer.sdManager.offset = menuVerticalOffset;
378 memset(reusableBuffer.sdManager.lines, 0, sizeof(reusableBuffer.sdManager.lines));
381 reusableBuffer.sdManager.count = 0;
383 FRESULT res = f_opendir(&dir, "."); // Open the directory
384 if (res == FR_OK) {
385 bool firstTime = true;
386 for (;;) {
387 res = sdReadDir(&dir, &fno, firstTime);
388 if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
389 if (strlen(fno.fname) > SD_SCREEN_FILE_LENGTH) continue;
390 if (fno.fattrib & AM_HID) continue; /* Ignore Windows hidden files */
391 if (fno.fname[0] == '.' && fno.fname[1] != '.') continue; /* Ignore UNIX hidden files, but not .. */
393 reusableBuffer.sdManager.count++;
395 bool isfile = !(fno.fattrib & AM_DIR);
397 if (menuVerticalOffset == 0) {
398 for (uint8_t i=0; i<NUM_BODY_LINES; i++) {
399 char * line = reusableBuffer.sdManager.lines[i];
400 if (line[0] == '\0' || isFilenameLower(isfile, fno.fname, line)) {
401 if (i < NUM_BODY_LINES-1) memmove(reusableBuffer.sdManager.lines[i+1], line, sizeof(reusableBuffer.sdManager.lines[i]) * (NUM_BODY_LINES-1-i));
402 memset(line, 0, sizeof(reusableBuffer.sdManager.lines[0]));
403 strcpy(line, fno.fname);
404 NODE_TYPE(line) = isfile;
405 break;
409 else if (reusableBuffer.sdManager.offset == menuVerticalOffset) {
410 for (int8_t i=NUM_BODY_LINES-1; i>=0; i--) {
411 char *line = reusableBuffer.sdManager.lines[i];
412 if (line[0] == '\0' || isFilenameGreater(isfile, fno.fname, line)) {
413 if (i > 0) memmove(reusableBuffer.sdManager.lines[0], reusableBuffer.sdManager.lines[1], sizeof(reusableBuffer.sdManager.lines[0]) * i);
414 memset(line, 0, sizeof(reusableBuffer.sdManager.lines[0]));
415 strcpy(line, fno.fname);
416 NODE_TYPE(line) = isfile;
417 break;
421 else if (menuVerticalOffset > reusableBuffer.sdManager.offset) {
422 if (isFilenameGreater(isfile, fno.fname, reusableBuffer.sdManager.lines[NUM_BODY_LINES-2]) && isFilenameLower(isfile, fno.fname, reusableBuffer.sdManager.lines[NUM_BODY_LINES-1])) {
423 memset(reusableBuffer.sdManager.lines[NUM_BODY_LINES-1], 0, sizeof(reusableBuffer.sdManager.lines[0]));
424 strcpy(reusableBuffer.sdManager.lines[NUM_BODY_LINES-1], fno.fname);
425 NODE_TYPE(reusableBuffer.sdManager.lines[NUM_BODY_LINES-1]) = isfile;
428 else {
429 if (isFilenameLower(isfile, fno.fname, reusableBuffer.sdManager.lines[1]) && isFilenameGreater(isfile, fno.fname, reusableBuffer.sdManager.lines[0])) {
430 memset(reusableBuffer.sdManager.lines[0], 0, sizeof(reusableBuffer.sdManager.lines[0]));
431 strcpy(reusableBuffer.sdManager.lines[0], fno.fname);
432 NODE_TYPE(reusableBuffer.sdManager.lines[0]) = isfile;
436 f_closedir(&dir);
440 reusableBuffer.sdManager.offset = menuVerticalOffset;
442 for (uint8_t i=0; i<NUM_BODY_LINES; i++) {
443 coord_t y = MENU_CONTENT_TOP + i*FH;
444 LcdFlags attr = (index == i ? INVERS : 0);
445 if (reusableBuffer.sdManager.lines[i][0]) {
446 if (s_editMode == EDIT_MODIFY_STRING && attr) {
447 uint8_t extlen, efflen;
448 const char * ext = getFileExtension(reusableBuffer.sdManager.originalName, 0, 0, NULL, &extlen);
449 editName(MENUS_MARGIN_LEFT, y, reusableBuffer.sdManager.lines[i], SD_SCREEN_FILE_LENGTH - extlen, _event, attr, 0);
450 efflen = effectiveLen(reusableBuffer.sdManager.lines[i], SD_SCREEN_FILE_LENGTH - extlen);
451 if (s_editMode == 0) {
452 if (ext) {
453 strAppend(&reusableBuffer.sdManager.lines[i][efflen], ext);
455 else {
456 reusableBuffer.sdManager.lines[i][efflen] = 0;
458 f_rename(reusableBuffer.sdManager.originalName, reusableBuffer.sdManager.lines[i]);
459 REFRESH_FILES();
462 else if (IS_DIRECTORY(reusableBuffer.sdManager.lines[i])) {
463 char s[sizeof(reusableBuffer.sdManager.lines[0])+2];
464 char * ptr = s;
465 *ptr++ = '[';
466 ptr = strAppend(ptr, reusableBuffer.sdManager.lines[i]);
467 *ptr++ = ']';
468 *ptr = '\0';
469 lcdDrawText(MENUS_MARGIN_LEFT, y, s, attr);
471 else {
472 lcdDrawText(MENUS_MARGIN_LEFT, y, reusableBuffer.sdManager.lines[i], attr);
477 const char * ext = getFileExtension(reusableBuffer.sdManager.lines[index]);
478 if (ext && isExtensionMatching(ext, BITMAPS_EXT)) {
479 if (currentBitmapIndex != menuVerticalPosition) {
480 currentBitmapIndex = menuVerticalPosition;
481 delete currentBitmap;
482 currentBitmap = BitmapBuffer::load(reusableBuffer.sdManager.lines[index]);
484 if (currentBitmap) {
485 uint16_t height = currentBitmap->getHeight();
486 uint16_t width = currentBitmap->getWidth();
487 if (height > MENU_BODY_HEIGHT-10) {
488 height = MENU_BODY_HEIGHT - 10;
490 if (width > LCD_W/2) {
491 width = LCD_W/2;
493 lcd->drawScaledBitmap(currentBitmap, LCD_W / 2 - 20 + LCD_W/4 - width/2, MENU_BODY_TOP + MENU_BODY_HEIGHT/2 - height/2, width, height);
497 return true;