// Pi1541 - A Commodore 1541 disk drive emulator // Copyright(C) 2018 Stephen White // // This file is part of Pi1541. // // Pi1541 is free software : you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Pi1541 is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Pi1541. If not, see . #include "FileBrowser.h" #include #include #include #include #include "debug.h" #include "Keyboard.h" #include "options.h" #include "InputMappings.h" #include "stb_image.h" #include "Petscii.h" extern "C" { #include "rpi-gpio.h" } #define PNG_WIDTH 320 #define PNG_HEIGHT 200 unsigned char FileBrowser::LSTBuffer[FileBrowser::LSTBuffer_size]; const unsigned FileBrowser::SwapKeys[30] = { KEY_F1, KEY_KP1, KEY_1, KEY_F2, KEY_KP2, KEY_2, KEY_F3, KEY_KP3, KEY_3, KEY_F4, KEY_KP4, KEY_4, KEY_F5, KEY_KP5, KEY_5, KEY_F6, KEY_KP6, KEY_6, KEY_F7, KEY_KP7, KEY_7, KEY_F8, KEY_KP8, KEY_8, KEY_F9, KEY_KP9, KEY_9, KEY_F10, KEY_KP0, KEY_0 }; static const u32 palette[] = { RGBA(0x00, 0x00, 0x00, 0xFF), RGBA(0xFF, 0xFF, 0xFF, 0xFF), RGBA(0x88, 0x39, 0x32, 0xFF), RGBA(0x67, 0xB6, 0xBD, 0xFF), RGBA(0x8B, 0x4F, 0x96, 0xFF), RGBA(0x55, 0xA0, 0x49, 0xFF), RGBA(0x40, 0x31, 0x8D, 0xFF), RGBA(0xBF, 0xCE, 0x72, 0xFF), RGBA(0x8B, 0x54, 0x29, 0xFF), RGBA(0x57, 0x42, 0x00, 0xFF), RGBA(0xB8, 0x69, 0x62, 0xFF), RGBA(0x50, 0x50, 0x50, 0xFF), RGBA(0x78, 0x78, 0x78, 0xFF), RGBA(0x94, 0xE0, 0x89, 0xFF), RGBA(0x78, 0x69, 0xC4, 0xFF), RGBA(0x9F, 0x9F, 0x9F, 0xFF) }; void FileBrowser::BrowsableListView::RefreshLine(u32 entryIndex, u32 x, u32 y, bool selected) { char buffer1[128] = { 0 }; char buffer2[256] = { 0 }; u32 colour; RGBA BkColour = RGBA(0, 0, 0, 0xFF); //palette[VIC2_COLOUR_INDEX_BLUE]; u32 columnsMax = columns; if (columnsMax > sizeof(buffer1)) columnsMax = sizeof(buffer1); if (entryIndex < list->entries.size()) { FileBrowser::BrowsableList::Entry* entry = &list->entries[entryIndex]; if (screen->IsMonocrome()) { if (entry->filImage.fattrib & AM_DIR) { snprintf(buffer2, 256, "[%s]", entry->filImage.fname); } else { if (entry->caddyIndex != -1) snprintf(buffer2, 256, "%d>%s", entry->caddyIndex, entry->filImage.fname); else snprintf(buffer2, 256, "%s", entry->filImage.fname); } } else { snprintf(buffer2, 256, "%s", entry->filImage.fname); } int len = strlen(buffer2 + highlightScrollOffset); strncpy(buffer1, buffer2 + highlightScrollOffset, sizeof(buffer1)); while (len < (int)columnsMax) buffer1[len++] = ' '; buffer1[columnsMax] = 0; if (selected) { if (entry->filImage.fattrib & AM_DIR) { screen->PrintText(false, x, y, buffer1, palette[VIC2_COLOUR_INDEX_LBLUE], RGBA(0xff, 0xff, 0xff, 0xff)); } else { colour = RGBA(0xff, 0, 0, 0xff); if (entry->filImage.fattrib & AM_RDO) colour = palette[VIC2_COLOUR_INDEX_RED]; screen->PrintText(false, x, y, buffer1, colour, RGBA(0xff, 0xff, 0xff, 0xff)); } } else { if (entry->filImage.fattrib & AM_DIR) { screen->PrintText(false, x, y, buffer1, palette[VIC2_COLOUR_INDEX_LBLUE], BkColour); } else { colour = palette[VIC2_COLOUR_INDEX_LGREY]; if (entry->filImage.fattrib & AM_RDO) colour = palette[VIC2_COLOUR_INDEX_PINK]; screen->PrintText(false, x, y, buffer1, colour, BkColour); } } } else { memset(buffer1, ' ', 80); screen->PrintText(false, x, y, buffer1, BkColour, BkColour); } } void FileBrowser::BrowsableListView::Refresh() { u32 index; u32 entryIndex; u32 x = positionX; u32 y = positionY; highlightScrollOffset = 0; // Ensure the current selection is visible if (list->currentIndex - offset >= rows) { //DEBUG_LOG("CI= %d O = %d R = %d\r\n", list->currentIndex, offset, rows); offset = list->currentIndex - rows + 1; if ((int)offset < 0) offset = 0; } for (index = 0; index < rows; ++index) { entryIndex = offset + index; RefreshLine(entryIndex, x, y, /*showSelected && */list->currentIndex == entryIndex); y += 16; } screen->SwapBuffers(); } void FileBrowser::BrowsableListView::RefreshHighlightScroll() { char buffer2[256] = { 0 }; FileBrowser::BrowsableList::Entry* entry = list->current; if (screen->IsMonocrome()) { if (entry->filImage.fattrib & AM_DIR) { snprintf(buffer2, 256, "[%s]", entry->filImage.fname); } else { if (entry->caddyIndex != -1) snprintf(buffer2, 256, "%d>%s", entry->caddyIndex, entry->filImage.fname); else snprintf(buffer2, 256, "%s", entry->filImage.fname); } } else { snprintf(buffer2, 256, "%s", entry->filImage.fname); } int len = strlen(buffer2); if (len > (int)columns) { if (highlightScrollOffset == 0) { highlightScrollStartCount++; if (highlightScrollStartCount > 10) { highlightScrollStartCount = 0; highlightScrollOffset = 1; } } else if (len - (int)(highlightScrollOffset + 1) <= (int)(columns - 1)) { highlightScrollEndCount++; if (highlightScrollEndCount > 10) { highlightScrollOffset = 0; highlightScrollEndCount = 0; } } else { highlightScrollOffset++; } int rowIndex = list->currentIndex - offset; u32 y = positionY; y += rowIndex * 16; RefreshLine(list->currentIndex, 0, y, true); screen->RefreshRows(rowIndex, 1); } } bool FileBrowser::BrowsableListView::CheckBrowseNavigation(bool pageOnly) { InputMappings* inputMappings = InputMappings::Instance(); bool dirty = false; u32 numberOfEntriesMinus1 = list->entries.size() - 1; if (inputMappings->BrowseDown()) { if (list->currentIndex < numberOfEntriesMinus1) { if (!pageOnly) { list->currentIndex++; list->SetCurrent(); } if (list->currentIndex >= (offset + rows) && (list->currentIndex < list->entries.size())) offset++; dirty = true; } } if (inputMappings->BrowseUp()) { if (list->currentIndex > 0) { if (!pageOnly) { list->currentIndex--; list->SetCurrent(); } if ((offset > 0) && (list->currentIndex < offset)) offset--; dirty = true; } } if ((lcdPgUpDown && inputMappings->BrowsePageDownLCD()) || (!lcdPgUpDown && inputMappings->BrowsePageDown())) { u32 rowsMinus1 = rows - 1; if (list->currentIndex == offset + rowsMinus1) { // Need to move the screen window down so that the currentIndex is now at the top of the screen offset = list->currentIndex; // Current index now becomes the bottom one if (offset + rowsMinus1 > numberOfEntriesMinus1) list->currentIndex = numberOfEntriesMinus1; // Not enough entries to move another page just move to the last entry else // Move the window down a page list->currentIndex = offset + rowsMinus1; } else { // Need to move to list->offset + rowsMinus1 if (offset + rowsMinus1 > numberOfEntriesMinus1) list->currentIndex = numberOfEntriesMinus1; // Run out of entries before we hit the bottom. Just move to the bottom. else list->currentIndex = offset + rowsMinus1; // Move the bottom of the screen } list->SetCurrent(); dirty = true; } if ((lcdPgUpDown && inputMappings->BrowsePageUpLCD()) || (!lcdPgUpDown && inputMappings->BrowsePageUp())) { if (list->currentIndex == offset) { // If the cursor is already at the top of the window then page up int offsetInWindow = (int)list->currentIndex - (int)rows; if (offsetInWindow < 0) offset = 0; else offset = (u32)offsetInWindow; list->currentIndex = offset; } else { list->currentIndex = offset; // Move the cursor to the top of the window } list->SetCurrent(); dirty = true; } return dirty; } void FileBrowser::BrowsableList::ClearSelections() { u32 entryIndex; for (entryIndex = 0; entryIndex < entries.size(); ++entryIndex) { entries[entryIndex].caddyIndex = -1; } } void FileBrowser::BrowsableList::RefreshViews() { u32 index; for (index = 0; index < views.size(); ++index) { views[index].Refresh(); } } void FileBrowser::BrowsableList::RefreshViewsHighlightScroll() { u32 index; for (index = 0; index < views.size(); ++index) { views[index].RefreshHighlightScroll(); } } bool FileBrowser::BrowsableList::CheckBrowseNavigation() { bool dirty = false; u32 index; for (index = 0; index < views.size(); ++index) { dirty |= views[index].CheckBrowseNavigation(index != 0); } return dirty; } FileBrowser::BrowsableList::Entry* FileBrowser::BrowsableList::FindEntry(const char* name) { int index; int len = (int)entries.size(); for (index = 0; index < len; ++index) { Entry* entry = &entries[index]; if (!(entry->filImage.fattrib & AM_DIR) && strcasecmp(name, entry->filImage.fname) == 0) return entry; } return 0; } FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, unsigned deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate) : state(State_Folders) , diskCaddy(diskCaddy) , selectionsMade(false) , roms(roms) , deviceID(deviceID) , displayPNGIcons(displayPNGIcons) , screenMain(screenMain) , screenLCD(screenLCD) , scrollHighlightRate(scrollHighlightRate) { u32 columns = screenMain->ScaleX(80); u32 rows = (int)(38.0f * screenMain->GetScaleY()); u32 positionX = 0; u32 positionY = 17; if (rows < 1) rows = 1; folder.scrollHighlightRate = scrollHighlightRate; folder.AddView(screenMain, columns, rows, positionX, positionY, false); positionX = screenMain->ScaleX(1024 - 320); caddySelections.AddView(screenMain, 6, rows, positionX, positionY, false); if (screenLCD) { columns = screenLCD->Width() / 8; rows = screenLCD->Height() / 16; positionX = 0; positionY = 0; folder.AddView(screenLCD, columns, rows, positionX, positionY, true); } } u32 FileBrowser::Colour(int index) { return palette[index & 0xf]; } struct greater { bool operator()(const FileBrowser::BrowsableList::Entry& lhs, const FileBrowser::BrowsableList::Entry& rhs) const { if (strcasecmp(lhs.filImage.fname, "..") == 0) return true; else if (strcasecmp(rhs.filImage.fname, "..") == 0) return false; else if (((lhs.filImage.fattrib & AM_DIR) && (rhs.filImage.fattrib & AM_DIR)) || (!(lhs.filImage.fattrib & AM_DIR) && !(rhs.filImage.fattrib & AM_DIR))) return strcasecmp(lhs.filImage.fname, rhs.filImage.fname) < 0; else if ((lhs.filImage.fattrib & AM_DIR) && !(rhs.filImage.fattrib & AM_DIR)) return true; else return false; } }; void FileBrowser::RefreshFolderEntries() { DIR dir; FileBrowser::BrowsableList::Entry entry; FRESULT res; char* ext; folder.Clear(); res = f_opendir(&dir, "."); if (res == FR_OK) { do { res = f_readdir(&dir, &entry.filImage); ext = strrchr(entry.filImage.fname, '.'); if (res == FR_OK && entry.filImage.fname[0] != 0 && !(ext && strcasecmp(ext, ".png") == 0)) folder.entries.push_back(entry); } while (res == FR_OK && entry.filImage.fname[0] != 0); f_closedir(&dir); // Now check for icons res = f_opendir(&dir, "."); if (res == FR_OK) { do { res = f_readdir(&dir, &entry.filIcon); ext = strrchr(entry.filIcon.fname, '.'); if (ext) { int length = ext - entry.filIcon.fname; if (res == FR_OK && entry.filIcon.fname[0] != 0 && strcasecmp(ext, ".png") == 0) { for (unsigned index = 0; index < folder.entries.size(); ++index) { FileBrowser::BrowsableList::Entry* entryAtIndex = &folder.entries[index]; if (strncasecmp(entry.filIcon.fname, entryAtIndex->filImage.fname, length) == 0) entryAtIndex->filIcon = entry.filIcon; } } } } while (res == FR_OK && entry.filIcon.fname[0] != 0); } f_closedir(&dir); strcpy(entry.filImage.fname, ".."); entry.filIcon.fname[0] = 0; folder.entries.push_back(entry); std::sort(folder.entries.begin(), folder.entries.end(), greater()); folder.currentIndex = 0; folder.SetCurrent(); } else { //DEBUG_LOG("Cannot open dir"); } // incase they deleted something selected in the caddy caddySelections.Clear(); } void FileBrowser::FolderChanged() { RefreshFolderEntries(); RefeshDisplay(); } void FileBrowser::DisplayRoot() { f_chdir("\\1541"); FolderChanged(); } /* void FileBrowser::RefeshDisplayForBrowsableList(FileBrowser::BrowsableList* browsableList, int xOffset, bool showSelected) { char buffer1[128] = { 0 }; char buffer2[128] = { 0 }; u32 index; u32 entryIndex; u32 x = xOffset; u32 y = 17; u32 colour; bool terminal = false; RGBA BkColour = RGBA(0, 0, 0, 0xFF); //palette[VIC2_COLOUR_INDEX_BLUE]; if (terminal) printf("\E[2J\E[f"); u32 maxCharacters = screenMain->ScaleX(80); for (index = 0; index < maxOnScreen; ++index) { entryIndex = browsableList->offset + index; if (entryIndex < browsableList->entries.size()) { FileBrowser::BrowsableList::Entry* entry = &browsableList->entries[entryIndex]; snprintf(buffer2, maxCharacters + 1, "%s", entry->filImage.fname); memset(buffer1, ' ', maxCharacters); buffer1[127] = 0; strncpy(buffer1, buffer2, strlen(buffer2)); if (showSelected && browsableList->currentIndex == entryIndex) { if (entry->filImage.fattrib & AM_DIR) { if (terminal) printf("\E[34;47m%s\E[0m\r\n", buffer1); screenMain->PrintText(false, x, y, buffer1, palette[VIC2_COLOUR_INDEX_LBLUE], RGBA(0xff, 0xff, 0xff, 0xff)); } else { colour = RGBA(0xff, 0, 0, 0xff); if (entry->filImage.fattrib & AM_RDO) colour = palette[VIC2_COLOUR_INDEX_RED]; screenMain->PrintText(false, x, y, buffer1, colour, RGBA(0xff, 0xff, 0xff, 0xff)); if (terminal) printf("\E[31;47m%s\E[0m\r\n", buffer1); } } else { if (entry->filImage.fattrib & AM_DIR) { screenMain->PrintText(false, x, y, buffer1, palette[VIC2_COLOUR_INDEX_LBLUE], BkColour); if (terminal) printf("\E[34m%s\E[0m\r\n", buffer1); } else { colour = palette[VIC2_COLOUR_INDEX_LGREY]; if (entry->filImage.fattrib & AM_RDO) colour = palette[VIC2_COLOUR_INDEX_PINK]; screenMain->PrintText(false, x, y, buffer1, colour, BkColour); if (terminal) printf("\E[0;m%s\E[0m\r\n", buffer1); } } } else { memset(buffer1, ' ', 80); screenMain->PrintText(false, x, y, buffer1, BkColour, BkColour); if (terminal) printf("%s\r\n", buffer1); } y += 16; } } */ void FileBrowser::RefeshDisplay() { char buffer[1024]; if (f_getcwd(buffer, 1024) == FR_OK) { u32 textColour = Colour(VIC2_COLOUR_INDEX_LGREEN); u32 bgColour = Colour(VIC2_COLOUR_INDEX_GREY); screenMain->ClearArea(0, 0, (int)screenMain->Width(), 17, bgColour); screenMain->PrintText(false, 0, 0, buffer, textColour, bgColour); } //u32 offsetX = screenMain->ScaleX(1024 - 320); //RefeshDisplayForBrowsableList(&folder, 0); //RefeshDisplayForBrowsableList(&caddySelections, offsetX, false); folder.RefreshViews(); caddySelections.RefreshViews(); DisplayPNG(); DisplayStatusBar(); } bool FileBrowser::CheckForPNG(const char* filename, FILINFO& filIcon) { bool foundValid = false; filIcon.fname[0] = 0; if (DiskImage::IsDiskImageExtention(filename)) { char fileName[256]; char* ptr = strrchr(filename, '.'); if (ptr) { int len = ptr - filename; strncpy(fileName, filename, len); fileName[len] = 0; strcat(fileName, ".png"); if (f_stat(fileName, &filIcon) == FR_OK && filIcon.fsize < FILEBROWSER_MAX_PNG_SIZE) foundValid = true; } } return foundValid; } void FileBrowser::DisplayPNG(FILINFO& filIcon, int x, int y) { if (filIcon.fname[0] != 0 && filIcon.fsize < FILEBROWSER_MAX_PNG_SIZE) { FIL fp; FRESULT res; res = f_open(&fp, filIcon.fname, FA_READ); if (res == FR_OK) { u32 bytesRead; SetACTLed(true); f_read(&fp, PNG, FILEBROWSER_MAX_PNG_SIZE, &bytesRead); SetACTLed(false); f_close(&fp); int w; int h; int channels_in_file; stbi_uc* image = stbi_load_from_memory((stbi_uc const*)PNG, bytesRead, &w, &h, &channels_in_file, 4); if (image && (w == PNG_WIDTH && h == PNG_HEIGHT)) { //DEBUG_LOG("Opened PNG %s w = %d h = %d cif = %d\r\n", fileName, w, h, channels_in_file); screenMain->PlotImage((u32*)image, x, y, w, h); } else { //DEBUG_LOG("Invalid PNG size %d x %d\r\n", w, h); } } } else { //DEBUG_LOG("Cannot find PNG %s\r\n", fileName); } } void FileBrowser::DisplayPNG() { if (displayPNGIcons && folder.current) { FileBrowser::BrowsableList::Entry* current = folder.current; u32 x = screenMain->ScaleX(1024 - 320); u32 y = screenMain->ScaleY(666) - 240; DisplayPNG(current->filIcon, x, y); } } void FileBrowser::PopFolder() { f_chdir(".."); //{ // char buffer[1024]; // if (f_getcwd(buffer, 1024) == FR_OK) // { // DEBUG_LOG("CWD = %s\r\n", buffer); // } //} RefreshFolderEntries(); caddySelections.Clear(); RefeshDisplay(); } void FileBrowser::UpdateCurrentHighlight() { if (folder.entries.size() > 0) { FileBrowser::BrowsableList::Entry* current = folder.current; if (current && folder.currentHighlightTime > 0) { folder.currentHighlightTime -= 0.000001f; if (folder.currentHighlightTime <= 0) { folder.RefreshViewsHighlightScroll(); } if (folder.currentHighlightTime <= 0) { folder.currentHighlightTime = scrollHighlightRate; } } } if (folder.entries.size() > 0) { FileBrowser::BrowsableList::Entry* current = caddySelections.current; if (current && caddySelections.currentHighlightTime > 0) { caddySelections.currentHighlightTime -= 0.000001f; if (caddySelections.currentHighlightTime <= 0) { caddySelections.RefreshViewsHighlightScroll(); } if (caddySelections.currentHighlightTime <= 0) { caddySelections.currentHighlightTime = scrollHighlightRate; } } } } void FileBrowser::Update() { InputMappings* inputMappings = InputMappings::Instance(); Keyboard* keyboard = Keyboard::Instance(); bool dirty = false; if (keyboard->CheckChanged()) dirty = inputMappings->CheckKeyboardBrowseMode(); else dirty = inputMappings->CheckButtonsBrowseMode(); if (dirty) { //if (state == State_Folders) UpdateInputFolders(); //else // UpdateInputDiskCaddy(); } UpdateCurrentHighlight(); } bool FileBrowser::FillCaddyWithSelections() { if (caddySelections.entries.size()) { for (auto it = caddySelections.entries.begin(); it != caddySelections.entries.end();) { bool readOnly = ((*it).filImage.fattrib & AM_RDO) != 0; if (diskCaddy->Insert(&(*it).filImage, readOnly) == false) caddySelections.entries.erase(it); else it++; } return true; } return false; } bool FileBrowser::AddToCaddy(FileBrowser::BrowsableList::Entry* current) { bool added = false; if (current && !(current->filImage.fattrib & AM_DIR) && DiskImage::IsDiskImageExtention(current->filImage.fname)) { bool canAdd = true; unsigned i; for (i = 0; i < caddySelections.entries.size(); ++i) { if (strcmp(current->filImage.fname, caddySelections.entries[i].filImage.fname) == 0) { canAdd = false; break; } } if (canAdd) { current->caddyIndex = caddySelections.entries.size(); caddySelections.entries.push_back(*current); added = true; } } return added; } void FileBrowser::UpdateInputFolders() { Keyboard* keyboard = Keyboard::Instance(); InputMappings* inputMappings = InputMappings::Instance(); if (folder.entries.size() > 0) { //u32 numberOfEntriesMinus1 = folder.entries.size() - 1; bool dirty = false; if (inputMappings->BrowseSelect()) { FileBrowser::BrowsableList::Entry* current = folder.current; if (current) { if (current->filImage.fattrib & AM_DIR) { if (strcmp(current->filImage.fname, "..") == 0) { PopFolder(); } else if (strcmp(current->filImage.fname, ".") != 0) { f_chdir(current->filImage.fname); RefreshFolderEntries(); } dirty = true; } else { if (strcmp(current->filImage.fname, "..") == 0) { PopFolder(); } else if (DiskImage::IsDiskImageExtention(current->filImage.fname)) { DiskImage::DiskType diskType = DiskImage::GetDiskImageTypeViaExtention(current->filImage.fname); // Should also be able to create a LST file from all the images currently selected in the caddy if (diskType == DiskImage::LST) { selectionsMade = SelectLST(current->filImage.fname); } else { // Add the current selected AddToCaddy(current); selectionsMade = FillCaddyWithSelections(); } if (selectionsMade) lastSelectionName = current->filImage.fname; dirty = true; } } } } else if (inputMappings->BrowseDone()) { selectionsMade = FillCaddyWithSelections(); } //else if (keyboard->KeyPressed(KEY_TAB)) //{ // state = State_DiskCaddy; // dirty = true; //} else if (inputMappings->BrowseBack()) { PopFolder(); dirty = true; } else if (inputMappings->Exit()) { ClearSelections(); dirty = true; } else if (inputMappings->BrowseInsert()) { FileBrowser::BrowsableList::Entry* current = folder.current; if (current) { dirty = AddToCaddy(current); } } else { unsigned keySetIndex; for (keySetIndex = 0; keySetIndex < ROMs::MAX_ROMS; ++keySetIndex) { unsigned keySetIndexBase = keySetIndex * 3; if (keyboard->KeyPressed(FileBrowser::SwapKeys[keySetIndexBase]) || keyboard->KeyPressed(FileBrowser::SwapKeys[keySetIndexBase + 1]) || keyboard->KeyPressed(FileBrowser::SwapKeys[keySetIndexBase + 2])) { if (roms->ROMValid[keySetIndex]) { roms->currentROMIndex = keySetIndex; roms->lastManualSelectedROMIndex = keySetIndex; DEBUG_LOG("Swap ROM %d %s\r\n", keySetIndex, roms->ROMNames[keySetIndex]); ShowDeviceAndROM(); } } } dirty = folder.CheckBrowseNavigation(); } if (dirty) RefeshDisplay(); } else { if (inputMappings->BrowseBack()) PopFolder(); } } bool FileBrowser::SelectLST(const char* filenameLST) { bool validImage = false; //DEBUG_LOG("Selected %s\r\n", filenameLST); if (DiskImage::IsLSTExtention(filenameLST)) { DiskImage::DiskType diskType; FIL fp; FRESULT res; res = f_open(&fp, filenameLST, FA_READ); if (res == FR_OK) { u32 bytesRead; SetACTLed(true); f_read(&fp, FileBrowser::LSTBuffer, FileBrowser::LSTBuffer_size, &bytesRead); SetACTLed(false); f_close(&fp); TextParser textParser; textParser.SetData((char*)FileBrowser::LSTBuffer); char* token = textParser.GetToken(true); while (token) { //DEBUG_LOG("LST token = %s\r\n", token); diskType = DiskImage::GetDiskImageTypeViaExtention(token); if (diskType == DiskImage::D64 || diskType == DiskImage::G64 || diskType == DiskImage::NIB || diskType == DiskImage::NBZ) { FileBrowser::BrowsableList::Entry* entry = folder.FindEntry(token); if (entry && !(entry->filImage.fattrib & AM_DIR)) { bool readOnly = (entry->filImage.fattrib & AM_RDO) != 0; if (diskCaddy->Insert(&entry->filImage, readOnly)) validImage = true; } } else { roms->SelectROM(token); } token = textParser.GetToken(true); } } } return validImage; } // Not used void FileBrowser::UpdateInputDiskCaddy() { bool dirty = false; Keyboard* keyboard = Keyboard::Instance(); if (keyboard->KeyPressed(KEY_DELETE)) { } else if (keyboard->KeyPressed(KEY_TAB)) { state = State_Folders; dirty = true; } else { if (keyboard->KeyHeld(KEY_DOWN)) { } if (keyboard->KeyHeld(KEY_UP)) { } } if (dirty) RefeshDisplay(); } void FileBrowser::DisplayStatusBar() { u32 x = 0; u32 y = screenMain->ScaleY(STATUS_BAR_POSITION_Y); char bufferOut[128]; snprintf(bufferOut, 256, "LED 0 Motor 0 Track 18.0 ATN 0 DAT 0 CLK 0"); screenMain->PrintText(false, x, y, bufferOut, RGBA(0, 0, 0, 0xff), RGBA(0xff, 0xff, 0xff, 0xff)); } void FileBrowser::ClearScreen() { u32 bgColour = palette[VIC2_COLOUR_INDEX_BLUE]; screenMain->Clear(bgColour); } void FileBrowser::ClearSelections() { selectionsMade = false; caddySelections.Clear(); folder.ClearSelections(); } void FileBrowser::ShowDeviceAndROM() { char buffer[256]; u32 textColour = RGBA(0, 0, 0, 0xff); u32 bgColour = RGBA(0xff, 0xff, 0xff, 0xff); u32 x = 0; // 43 * 8 u32 y = screenMain->ScaleY(STATUS_BAR_POSITION_Y) - 20; snprintf(buffer, 256, "Device %d %s\r\n", deviceID, roms->ROMNames[roms->currentROMIndex]); screenMain->PrintText(false, x, y, buffer, textColour, bgColour); } void FileBrowser::DisplayDiskInfo(DiskImage* diskImage, const char* filenameForIcon) { // Ideally we should not have to load the entire disk to read the directory. static const char* fileTypes[]= { "DEL", "SEQ", "PRG", "USR", "REL", "UKN", "UKN", "UKN" }; // Decode the BAM unsigned track = 18; unsigned sectorNo = 0; char name[17] = { 0 }; unsigned char buffer[260] = { 0 }; int charIndex; u32 fontHeight = screenMain->GetFontHeight(); u32 x = 0; u32 y = 0; char bufferOut[128] = { 0 }; u32 textColour = palette[VIC2_COLOUR_INDEX_LBLUE]; u32 bgColour = palette[VIC2_COLOUR_INDEX_BLUE]; u32 usedColour = palette[VIC2_COLOUR_INDEX_RED]; u32 freeColour = palette[VIC2_COLOUR_INDEX_LGREEN]; u32 BAMOffsetX = screenMain->ScaleX(400); ClearScreen(); if (diskImage->GetDecodedSector(track, sectorNo, buffer)) { track = buffer[0]; sectorNo = buffer[1]; //144-161 ($90-Al) Name of the disk (padded with "shift space") //162,163 ($A2,$A3) Disk ID marker //164 ($A4) $A0 Shift Space //165,166 ($A5,$A6) $32,$41 ASCII chars "2A" DOS indicator //167-170 ($A7-$AA) $A0 Shift Space //171-255 ($AB-$FF) $00 Not used, filled with zero (The bytes 180 to 191 can have the contents "blocks free" on many disks.) strncpy(name, (char*)&buffer[144], 16); int blocksFree = 0; int bamTrack; int lastTrackUsed = (int)diskImage->LastTrackUsed() >> 1; for (bamTrack = 0; bamTrack < 35; ++bamTrack) { if ((bamTrack + 1) != 18) blocksFree += buffer[BAM_OFFSET + bamTrack * BAM_ENTRY_SIZE]; x = BAMOffsetX; for (int bit = 0; bit < DiskImage::SectorsPerTrack[bamTrack]; bit++) { u32 bits = buffer[BAM_OFFSET + 1 + (bit >> 3) + bamTrack * BAM_ENTRY_SIZE]; bool used = (bits & (1 << (bit & 0x7))) == 0; if (!used) { snprintf(bufferOut, 128, "%c", screen2petscii(87)); screenMain->PrintText(true, x, y, bufferOut, usedColour, bgColour); } else { snprintf(bufferOut, 128, "%c", screen2petscii(81)); screenMain->PrintText(true, x, y, bufferOut, freeColour, bgColour); } x += 8; bits <<= 1; } y += fontHeight; } for (; bamTrack < lastTrackUsed; ++bamTrack) { x = BAMOffsetX; for (int bit = 0; bit < DiskImage::SectorsPerTrack[bamTrack]; bit++) { snprintf(bufferOut, 128, "%c", screen2petscii(87)); screenMain->PrintText(true, x, y, bufferOut, usedColour, bgColour); x += 8; } y += fontHeight; } x = 0; y = 0; snprintf(bufferOut, 128, "0"); screenMain->PrintText(true, x, y, bufferOut, textColour, bgColour); x = 16; snprintf(bufferOut, 128, "\"%s\" %c%c%c%c%c%c", name, buffer[162], buffer[163], buffer[164], buffer[165], buffer[166], buffer[167]); screenMain->PrintText(true, x, y, bufferOut, bgColour, textColour); x = 0; y += fontHeight; if (track != 0) { unsigned trackPrev = 0xff; unsigned sectorPrev = 0xff; bool complete = false; // Blocks 1 through 19 on track 18 contain the file entries. The first two bytes of a block point to the next directory block with file entries. If no more directory blocks follow, these bytes contain $00 and $FF, respectively. while (!complete) { //DEBUG_LOG("track %d sector %d\r\n", track, sectorNo); if (diskImage->GetDecodedSector(track, sectorNo, buffer)) { unsigned trackNext = buffer[0]; unsigned sectorNoNext = buffer[1]; complete = (track == trackNext) && (sectorNo == sectorNoNext); // Detect looping directory entries (raid over moscow ntsc) complete |= (trackNext == trackPrev) && (sectorNoNext == sectorPrev); // Detect looping directory entries (IndustrialBreakdown) complete |= (trackNext == 00) || (sectorNoNext == 0xff); complete |= (trackNext == 18) && (sectorNoNext == 1); trackPrev = track; sectorPrev = sectorNo; track = trackNext; sectorNo = sectorNoNext; int entry; int entryOffset = 2; for (entry = 0; entry < 8; ++entry) { bool done = true; for (int i = 0; i < 0x1d; ++i) { if (buffer[i + entryOffset]) done = false; } if (!done) { u8 fileType = buffer[DIR_ENTRY_OFFSET_TYPE + entryOffset]; u16 blocks = (buffer[DIR_ENTRY_OFFSET_BLOCKS + entryOffset + 1] << 8) | buffer[DIR_ENTRY_OFFSET_BLOCKS + entryOffset]; if (fileType != 0) { // hide scratched files x = 0; for (charIndex = 0; charIndex < DIR_ENTRY_NAME_LENGTH; ++charIndex) { char c = buffer[DIR_ENTRY_OFFSET_NAME + entryOffset + charIndex]; if (c == 0xa0) c = 0x20; name[charIndex] = c; } name[charIndex] = 0; //DEBUG_LOG("%d name = %s %x\r\n", blocks, name, fileType); snprintf(bufferOut, 128, "%d", blocks); screenMain->PrintText(true, x, y, bufferOut, textColour, bgColour); x += 5 * 8; snprintf(bufferOut, 128, "\"%s\"", name); screenMain->PrintText(true, x, y, bufferOut, textColour, bgColour); x += 19 * 8; char modifier = 0x20; if ((fileType & 0x80) == 0) modifier = screen2petscii(42); else if (fileType & 0x40) modifier = screen2petscii(60); snprintf(bufferOut, 128, "%s%c", fileTypes[fileType & 7], modifier); screenMain->PrintText(true, x, y, bufferOut, textColour, bgColour); y += fontHeight; } } entryOffset += 32; } } else { // Error, just abort complete = true; } } } x = 0; //DEBUG_LOG("%d blocks free\r\n", blocksFree); snprintf(bufferOut, 128, "%d BLOCKS FREE.\r\n", blocksFree); screenMain->PrintText(true, x, y, bufferOut, textColour, bgColour); y += fontHeight; } DisplayStatusBar(); if (filenameForIcon) { FILINFO filIcon; if (CheckForPNG(filenameForIcon, filIcon)) { x = screenMain->ScaleX(1024) - 320; y = screenMain->ScaleY(0); DisplayPNG(filIcon, x, y); } } } void FileBrowser::AutoSelectImage(const char* image) { FileBrowser::BrowsableList::Entry* current = 0; int index; int maxEntries = folder.entries.size(); for (index = 0; index < maxEntries; ++index) { current = &folder.entries[index]; if (strcasecmp(current->filImage.fname, image) == 0) { break; } } if (index != maxEntries) { ClearSelections(); caddySelections.entries.push_back(*current); selectionsMade = FillCaddyWithSelections(); } }