allow splash image to be loaded from SD on Horus (#5463)
[opentx.git] / radio / src / gui / 480x272 / radio_sdmanager.cpp
blobf0bbe5a3214055e3802d9e63fada314b42982be6
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 "opentx.h"
24 #define REFRESH_FILES() do { reusableBuffer.sdmanager.offset = 65535; currentBitmapIndex = -1; } while (0)
25 #define NODE_TYPE(fname) fname[SD_SCREEN_FILE_LENGTH+1]
26 #define IS_DIRECTORY(fname) ((bool)(!NODE_TYPE(fname)))
27 #define IS_FILE(fname) ((bool)(NODE_TYPE(fname)))
29 int currentBitmapIndex = 0;
30 BitmapBuffer * currentBitmap = NULL;
32 bool menuRadioSdManagerInfo(event_t event)
34 SIMPLE_SUBMENU(STR_SD_INFO_TITLE, ICON_RADIO_SD_BROWSER, 1);
36 lcdDrawText(MENUS_MARGIN_LEFT, 2*FH, STR_SD_TYPE);
37 lcdDrawText(100, 2*FH, SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD);
39 lcdDrawText(MENUS_MARGIN_LEFT, 3*FH, STR_SD_SIZE);
40 lcdDrawNumber(100, 3*FH, sdGetSize(), LEFT, 0, NULL, "M");
42 lcdDrawText(MENUS_MARGIN_LEFT, 4*FH, STR_SD_SECTORS);
43 #if defined(SD_GET_FREE_BLOCKNR)
44 lcdDrawNumber(100, 4*FH, SD_GET_FREE_BLOCKNR()/1000, LEFT, 0, NULL, "/");
45 lcdDrawNumber(150, 4*FH, sdGetNoSectors()/1000, LEFT);
46 #else
47 lcdDrawNumber(100, 4*FH, sdGetNoSectors()/1000, LEFT, 0, NULL, "k");
48 #endif
50 lcdDrawText(MENUS_MARGIN_LEFT, 5*FH, STR_SD_SPEED);
51 lcdDrawNumber(100, 5*FH, SD_GET_SPEED()/1000, LEFT, 0, NULL, "kb/s");
53 return true;
56 inline bool isFilenameGreater(bool isfile, const char * fn, const char * line)
58 return (isfile && IS_DIRECTORY(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) > 0);
61 inline bool isFilenameLower(bool isfile, const char * fn, const char * line)
63 return (!isfile && IS_FILE(line)) || (isfile==IS_FILE(line) && strcasecmp(fn, line) < 0);
66 void getSelectionFullPath(char * lfn)
68 f_getcwd(lfn, _MAX_LFN);
69 strcat(lfn, PSTR("/"));
70 strcat(lfn, reusableBuffer.sdmanager.lines[menuVerticalPosition - menuVerticalOffset]);
73 void onSdManagerMenu(const char * result)
75 TCHAR lfn[_MAX_LFN+1];
77 // TODO possible buffer overflows here!
79 uint8_t index = menuVerticalPosition-menuVerticalOffset;
80 char *line = reusableBuffer.sdmanager.lines[index];
82 if (result == STR_SD_INFO) {
83 pushMenu(menuRadioSdManagerInfo);
85 else if (result == STR_SD_FORMAT) {
86 POPUP_CONFIRMATION(STR_CONFIRM_FORMAT);
88 else if (result == STR_COPY_FILE) {
89 clipboard.type = CLIPBOARD_TYPE_SD_FILE;
90 f_getcwd(clipboard.data.sd.directory, CLIPBOARD_PATH_LEN);
91 strncpy(clipboard.data.sd.filename, line, CLIPBOARD_PATH_LEN-1);
93 else if (result == STR_PASTE) {
94 f_getcwd(lfn, _MAX_LFN);
95 // if destination is dir, copy into that dir
96 if (IS_DIRECTORY(line)) {
97 strcat(lfn, PSTR("/"));
98 strcat(lfn, line);
100 if (strcmp(clipboard.data.sd.directory, lfn)) { // prevent copying to the same directory
101 POPUP_WARNING(sdCopyFile(clipboard.data.sd.filename, clipboard.data.sd.directory, clipboard.data.sd.filename, lfn));
102 REFRESH_FILES();
105 else if (result == STR_RENAME_FILE) {
106 memcpy(reusableBuffer.sdmanager.originalName, line, sizeof(reusableBuffer.sdmanager.originalName));
107 uint8_t fnlen = 0, extlen = 0;
108 getFileExtension(line, 0, LEN_FILE_EXTENSION_MAX, &fnlen, &extlen);
109 // write spaces to allow extending the length of a filename
110 memset(line + fnlen - extlen, ' ', SD_SCREEN_FILE_LENGTH - fnlen + extlen);
111 line[SD_SCREEN_FILE_LENGTH-extlen] = '\0';
112 s_editMode = EDIT_MODIFY_STRING;
113 editNameCursorPos = 0;
115 else if (result == STR_DELETE_FILE) {
116 getSelectionFullPath(lfn);
117 f_unlink(lfn);
118 menuVerticalOffset = 0;
119 menuVerticalPosition = 0;
120 REFRESH_FILES();
122 else if (result == STR_PLAY_FILE) {
123 getSelectionFullPath(lfn);
124 audioQueue.stopAll();
125 audioQueue.playFile(lfn, 0, ID_PLAY_FROM_SD_MANAGER);
127 else if (result == STR_ASSIGN_BITMAP) {
128 memcpy(g_model.header.bitmap, line, sizeof(g_model.header.bitmap));
129 storageDirty(EE_MODEL);
131 else if (result == STR_ASSIGN_SPLASH) {
132 f_getcwd(lfn, _MAX_LFN);
133 sdCopyFile(line, lfn, SPLASH_FILE, BITMAPS_PATH);
135 else if (result == STR_VIEW_TEXT) {
136 getSelectionFullPath(lfn);
137 pushMenuTextView(lfn);
139 else if (result == STR_FLASH_EXTERNAL_DEVICE) {
140 getSelectionFullPath(lfn);
141 sportFlashDevice(EXTERNAL_MODULE, lfn);
143 #if defined(LUA)
144 else if (result == STR_EXECUTE_FILE) {
145 getSelectionFullPath(lfn);
146 luaExec(lfn);
148 #endif
151 bool menuRadioSdManager(event_t _event)
153 if (warningResult) {
154 warningResult = 0;
155 showMessageBox(STR_FORMATTING);
156 logsClose();
157 audioQueue.stopSD();
158 if(sdCardFormat()) {
159 f_chdir("/");
160 REFRESH_FILES();
164 event_t event = (EVT_KEY_MASK(_event) == KEY_ENTER ? 0 : _event);
165 SIMPLE_MENU(SD_IS_HC() ? STR_SDHC_CARD : STR_SD_CARD, RADIO_ICONS, menuTabGeneral, MENU_RADIO_SD_MANAGER, reusableBuffer.sdmanager.count);
167 int index = menuVerticalPosition-menuVerticalOffset;
169 switch(_event) {
170 case EVT_ENTRY:
171 f_chdir(ROOT_PATH);
172 REFRESH_FILES();
173 break;
175 #if 0
176 // TODO: Implement it
177 case EVT_KEY_LONG(KEY_MENU):
178 if (!READ_ONLY() && s_editMode == 0) {
179 killEvents(_event);
180 POPUP_MENU_ADD_ITEM(STR_SD_INFO);
181 POPUP_MENU_ADD_ITEM(STR_SD_FORMAT);
182 POPUP_MENU_START(onSdManagerMenu);
184 break;
185 #endif
187 case EVT_KEY_BREAK(KEY_EXIT):
188 REFRESH_FILES();
189 break;
191 case EVT_KEY_BREAK(KEY_ENTER):
192 if (s_editMode > 0) {
193 break;
195 else {
196 if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[index])) {
197 f_chdir(reusableBuffer.sdmanager.lines[index]);
198 menuVerticalOffset = 0;
199 menuVerticalPosition = 1;
200 index = 1;
201 REFRESH_FILES();
202 killEvents(_event);
203 return true;
206 // no break
208 case EVT_KEY_LONG(KEY_ENTER):
209 if (s_editMode == 0) {
210 killEvents(_event);
211 char * line = reusableBuffer.sdmanager.lines[index];
212 if (!strcmp(line, "..")) {
213 break; // no menu for parent dir
215 const char * ext = getFileExtension(line);
216 if (ext) {
217 if (!strcasecmp(ext, SOUNDS_EXT)) {
218 POPUP_MENU_ADD_ITEM(STR_PLAY_FILE);
220 else if (isExtensionMatching(ext, BITMAPS_EXT)) {
221 TCHAR lfn[_MAX_LFN+1];
222 f_getcwd(lfn, _MAX_LFN);
223 if (!READ_ONLY() && unsigned(strlen(ext)+ext-line) <= sizeof(g_model.header.bitmap) && !strcmp(lfn, BITMAPS_PATH)) {
224 POPUP_MENU_ADD_ITEM(STR_ASSIGN_BITMAP);
226 if (!strcmp(ext, PNG_EXT)) {
227 POPUP_MENU_ADD_ITEM(STR_ASSIGN_SPLASH);
230 else if (!strcasecmp(ext, TEXT_EXT)) {
231 POPUP_MENU_ADD_ITEM(STR_VIEW_TEXT);
233 else if (!READ_ONLY() && !strcasecmp(ext, SPORT_FIRMWARE_EXT)) {
234 POPUP_MENU_ADD_ITEM(STR_FLASH_EXTERNAL_DEVICE);
236 else if (isExtensionMatching(ext, SCRIPTS_EXT)) {
237 POPUP_MENU_ADD_ITEM(STR_EXECUTE_FILE);
240 if (!READ_ONLY()) {
241 if (IS_FILE(line))
242 POPUP_MENU_ADD_ITEM(STR_COPY_FILE);
243 if (clipboard.type == CLIPBOARD_TYPE_SD_FILE)
244 POPUP_MENU_ADD_ITEM(STR_PASTE);
245 POPUP_MENU_ADD_ITEM(STR_RENAME_FILE);
246 if (IS_FILE(line))
247 POPUP_MENU_ADD_ITEM(STR_DELETE_FILE);
249 POPUP_MENU_START(onSdManagerMenu);
251 break;
254 if (reusableBuffer.sdmanager.offset != menuVerticalOffset) {
255 FILINFO fno;
256 DIR dir;
258 if (menuVerticalOffset == 0) {
259 reusableBuffer.sdmanager.offset = 0;
260 memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines));
262 else if (menuVerticalOffset == reusableBuffer.sdmanager.count-NUM_BODY_LINES) {
263 reusableBuffer.sdmanager.offset = menuVerticalOffset;
264 memset(reusableBuffer.sdmanager.lines, 0, sizeof(reusableBuffer.sdmanager.lines));
266 else if (menuVerticalOffset > reusableBuffer.sdmanager.offset) {
267 memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0]));
268 memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0xff, SD_SCREEN_FILE_LENGTH);
269 NODE_TYPE(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1]) = 1;
271 else {
272 memmove(reusableBuffer.sdmanager.lines[1], reusableBuffer.sdmanager.lines[0], (NUM_BODY_LINES-1)*sizeof(reusableBuffer.sdmanager.lines[0]));
273 memset(reusableBuffer.sdmanager.lines[0], 0, sizeof(reusableBuffer.sdmanager.lines[0]));
276 reusableBuffer.sdmanager.count = 0;
278 FRESULT res = f_opendir(&dir, "."); // Open the directory
279 if (res == FR_OK) {
280 bool firstTime = true;
281 for (;;) {
282 res = sdReadDir(&dir, &fno, firstTime);
283 if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
284 if (strlen(fno.fname) > SD_SCREEN_FILE_LENGTH) continue;
285 if (fno.fname[0] == '.' && fno.fname[1] != '.') continue; /* Ignore hidden files under UNIX, but not .. */
287 reusableBuffer.sdmanager.count++;
289 bool isfile = !(fno.fattrib & AM_DIR);
291 if (menuVerticalOffset == 0) {
292 for (uint8_t i=0; i<NUM_BODY_LINES; i++) {
293 char * line = reusableBuffer.sdmanager.lines[i];
294 if (line[0] == '\0' || isFilenameLower(isfile, fno.fname, line)) {
295 if (i < NUM_BODY_LINES-1) memmove(reusableBuffer.sdmanager.lines[i+1], line, sizeof(reusableBuffer.sdmanager.lines[i]) * (NUM_BODY_LINES-1-i));
296 memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[0]));
297 strcpy(line, fno.fname);
298 NODE_TYPE(line) = isfile;
299 break;
303 else if (reusableBuffer.sdmanager.offset == menuVerticalOffset) {
304 for (int8_t i=NUM_BODY_LINES-1; i>=0; i--) {
305 char *line = reusableBuffer.sdmanager.lines[i];
306 if (line[0] == '\0' || isFilenameGreater(isfile, fno.fname, line)) {
307 if (i > 0) memmove(reusableBuffer.sdmanager.lines[0], reusableBuffer.sdmanager.lines[1], sizeof(reusableBuffer.sdmanager.lines[0]) * i);
308 memset(line, 0, sizeof(reusableBuffer.sdmanager.lines[0]));
309 strcpy(line, fno.fname);
310 NODE_TYPE(line) = isfile;
311 break;
315 else if (menuVerticalOffset > reusableBuffer.sdmanager.offset) {
316 if (isFilenameGreater(isfile, fno.fname, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-2]) && isFilenameLower(isfile, fno.fname, reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1])) {
317 memset(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], 0, sizeof(reusableBuffer.sdmanager.lines[0]));
318 strcpy(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1], fno.fname);
319 NODE_TYPE(reusableBuffer.sdmanager.lines[NUM_BODY_LINES-1]) = isfile;
322 else {
323 if (isFilenameLower(isfile, fno.fname, reusableBuffer.sdmanager.lines[1]) && isFilenameGreater(isfile, fno.fname, reusableBuffer.sdmanager.lines[0])) {
324 memset(reusableBuffer.sdmanager.lines[0], 0, sizeof(reusableBuffer.sdmanager.lines[0]));
325 strcpy(reusableBuffer.sdmanager.lines[0], fno.fname);
326 NODE_TYPE(reusableBuffer.sdmanager.lines[0]) = isfile;
330 f_closedir(&dir);
334 reusableBuffer.sdmanager.offset = menuVerticalOffset;
336 for (uint8_t i=0; i<NUM_BODY_LINES; i++) {
337 coord_t y = MENU_CONTENT_TOP + i*FH;
338 LcdFlags attr = (index == i ? INVERS : 0);
339 if (reusableBuffer.sdmanager.lines[i][0]) {
340 if (s_editMode == EDIT_MODIFY_STRING && attr) {
341 uint8_t extlen, efflen;
342 const char * ext = getFileExtension(reusableBuffer.sdmanager.originalName, 0, 0, NULL, &extlen);
343 editName(MENUS_MARGIN_LEFT, y, reusableBuffer.sdmanager.lines[i], SD_SCREEN_FILE_LENGTH - extlen, _event, attr, 0);
344 efflen = effectiveLen(reusableBuffer.sdmanager.lines[i], SD_SCREEN_FILE_LENGTH - extlen);
345 if (s_editMode == 0) {
346 if (ext) {
347 strAppend(&reusableBuffer.sdmanager.lines[i][efflen], ext);
349 else {
350 reusableBuffer.sdmanager.lines[i][efflen] = 0;
352 f_rename(reusableBuffer.sdmanager.originalName, reusableBuffer.sdmanager.lines[i]);
353 REFRESH_FILES();
356 else if (IS_DIRECTORY(reusableBuffer.sdmanager.lines[i])) {
357 char s[sizeof(reusableBuffer.sdmanager.lines[0])+2];
358 char * ptr = s;
359 *ptr++ = '[';
360 ptr = strAppend(ptr, reusableBuffer.sdmanager.lines[i]);
361 *ptr++ = ']';
362 *ptr = '\0';
363 lcdDrawText(MENUS_MARGIN_LEFT, y, s, attr);
365 else {
366 lcdDrawText(MENUS_MARGIN_LEFT, y, reusableBuffer.sdmanager.lines[i], attr);
371 const char * ext = getFileExtension(reusableBuffer.sdmanager.lines[index]);
372 if (ext && isExtensionMatching(ext, BITMAPS_EXT)) {
373 if (currentBitmapIndex != menuVerticalPosition) {
374 currentBitmapIndex = menuVerticalPosition;
375 delete currentBitmap;
376 currentBitmap = BitmapBuffer::load(reusableBuffer.sdmanager.lines[index]);
378 if (currentBitmap) {
379 uint16_t height = currentBitmap->getHeight();
380 uint16_t width = currentBitmap->getWidth();
381 if (height > MENU_BODY_HEIGHT-10) {
382 height = MENU_BODY_HEIGHT - 10;
384 if (width > LCD_W/2) {
385 width = LCD_W/2;
387 lcd->drawScaledBitmap(currentBitmap, LCD_W / 2 - 20 + LCD_W/4 - width/2, MENU_BODY_TOP + MENU_BODY_HEIGHT/2 - height/2, width, height);
391 return true;