// license:BSD-3-Clause // For licensing and usage information, read docs/release/winui_license.txt // standard windows headers #include #include // standard C headers #include #ifdef _MSC_VER #include #endif #include // MAME/MAMEUI headers #include "emu.h" #include "mui_util.h" #include "winui.h" #include "treeview.h" #include "resource.h" #include "mui_opts.h" #include "mui_util.h" #include "emu_opts.h" #include "dialogs.h" #include "winutf8.h" #include "screen.h" #include "drivenum.h" #include "corestr.h" HANDLE winui2_find_first_file_utf8(const char* filename, WIN32_FIND_DATA *findfiledata) { wchar_t *t_filename = ui_wstring_from_utf8(filename); if (!t_filename) return NULL; HANDLE result = FindFirstFile(t_filename, findfiledata); free(t_filename); return result; } bool winui_move_file_utf8(const char* existingfilename, const char* newfilename) { bool result = false; wchar_t *t_existingfilename = ui_wstring_from_utf8(existingfilename); if (!t_existingfilename) return result; wchar_t *t_newfilename = ui_wstring_from_utf8(newfilename); if (!t_newfilename) { free(t_existingfilename); return result; } result = MoveFile(t_existingfilename, t_newfilename); free(t_newfilename); free(t_existingfilename); return result; } const char* GetFolderDir() { string a = dir_get_value(24); return a.c_str(); } /*************************************************************************** private variables ***************************************************************************/ /* this has an entry for every folder eventually in the UI, including subfolders */ static TREEFOLDER **treeFolders = 0; static UINT numFolders = 0; /* Number of folder in the folder array */ static UINT next_folder_id = MAX_FOLDERS; static UINT folderArrayLength = 0; /* Size of the folder array */ static LPTREEFOLDER lpCurrentFolder = 0; /* Currently selected folder */ static UINT nCurrentFolder = 0; /* Current folder ID */ static WNDPROC g_lpTreeWndProc = 0; /* for subclassing the TreeView */ static HIMAGELIST hTreeSmall = 0; /* TreeView Image list of icons */ /* this only has an entry for each TOP LEVEL extra folder + SubFolders*/ LPEXFOLDERDATA ExtraFolderData[MAX_EXTRA_FOLDERS * MAX_EXTRA_SUBFOLDERS]; static int numExtraFolders = 0; static int numExtraIcons = 0; static char *ExtraFolderIcons[MAX_EXTRA_FOLDERS]; // built in folders and filters static LPCFOLDERDATA g_lpFolderData; static LPCFILTER_ITEM g_lpFilterList; /*************************************************************************** private function prototypes ***************************************************************************/ static bool InitFolders(void); static bool CreateTreeIcons(void); static void CreateAllChildFolders(void); static bool AddFolder(LPTREEFOLDER lpFolder); static LPTREEFOLDER NewFolder(const char *lpTitle, UINT nFolderId, int nParent, UINT nIconId, DWORD dwFlags); static void DeleteFolder(LPTREEFOLDER lpFolder); static LRESULT CALLBACK TreeWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static int InitExtraFolders(void); static void FreeExtraFolders(void); static void SetExtraIcons(char *name, int *id); static bool TryAddExtraFolderAndChildren(int parent_index); static bool TrySaveExtraFolder(LPTREEFOLDER lpFolder); static void LoadExternalFolders(int parent_index, int id); static void SaveExternalFolders(int parent_index); //static bool FilterAvailable(int driver_index); /*************************************************************************** Functions to build builtin folder lists ***************************************************************************/ #if 0 static void CreateManufacturerFolders(int parent_index); static void CreateYearFolders(int parent_index); static void CreateSourceFolders(int parent_index); static void CreateDeficiencyFolders(int parent_index); static void CreateBIOSFolders(int parent_index); static void CreateCPUFolders(int parent_index); static void CreateSoundFolders(int parent_index); static void CreateScreenFolders(int parent_index); static void CreateFPSFolders(int parent_index); static void CreateResolutionFolders(int parent_index); static void CreateSaveStateFolders(int parent_index); static void CreateDumpingFolders(int parent_index); #endif /*************************************************************************** public structures ***************************************************************************/ static const TREEICON treeIconNames[] = { // { IDI_FOLDER_OPEN, "foldopen" }, { IDI_FOLDER, "folder" }, { IDI_FOLDER_ALLGAMES, "allgames" }, { IDI_FOLDER_AVAILABLE, "foldavail" }, { IDI_FOLDER_BIOS, "foldcsb" }, { IDI_FOLDER_CLONES, "foldclone" }, { IDI_FOLDER_CPU, "foldcsb" }, { IDI_FOLDER_DUMP, "foldcsb" }, { IDI_FOLDER_FPS, "foldfps" }, { IDI_FOLDER_HARDDISK, "harddisk" }, { IDI_FOLDER_HORIZONTAL, "horz" }, { IDI_FOLDER_IMPERFECT, "foldimp" }, { IDI_FOLDER_LIGHTGUN, "foldgun" }, { IDI_FOLDER_MANUFACTURER, "foldmanu" }, { IDI_FOLDER_MECHANICAL, "mechanical" }, { IDI_FOLDER_MONITOR, "multimon" }, { IDI_FOLDER_NONWORKING, "nonwork" }, { IDI_FOLDER_ORIGINALS, "foldorig" }, { IDI_FOLDER_RASTER, "foldrast" }, { IDI_FOLDER_RESOL, "foldres" }, { IDI_FOLDER_SAMPLES, "foldsamp" }, { IDI_FOLDER_SAVESTATE, "savestate" }, { IDI_FOLDER_SOUND, "foldsamp" }, { IDI_FOLDER_SOURCE, "foldsrc" }, { IDI_SOURCE, "source" }, { IDI_FOLDER_TRACKBALL, "foldball" }, { IDI_FOLDER_UNAVAILABLE, "foldunav" }, { IDI_FOLDER_VECTOR, "foldvect" }, { IDI_FOLDER_VERTICAL, "vert" }, { IDI_FOLDER_WORKING, "working" }, { IDI_FOLDER_YEAR, "foldyear" }, { IDI_BIOS, "bios" }, { IDI_CHIP, "chip" }, { IDI_MANUFACTURER, "manufact" }, { IDI_MONITOR, "monitor" }, { IDI_YEAR, "year" }, }; /*************************************************************************** public functions ***************************************************************************/ /* De-allocate all folder memory */ void FreeFolders(void) { if (treeFolders) { if (numExtraFolders) { FreeExtraFolders(); numFolders -= numExtraFolders; } for (int i = numFolders - 1; i >= 0; i--) { DeleteFolder(treeFolders[i]); treeFolders[i] = NULL; numFolders--; } free(treeFolders); treeFolders = NULL; } numFolders = 0; } /* Reset folder filters */ void ResetFilters(void) { if (treeFolders) for (int i = 0; i < (int)numFolders; i++) treeFolders[i]->m_dwFlags &= ~F_MASK; } void InitTree(LPCFOLDERDATA lpFolderData, LPCFILTER_ITEM lpFilterList) { g_lpFolderData = lpFolderData; g_lpFilterList = lpFilterList; InitFolders(); /* this will subclass the treeview (where WM_DRAWITEM gets sent for the header control) */ LONG_PTR l = GetWindowLongPtr(GetTreeView(), GWLP_WNDPROC); g_lpTreeWndProc = (WNDPROC)l; SetWindowLongPtr(GetTreeView(), GWLP_WNDPROC, (LONG_PTR)TreeWndProc); } void SetCurrentFolder(LPTREEFOLDER lpFolder) { lpCurrentFolder = (lpFolder == 0) ? treeFolders[0] : lpFolder; nCurrentFolder = (lpCurrentFolder) ? lpCurrentFolder->m_nFolderId : 0; } LPTREEFOLDER GetCurrentFolder(void) { return lpCurrentFolder; } UINT GetCurrentFolderID(void) { return nCurrentFolder; } int GetNumFolders(void) { return numFolders; } LPTREEFOLDER GetFolder(UINT nFolder) { return (nFolder < numFolders) ? treeFolders[nFolder] : NULL; } LPTREEFOLDER GetFolderByID(UINT nID) { for (int i = 0; i < numFolders; i++) { if (treeFolders[i]->m_nFolderId == nID) return treeFolders[i]; } return (LPTREEFOLDER)0; } void AddGame(LPTREEFOLDER lpFolder, UINT nGame) { if (lpFolder) SetBit(lpFolder->m_lpGameBits, nGame); } void RemoveGame(LPTREEFOLDER lpFolder, UINT nGame) { ClearBit(lpFolder->m_lpGameBits, nGame); } int FindGame(LPTREEFOLDER lpFolder, int nGame) { return FindBit(lpFolder->m_lpGameBits, nGame, true); } // Called to re-associate games with folders void ResetWhichGamesInFolders(void) { for (int i = 0; i < numFolders; i++) { LPTREEFOLDER lpFolder = treeFolders[i]; // setup the games in our built-in folders for (int k = 0; g_lpFolderData[k].m_lpTitle; k++) { if (lpFolder->m_nFolderId == g_lpFolderData[k].m_nFolderId) { if (g_lpFolderData[k].m_pfnQuery || g_lpFolderData[k].m_bExpectedResult) { SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { // invoke the query function bool b = g_lpFolderData[k].m_pfnQuery ? g_lpFolderData[k].m_pfnQuery(jj) : true; // if we expect false, flip the result if (!g_lpFolderData[k].m_bExpectedResult) b = !b; // if we like what we hear, add the game if (b) AddGame(lpFolder, jj); } } break; } } } } /* Used to build the GameList */ bool GameFiltered(int nGame, DWORD dwMask) { LPTREEFOLDER lpFolder = GetCurrentFolder(); //Filter out the Bioses on all Folders, except for the Bios Folder if(lpFolder && lpFolder->m_nFolderId != FOLDER_BIOS) { if(DriverIsBios(nGame)) return true; } if(driver_list::driver(nGame).name[0] == '_') return true; // Filter games--return true if the game should be HIDDEN in this view if(GetFilterInherit()) { if(lpFolder) { LPTREEFOLDER lpParent = GetFolder(lpFolder->m_nParent); if(lpParent) { /* Check the Parent Filters and inherit them on child, * The inherited filters don't display on the custom Filter Dialog for the Child folder * No need to promote all games to parent folder, works as is */ dwMask |= lpParent->m_dwFlags; } } } if (strlen(GetSearchText()) && _stricmp(GetSearchText(), SEARCH_PROMPT)) if (MyStrStrI(driver_list::driver(nGame).type.fullname(),GetSearchText()) == NULL && MyStrStrI(driver_list::driver(nGame).name,GetSearchText()) == NULL) return true; /*Filter Text is already global*/ if (MyStrStrI(driver_list::driver(nGame).type.fullname(),GetFilterText()) == NULL && MyStrStrI(driver_list::driver(nGame).name,GetFilterText()) == NULL && MyStrStrI(driver_list::driver(nGame).type.source(),GetFilterText()) == NULL && MyStrStrI(driver_list::driver(nGame).manufacturer,GetFilterText()) == NULL) return true; // Are there filters set on this folder? if ((dwMask & F_MASK) == 0) return false; // Filter out clones? if (dwMask & F_CLONES && DriverIsClone(nGame)) return true; for (int i = 0; g_lpFilterList[i].m_dwFilterType; i++) if (dwMask & g_lpFilterList[i].m_dwFilterType) if (g_lpFilterList[i].m_pfnQuery(nGame) == g_lpFilterList[i].m_bExpectedResult) return true; return false; } /* Get the parent of game in this view */ bool GetParentFound(int nGame) { LPTREEFOLDER lpFolder = GetCurrentFolder(); if(lpFolder) { int nParentIndex = GetParentIndex(&driver_list::driver(nGame)); /* return false if no parent is there in this view */ if( nParentIndex == -1) return false; /* return false if the folder should be HIDDEN in this view */ if (TestBit(lpFolder->m_lpGameBits, nParentIndex) == 0) return false; /* return false if the game should be HIDDEN in this view */ if (GameFiltered(nParentIndex, lpFolder->m_dwFlags)) return false; return true; } return false; } LPCFILTER_ITEM GetFilterList(void) { return g_lpFilterList; } /*************************************************************************** private functions ***************************************************************************/ //#if 0 void CreateSourceFolders(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { const char *source = GetDriverFilename(jj); // look for an existant source treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, source) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a source file we haven't seen before, make it. lpTemp = NewFolder(source, next_folder_id++, parent_index, IDI_SOURCE, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } SetNumOptionFolders(-1); } void CreateManufacturerFolders(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { const char *made = driver_list::driver(jj).manufacturer; // look for an existant manufacturer treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, made) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a manufacturer we haven't seen before, make it. lpTemp = NewFolder(made, next_folder_id++, parent_index, IDI_MANUFACTURER, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } void CreateDeficiencyFolders(int parent_index) { LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpWrongCol, lpImpCol, lpImpGraph, lpMissSnd, lpImpSnd, lpIncomplete, lpNoSndHw; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); // create our subfolders lpWrongCol = NewFolder("Wrong Colors", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpImpCol = NewFolder("Imperfect Colors", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpImpGraph = NewFolder("Imperfect Graphics", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpMissSnd = NewFolder("Missing Sound", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpImpSnd = NewFolder("Imperfect Sound", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpIncomplete = NewFolder("Incomplete Prototype", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpNoSndHw = NewFolder("No Sound Hardware", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpWrongCol); AddFolder(lpImpCol); AddFolder(lpImpGraph); AddFolder(lpMissSnd); AddFolder(lpImpSnd); AddFolder(lpIncomplete); AddFolder(lpNoSndHw); for (int jj = 0; jj < driver_list::total(); jj++) { uint32_t cache = GetDriverCacheLower(jj); if (BIT(cache, 21)) AddGame(lpWrongCol, jj); if (BIT(cache, 20)) AddGame(lpImpCol, jj); if (BIT(cache, 18)) AddGame(lpImpGraph, jj); if (BIT(cache, 17)) AddGame(lpMissSnd, jj); if (BIT(cache, 16)) AddGame(lpImpSnd, jj); if (BIT(cache, 15)) AddGame(lpIncomplete, jj); if (BIT(cache, 13)) AddGame(lpNoSndHw, jj); } } void CreateYearFolders(int parent_index) { if (!RequiredDriverCache()) { LoadExternalFolders(parent_index, IDI_YEAR); return; } int i = 0; int nGames = driver_list::total(); int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < nGames; jj++) { char s[16]; int t1 = sizeof(s); memset(s, '\0', sizeof(s)); int t2 = sizeof(driver_list::driver(jj).year)+1; if (t2 > t1) t2 = t1; if (t2) strcpy_s(s, t2, driver_list::driver(jj).year); if (s[0] == '\0' || s[0] == '?') continue; if (s[4] == '?') s[4] = '\0'; // look for an extant year treefolder for this game // (likely to be the previous one, so start at the end) for (i=numFolders-1;i>=start_folder;i--) { if (strncmp(treeFolders[i]->m_lpTitle, s, 4) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder-1) { // nope, it's a year we haven't seen before, make it. //printf("%s ",s); LPTREEFOLDER lpTemp = NewFolder(s, next_folder_id, parent_index, IDI_YEAR, GetFolderFlags(numFolders)); ExtraFolderData[next_folder_id] = (EXFOLDERDATA*)malloc(sizeof(EXFOLDERDATA)); memset(ExtraFolderData[next_folder_id], 0, sizeof(EXFOLDERDATA)); ExtraFolderData[next_folder_id]->m_nFolderId = next_folder_id; ExtraFolderData[next_folder_id]->m_nIconId = IDI_YEAR; ExtraFolderData[next_folder_id]->m_nParent = lpFolder->m_nFolderId; ExtraFolderData[next_folder_id]->m_nSubIconId = -1; strcpy( ExtraFolderData[next_folder_id]->m_szTitle, s ); ExtraFolderData[next_folder_id++]->m_dwFlags = 0; AddFolder(lpTemp); AddGame(lpTemp, jj); } } SaveExternalFolders(parent_index); } void CreateBIOSFolders(int parent_index) { int i = 0; int start_folder = numFolders; const game_driver *drv; int nParentIndex = -1; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { if (DriverIsClone(jj)) { nParentIndex = GetParentIndex(&driver_list::driver(jj)); if (nParentIndex < 0) return; drv = &driver_list::driver(nParentIndex); } else drv = &driver_list::driver(jj); nParentIndex = GetParentIndex(drv); if (nParentIndex < 0 || !driver_list::driver(nParentIndex).type.fullname()) continue; for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, driver_list::driver(nParentIndex).type.fullname()) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { lpTemp = NewFolder(driver_list::driver(nParentIndex).type.fullname(), next_folder_id++, parent_index, IDI_BIOS, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } void CreateScreenFoldersIni(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { char screen[4]; snprintf(screen, std::size(screen), "%d", DriverNumScreens(jj)); // look for an existant screen treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, screen) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a screen we haven't seen before, make it. lpTemp = NewFolder(screen, next_folder_id++, parent_index, IDI_MONITOR, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } void CreateCPUFoldersIni(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { machine_config config(driver_list::driver(jj), MameUIGlobal()); // enumerate through all devices for (device_execute_interface &device : execute_interface_enumerator(config.root_device())) { // get the name const char *cpu = device.device().name(); // look for an existant CPU treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, cpu) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a CPU we haven't seen before, make it. lpTemp = NewFolder(cpu, next_folder_id++, parent_index, IDI_CHIP, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } } void CreateSoundFoldersIni(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); for (int jj = 0; jj < driver_list::total(); jj++) { machine_config config(driver_list::driver(jj), MameUIGlobal()); // enumerate through all devices for (device_sound_interface &device : sound_interface_enumerator(config.root_device())) { // get the name const char *sound = device.device().name(); // look for an existant sound chip treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, sound) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a sound chip we haven't seen before, make it. lpTemp = NewFolder(sound, next_folder_id++, parent_index, IDI_CHIP, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } } void CreateSaveStateFolders(int parent_index) { LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpSupported, lpUnsupported; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits,false); // create our two subfolders lpSupported = NewFolder("Supported", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpUnsupported = NewFolder("Unsupported", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpSupported); AddFolder(lpUnsupported); for (int jj = 0; jj < driver_list::total(); jj++) { if (DriverSupportsSaveState(jj)) AddGame(lpSupported, jj); else AddGame(lpUnsupported, jj); } } void CreateResolutionFoldersIni(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp, lpVectorH, lpVectorV, lpScreenless; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); // create our two subfolders lpVectorH = NewFolder("Vector (H)", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpVectorV = NewFolder("Vector (V)", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpScreenless = NewFolder("Screenless", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpVectorH); AddFolder(lpVectorV); AddFolder(lpScreenless); for (int jj = 0; jj < driver_list::total(); jj++) { machine_config config(driver_list::driver(jj), MameUIGlobal()); char res[32]; if (DriverIsVector(jj)) { if (DriverIsVertical(jj)) { AddGame(lpVectorV, jj); continue; } else { AddGame(lpVectorH, jj); continue; } } const screen_device *screen = screen_device_enumerator(config.root_device()).first(); if (screen == nullptr) { AddGame(lpScreenless, jj); continue; } const rectangle &visarea = screen->visible_area(); if (DriverIsVertical(jj)) snprintf(res, std::size(res), "%d x %d (V)", visarea.width(), visarea.height()); else snprintf(res, std::size(res), "%d x %d (H)", visarea.width(), visarea.height()); // look for an existant resolution treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, res) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a resolution we haven't seen before, make it. lpTemp = NewFolder(res, next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } void CreateFPSFoldersIni(int parent_index) { int i = 0; int start_folder = numFolders; LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpTemp, lpVector, lpScreenless; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits, false); // create our two subfolders lpVector = NewFolder("Vector", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpScreenless = NewFolder("Screenless", next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpVector); AddFolder(lpScreenless); for (int jj = 0; jj < driver_list::total(); jj++) { machine_config config(driver_list::driver(jj), MameUIGlobal()); char fps[16]; if (DriverIsVector(jj)) { AddGame(lpVector, jj); continue; } const screen_device *screen = screen_device_enumerator(config.root_device()).first(); if (screen == nullptr) { AddGame(lpScreenless, jj); continue; } snprintf(fps, std::size(fps), "%f Hz", ATTOSECONDS_TO_HZ(screen->refresh_attoseconds())); // look for an existant refresh treefolder for this game // (likely to be the previous one, so start at the end) for (i = numFolders - 1; i >= start_folder; i--) { if (strcmp(treeFolders[i]->m_lpTitle, fps) == 0) { AddGame(treeFolders[i], jj); break; } } if (i == start_folder - 1) { // nope, it's a refresh we haven't seen before, make it. lpTemp = NewFolder(fps, next_folder_id++, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpTemp); AddGame(lpTemp, jj); } } } void CreateDumpingFoldersIni(int parent_index) { LPTREEFOLDER lpFolder = treeFolders[parent_index]; LPTREEFOLDER lpBadDump, lpNoDump, lpGoodDump; // no games in top level folder SetAllBits(lpFolder->m_lpGameBits,false); // create our two subfolders lpNoDump = NewFolder("No Dump", next_folder_id, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpBadDump = NewFolder("Bad Dump", next_folder_id, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); lpGoodDump = NewFolder("Good Dump", next_folder_id, parent_index, IDI_FOLDER, GetFolderFlags(numFolders)); AddFolder(lpNoDump); AddFolder(lpBadDump); AddFolder(lpGoodDump); for (int jj = 0; jj < driver_list::total(); jj++) { machine_config config(driver_list::driver(jj), MameUIGlobal()); bool bBadDump = false; bool bNoDump = false; if (!DriverUsesRoms(jj)) continue; for (device_t &device : device_enumerator(config.root_device())) { for (const rom_entry *region = rom_first_region(device); region != nullptr; region = rom_next_region(region)) { for (const rom_entry *rom = rom_first_file(region); rom != nullptr; rom = rom_next_file(rom)) { if (ROMREGION_ISROMDATA(region) || ROMREGION_ISDISKDATA(region)) { util::hash_collection hashes(rom->hashdata()); if (hashes.flag(util::hash_collection::FLAG_NO_DUMP)) bNoDump = true; if (hashes.flag(util::hash_collection::FLAG_BAD_DUMP)) bBadDump = true; } } } } if (bNoDump) { AddGame(lpNoDump, jj); continue; } if (bBadDump) { AddGame(lpBadDump, jj); continue; } AddGame(lpGoodDump, jj); } } void CreateCPUFolders(int parent_index) { if (RequiredDriverCache()) { CreateCPUFoldersIni(parent_index); SaveExternalFolders(parent_index); } else LoadExternalFolders(parent_index, IDI_CHIP); SendMessage(GetProgressBar(), PBM_SETPOS, 20, 0); } void CreateSoundFolders(int parent_index) { if (RequiredDriverCache()) { CreateSoundFoldersIni(parent_index); SaveExternalFolders(parent_index); } else LoadExternalFolders(parent_index, IDI_CHIP); SendMessage(GetProgressBar(), PBM_SETPOS, 95, 0); } void CreateScreenFolders(int parent_index) { if (RequiredDriverCache()) { CreateScreenFoldersIni(parent_index); SaveExternalFolders(parent_index); } else LoadExternalFolders(parent_index, IDI_MONITOR); SendMessage(GetProgressBar(), PBM_SETPOS, 80, 0); } void CreateResolutionFolders(int parent_index) { if (RequiredDriverCache()) { CreateResolutionFoldersIni(parent_index); SaveExternalFolders(parent_index); } else LoadExternalFolders(parent_index, IDI_FOLDER); SendMessage(GetProgressBar(), PBM_SETPOS, 65, 0); } void CreateFPSFolders(int parent_index) { if (RequiredDriverCache()) { CreateFPSFoldersIni(parent_index); SaveExternalFolders(parent_index); } else LoadExternalFolders(parent_index, IDI_FOLDER); SendMessage(GetProgressBar(), PBM_SETPOS, 50, 0); } void CreateDumpingFolders(int parent_index) { if (RequiredDriverCache()) { CreateDumpingFoldersIni(parent_index); SaveExternalFolders(parent_index); } else LoadExternalFolders(parent_index, IDI_FOLDER); SendMessage(GetProgressBar(), PBM_SETPOS, 35, 0); } static void LoadExternalFolders(int parent_index, int id) { const char* fname = NULL; LPTREEFOLDER lpFolder = treeFolders[parent_index]; for (int j = 0; g_lpFolderData[j].m_lpTitle; j++) if (strcmp(lpFolder->m_lpTitle, g_lpFolderData[j].m_lpTitle)==0) fname = g_lpFolderData[j].short_name; if (fname == NULL) return; char filename[MAX_PATH]; snprintf(filename, std::size(filename), "%s\\%s.ini", GetFolderDir(), fname); FILE *f = fopen(filename, "r"); if (f == NULL) return; char readbuf[256]; char *name = NULL; LPTREEFOLDER lpTemp = NULL; int current_id = lpFolder->m_nFolderId; while (fgets(readbuf, 256, f)) { /* do we have [...] ? */ if (readbuf[0] == '[') { char *p = strchr(readbuf, ']'); if (p == NULL) continue; *p = '\0'; name = &readbuf[1]; /* is it [FOLDER_SETTINGS]? */ if (strcmp(name, "FOLDER_SETTINGS") == 0) { current_id = -1; continue; } else { /* is it [ROOT_FOLDER]? */ if (!strcmp(name, "ROOT_FOLDER")) { current_id = lpFolder->m_nFolderId; lpTemp = lpFolder; } else { current_id = next_folder_id++; lpTemp = NewFolder(name, current_id, parent_index, id, GetFolderFlags(numFolders)); AddFolder(lpTemp); } } } else if (current_id != -1) { /* string on a line by itself -- game name */ name = strtok(readbuf, " \t\r\n"); if (name == NULL) { current_id = -1; continue; } AddGame(lpTemp, GetGameNameIndex(name)); } } fclose(f); } static void SaveExternalFolders(int parent_index) { const char* fname = NULL; LPTREEFOLDER lpFolder = treeFolders[parent_index]; for (int j = 0; g_lpFolderData[j].m_lpTitle; j++) if (strcmp(lpFolder->m_lpTitle, g_lpFolderData[j].m_lpTitle)==0) fname = g_lpFolderData[j].short_name; if (fname == NULL) return; char filename[MAX_PATH]; snprintf(filename, std::size(filename), "%s\\%s.ini", GetFolderDir(), fname); wchar_t *temp = ui_wstring_from_utf8(GetFolderDir()); CreateDirectory(temp, NULL); free(temp); FILE *f = fopen(filename, "w"); if (f == NULL) return; fprintf(f, "[FOLDER_SETTINGS]\n"); fprintf(f, "RootFolderIcon custom\n"); fprintf(f, "SubFolderIcon custom\n"); /* need to loop over all our TREEFOLDERs--first the root one, then each child. start with the root */ TREEFOLDER *folder_data = lpFolder; fprintf(f, "\n[ROOT_FOLDER]\n"); for (int i = 0; i < driver_list::total(); i++) if (TestBit(folder_data->m_lpGameBits, i)) fprintf(f, "%s\n", driver_list::driver(i).name); /* look through the custom folders for ones with our root as parent */ for (int jj = 0; jj < numFolders; jj++) { folder_data = treeFolders[jj]; if (folder_data->m_nParent >= 0 && treeFolders[folder_data->m_nParent] == lpFolder) { fprintf(f, "\n[%s]\n", folder_data->m_lpTitle); for (int i = 0; i < driver_list::total(); i++) if (TestBit(folder_data->m_lpGameBits, i)) fprintf(f, "%s\n", driver_list::driver(i).name); } } fclose(f); } // creates child folders of all the top level folders, including custom ones void CreateAllChildFolders(void) { int num_top_level_folders = numFolders; for (int i = 0; i < num_top_level_folders; i++) { LPTREEFOLDER lpFolder = treeFolders[i]; LPCFOLDERDATA lpFolderData = NULL; for (int j = 0; g_lpFolderData[j].m_lpTitle; j++) { if (g_lpFolderData[j].m_nFolderId == lpFolder->m_nFolderId) { lpFolderData = &g_lpFolderData[j]; break; } } if (lpFolderData) { if (lpFolderData->m_pfnCreateFolders) lpFolderData->m_pfnCreateFolders(i); } else { if ((lpFolder->m_dwFlags & F_CUSTOM) == 0) continue; // load the extra folder files, which also adds children if (TryAddExtraFolderAndChildren(i) == false) lpFolder->m_nFolderId = FOLDER_NONE; } } } // adds these folders to the treeview void ResetTreeViewFolders(void) { HWND hTreeView = GetTreeView(); TVITEM tvi; TVINSERTSTRUCT tvs; // currently "cached" parent HTREEITEM hti_parent = NULL; int index_parent = -1; (void)TreeView_DeleteAllItems(hTreeView); tvs.hInsertAfter = TVI_LAST; // main items inserted according to g_folderData[] array for (int i = 0; i < numFolders; i++) { LPTREEFOLDER lpFolder = treeFolders[i]; if (lpFolder->m_nParent == -1) { if (lpFolder->m_nFolderId < MAX_FOLDERS) { // it's a built in folder, let's see if we should show it if (GetShowFolder(lpFolder->m_nFolderId) == false) continue; } tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvs.hParent = TVI_ROOT; tvi.pszText = lpFolder->m_lptTitle; tvi.lParam = (LPARAM)lpFolder; tvi.iImage = GetTreeViewIconIndex(lpFolder->m_nIconId); tvi.iSelectedImage = 0; tvs.item = tvi; // Add root branch hti_parent = TreeView_InsertItem(hTreeView, &tvs); continue; } // not a top level branch, so look for parent if (treeFolders[i]->m_nParent != index_parent) { hti_parent = TreeView_GetRoot(hTreeView); while (1) { if (hti_parent == NULL) // couldn't find parent folder, so it's a built-in but // not shown folder break; tvi.hItem = hti_parent; tvi.mask = TVIF_PARAM; (void)TreeView_GetItem(hTreeView, &tvi); if (((LPTREEFOLDER)tvi.lParam) == treeFolders[treeFolders[i]->m_nParent]) break; hti_parent = TreeView_GetNextSibling(hTreeView, hti_parent); } // if parent is not shown, then don't show the child either obviously! if (hti_parent == NULL) continue; index_parent = treeFolders[i]->m_nParent; } tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvs.hParent = hti_parent; tvi.iImage = GetTreeViewIconIndex(treeFolders[i]->m_nIconId); tvi.iSelectedImage = 0; tvi.pszText = treeFolders[i]->m_lptTitle; tvi.lParam = (LPARAM)treeFolders[i]; tvs.item = tvi; // Add it to this tree branch tvs.hInsertAfter = TVI_SORT; // sub items always sorted (void)TreeView_InsertItem(hTreeView, &tvs); tvs.hInsertAfter = TVI_LAST; // restore for next main item } } void SelectTreeViewFolder(int folder_id) { HWND hTreeView = GetTreeView(); HTREEITEM hti = TreeView_GetRoot(hTreeView); TVITEM tvi; memset(&tvi, 0, sizeof(TVITEM)); while (hti != NULL) { tvi.hItem = hti; tvi.mask = TVIF_PARAM; (void)TreeView_GetItem(hTreeView, &tvi); if (((LPTREEFOLDER)tvi.lParam)->m_nFolderId == folder_id) { (void)TreeView_SelectItem(hTreeView,tvi.hItem); SetCurrentFolder((LPTREEFOLDER)tvi.lParam); return; } HTREEITEM hti_next = TreeView_GetChild(hTreeView, hti); if (hti_next == NULL) { hti_next = TreeView_GetNextSibling(hTreeView, hti); if (hti_next == NULL) { hti_next = TreeView_GetParent(hTreeView, hti); if (hti_next != NULL) hti_next = TreeView_GetNextSibling(hTreeView, hti_next); } } hti = hti_next; } // could not find folder to select // make sure we select something tvi.hItem = TreeView_GetRoot(hTreeView); tvi.mask = TVIF_PARAM; (void)TreeView_GetItem(hTreeView, &tvi); (void)TreeView_SelectItem(hTreeView, tvi.hItem); SetCurrentFolder((LPTREEFOLDER)tvi.lParam); } /* * Does this folder have an INI associated with it? * Currently only true for FOLDER_VECTOR and children * of FOLDER_SOURCE. */ static bool FolderHasIni(LPTREEFOLDER lpFolder) { #if 0 if (FOLDER_RASTER == lpFolder->m_nFolderId || FOLDER_VECTOR == lpFolder->m_nFolderId || FOLDER_VERTICAL == lpFolder->m_nFolderId || FOLDER_HORIZONTAL == lpFolder->m_nFolderId) return true; if (lpFolder->m_nParent != -1 && FOLDER_SOURCE == treeFolders[lpFolder->m_nParent]->m_nFolderId) return true; return false; #endif LPCFOLDERDATA data = FindFilter(lpFolder->m_nFolderId); if (data) if (data->m_opttype < OPTIONS_MAX) return true; if (lpFolder->m_nParent != -1 && FOLDER_SOURCE == treeFolders[lpFolder->m_nParent]->m_nFolderId) return true; return false; } /* Add a folder to the list. Does not allocate */ static bool AddFolder(LPTREEFOLDER lpFolder) { TREEFOLDER **tmpTree = NULL; UINT oldFolderArrayLength = folderArrayLength; if (numFolders + 1 >= folderArrayLength) { folderArrayLength += 500; tmpTree = (TREEFOLDER **)malloc(sizeof(TREEFOLDER **) * folderArrayLength); memcpy(tmpTree, treeFolders, sizeof(TREEFOLDER **) * oldFolderArrayLength); if (treeFolders) free(treeFolders); treeFolders = tmpTree; } /* Is there an folder.ini that can be edited? */ if (FolderHasIni(lpFolder)) lpFolder->m_dwFlags |= F_INIEDIT; treeFolders[numFolders] = lpFolder; numFolders++; return true; } /* Allocate and initialize a NEW TREEFOLDER */ static LPTREEFOLDER NewFolder(const char *lpTitle, UINT nFolderId, int nParent, UINT nIconId, DWORD dwFlags) { LPTREEFOLDER lpFolder = (LPTREEFOLDER)malloc(sizeof(TREEFOLDER)); memset(lpFolder, 0, sizeof(TREEFOLDER)); lpFolder->m_lpTitle = (char *)malloc(strlen(lpTitle) + 1); strcpy((char *)lpFolder->m_lpTitle, lpTitle); lpFolder->m_lptTitle = ui_wstring_from_utf8(lpFolder->m_lpTitle); lpFolder->m_lpGameBits = NewBits(driver_list::total()); lpFolder->m_nFolderId = nFolderId; lpFolder->m_nParent = nParent; lpFolder->m_nIconId = nIconId; lpFolder->m_dwFlags = dwFlags; return lpFolder; } /* Deallocate the passed in LPTREEFOLDER */ static void DeleteFolder(LPTREEFOLDER lpFolder) { if (lpFolder) { if (lpFolder->m_lpGameBits) { DeleteBits(lpFolder->m_lpGameBits); lpFolder->m_lpGameBits = 0; } free(lpFolder->m_lptTitle); lpFolder->m_lptTitle = 0; free(lpFolder->m_lpTitle); lpFolder->m_lpTitle = 0; free(lpFolder); } } /* Can be called to re-initialize the array of treeFolders */ static bool InitFolders(void) { int i = 0; DWORD dwFolderFlags = 0; if (treeFolders) { for (i = numFolders - 1; i >= 0; i--) { DeleteFolder(treeFolders[i]); treeFolders[i] = 0; numFolders--; } } numFolders = 0; if (folderArrayLength == 0) { folderArrayLength = 200; treeFolders = (TREEFOLDER **)malloc(sizeof(TREEFOLDER **) * folderArrayLength); if (!treeFolders) { folderArrayLength = 0; return 0; } else memset(treeFolders, 0, sizeof(TREEFOLDER **) * folderArrayLength); } // built-in top level folders for (i = 0; g_lpFolderData[i].m_lpTitle; i++) { LPCFOLDERDATA fData = &g_lpFolderData[i]; /* get the saved folder flags */ dwFolderFlags = GetFolderFlags(numFolders); /* create the folder */ AddFolder(NewFolder(fData->m_lpTitle, fData->m_nFolderId, -1, fData->m_nIconId, dwFolderFlags)); } numExtraFolders = InitExtraFolders(); for (i = 0; i < numExtraFolders; i++) { LPEXFOLDERDATA fExData = ExtraFolderData[i]; // OR in the saved folder flags dwFolderFlags = fExData->m_dwFlags | GetFolderFlags(numFolders); // create the folder int k = 0; //if (RequiredDriverCache()) for (int j = 0; g_lpFolderData[j].m_lpTitle; j++) if (strcmp(fExData->m_szTitle, g_lpFolderData[j].short_name)==0) k++; if (k == 0) AddFolder(NewFolder(fExData->m_szTitle, fExData->m_nFolderId, fExData->m_nParent, fExData->m_nIconId, dwFolderFlags)); } CreateAllChildFolders(); CreateTreeIcons(); ResetWhichGamesInFolders(); ResetTreeViewFolders(); SelectTreeViewFolder(GetSavedFolderID()); LoadFolderFlags(); return true; } // create iconlist and Treeview control static bool CreateTreeIcons(void) { HICON hIcon = NULL; HINSTANCE hInst = GetModuleHandle(NULL); int numIcons = ICON_MAX + numExtraIcons; hTreeSmall = ImageList_Create (16, 16, ILC_COLORDDB | ILC_MASK, numIcons, numIcons); for (int i = 0; i < ICON_MAX; i++) { hIcon = LoadIconFromFile(treeIconNames[i].lpName); if (!hIcon) hIcon = LoadIcon(hInst, MAKEINTRESOURCE(treeIconNames[i].nResourceID)); if (ImageList_AddIcon(hTreeSmall, hIcon) == -1) { ErrorMessageBox("Error creating icon on regular folder, %i %i", i, hIcon != NULL); return false; } } for (int i = 0; i < numExtraIcons; i++) { if ((hIcon = LoadIconFromFile(ExtraFolderIcons[i])) == 0) hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_FOLDER)); if (ImageList_AddIcon(hTreeSmall, hIcon) == -1) { ErrorMessageBox("Error creating icon on extra folder, %i %i", i, hIcon != NULL); return false; } } // Be sure that all the small icons were added. if (ImageList_GetImageCount(hTreeSmall) < numIcons) { ErrorMessageBox("Error with icon list--too few images. %i %i", ImageList_GetImageCount(hTreeSmall), numIcons); return false; } // Be sure that all the small icons were added. if (ImageList_GetImageCount (hTreeSmall) < ICON_MAX) { ErrorMessageBox("Error with icon list--too few images. %i < %i", ImageList_GetImageCount(hTreeSmall), ICON_MAX); return false; } // Associate the image lists with the list view control. (void)TreeView_SetImageList(GetTreeView(), hTreeSmall, TVSIL_NORMAL); return true; } static void TreeCtrlOnPaint(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT rcClip, rcClient; HBITMAP hBackground = GetBackgroundBitmap(); MYBITMAPINFO *bmDesc = GetBackgroundInfo(); HDC hDC = BeginPaint(hWnd, &ps); GetClipBox(hDC, &rcClip); GetClientRect(hWnd, &rcClient); // Create a compatible memory DC HDC memDC = CreateCompatibleDC(hDC); // Select a compatible bitmap into the memory DC HBITMAP bitmap = CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); HBITMAP hOldBitmap = (HBITMAP)SelectObject(memDC, bitmap); // First let the control do its default drawing. CallWindowProc(g_lpTreeWndProc, hWnd, uMsg, (WPARAM)memDC, 0); // Draw bitmap in the background // Now create a mask HDC maskDC = CreateCompatibleDC(hDC); // Create monochrome bitmap for the mask HBITMAP maskBitmap = CreateBitmap(rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, 1, 1, NULL); HBITMAP hOldMaskBitmap = (HBITMAP)SelectObject(maskDC, maskBitmap); SetBkColor(memDC, GetSysColor(COLOR_WINDOW)); // Create the mask from the memory DC BitBlt(maskDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, memDC, rcClient.left, rcClient.top, SRCCOPY); HDC tempDC = CreateCompatibleDC(hDC); HBITMAP hOldHBitmap = (HBITMAP)SelectObject(tempDC, hBackground); HDC imageDC = CreateCompatibleDC(hDC); HBITMAP bmpImage = CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top); HBITMAP hOldBmpImage = (HBITMAP)SelectObject(imageDC, bmpImage); HPALETTE hPAL = GetBackgroundPalette(); if (hPAL == NULL) hPAL = CreateHalftonePalette(hDC); if (GetDeviceCaps(hDC, RASTERCAPS) & RC_PALETTE && hPAL != NULL) { SelectPalette(hDC, hPAL, false); RealizePalette(hDC); SelectPalette(imageDC, hPAL, false); } // Get x and y offset RECT rcRoot; TreeView_GetItemRect(hWnd, TreeView_GetRoot(hWnd), &rcRoot, false); rcRoot.left = -GetScrollPos(hWnd, SB_HORZ); // Draw bitmap in tiled manner to imageDC for (int i = rcRoot.left; i < rcClient.right; i += bmDesc->bmWidth) for (int j = rcRoot.top; j < rcClient.bottom; j += bmDesc->bmHeight) BitBlt(imageDC, i, j, bmDesc->bmWidth, bmDesc->bmHeight, tempDC, 0, 0, SRCCOPY); // Set the background in memDC to black. Using SRCPAINT with black and any other // color results in the other color, thus making black the transparent color SetBkColor(memDC, RGB(0,0,0)); SetTextColor(memDC, RGB(255,255,255)); BitBlt(memDC, rcClip.left, rcClip.top, rcClip.right - rcClip.left, rcClip.bottom - rcClip.top, maskDC, rcClip.left, rcClip.top, SRCAND); // Set the foreground to black. See comment above. SetBkColor(imageDC, RGB(255,255,255)); SetTextColor(imageDC, RGB(0,0,0)); BitBlt(imageDC, rcClip.left, rcClip.top, rcClip.right - rcClip.left, rcClip.bottom - rcClip.top, maskDC, rcClip.left, rcClip.top, SRCAND); // Combine the foreground with the background BitBlt(imageDC, rcClip.left, rcClip.top, rcClip.right - rcClip.left, rcClip.bottom - rcClip.top, memDC, rcClip.left, rcClip.top, SRCPAINT); // Draw the final image to the screen BitBlt(hDC, rcClip.left, rcClip.top, rcClip.right - rcClip.left, rcClip.bottom - rcClip.top, imageDC, rcClip.left, rcClip.top, SRCCOPY); SelectObject(maskDC, hOldMaskBitmap); SelectObject(tempDC, hOldHBitmap); SelectObject(imageDC, hOldBmpImage); DeleteDC(maskDC); DeleteDC(imageDC); DeleteDC(tempDC); DeleteBitmap(bmpImage); DeleteBitmap(maskBitmap); if (GetBackgroundPalette() == NULL) { DeletePalette(hPAL); hPAL = NULL; } SelectObject(memDC, hOldBitmap); DeleteBitmap(bitmap); DeleteDC(memDC); EndPaint(hWnd, &ps); ReleaseDC(hWnd, hDC); } /* Header code - Directional Arrows */ static LRESULT CALLBACK TreeWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (GetBackgroundBitmap() != NULL) { switch (uMsg) { case WM_MOUSEMOVE: { if (MouseHasBeenMoved()) ShowCursor(true); break; } case WM_KEYDOWN : if (wParam == VK_F2) { if (lpCurrentFolder->m_dwFlags & F_CUSTOM) { TreeView_EditLabel(hWnd,TreeView_GetSelection(hWnd)); return true; } } break; case WM_ERASEBKGND: return true; case WM_PAINT: TreeCtrlOnPaint(hWnd, uMsg, wParam, lParam); UpdateWindow(hWnd); break; } } /* message not handled */ return CallWindowProc(g_lpTreeWndProc, hWnd, uMsg, wParam, lParam); } /* * Filter code - should be moved to filter.c/filter.h * Added 01/09/99 - MSH */ /* find a FOLDERDATA by folderID */ LPCFOLDERDATA FindFilter(DWORD folderID) { for (int i = 0; g_lpFolderData[i].m_lpTitle; i++) if (g_lpFolderData[i].m_nFolderId == folderID) return &g_lpFolderData[i]; return (LPFOLDERDATA) 0; } LPTREEFOLDER GetFolderByName(int nParentId, const char *pszFolderName) { //First Get the Parent TreeviewItem //Enumerate Children for (int i = 0; i < numFolders; i++) { if (!strcmp(treeFolders[i]->m_lpTitle, pszFolderName)) { int nParent = treeFolders[i]->m_nParent; if ((nParent >= 0) && treeFolders[nParent]->m_nFolderId == nParentId) return treeFolders[i]; } } return NULL; } static int InitExtraFolders(void) { WIN32_FIND_DATA FindFileData; int count = 0; char buf[256]; char path[MAX_PATH]; const char *dir = GetFolderDir(); memset(ExtraFolderData, 0, (MAX_EXTRA_FOLDERS * MAX_EXTRA_SUBFOLDERS)* sizeof(LPEXFOLDERDATA)); // Why create the directory if it doesn't exist, just return 0 folders. if (osd::directory::open(dir) == nullptr) return 0; snprintf(path, std::size(path), "%s\\*.*", dir); HANDLE hFind = winui2_find_first_file_utf8(path, &FindFileData); for (int i = 0; i < MAX_EXTRA_FOLDERS; i++) { ExtraFolderIcons[i] = NULL; } numExtraIcons = 0; if (hFind != INVALID_HANDLE_VALUE) { while (FindNextFile (hFind, &FindFileData) != 0) { char *file = ui_utf8_from_wstring(FindFileData.cFileName); char inifile[MAX_PATH]; memset(&inifile, 0, sizeof(inifile)); snprintf(inifile, std::size(inifile), "%s\\%s", dir, file); FILE *readfile = fopen(inifile, "r"); if (readfile != NULL) { int icon[2] = { 0, 0 }; while (fgets(buf, 256, readfile)) { if (buf[0] == '[') { char *p = strchr(buf, ']'); if (p == NULL) continue; *p = '\0'; char *name = &buf[1]; if (!strcmp(name, "FOLDER_SETTINGS")) { while (fgets(buf, 256, readfile)) { name = strtok(buf, " =\r\n"); if (name == NULL) break; if (!strcmp(name, "RootFolderIcon")) { name = strtok(NULL, " =\r\n"); if (name != NULL) SetExtraIcons(name, &icon[0]); } if (!strcmp(name, "SubFolderIcon")) { name = strtok(NULL, " =\r\n"); if (name != NULL) SetExtraIcons(name, &icon[1]); } } break; } } } fclose(readfile); strcpy(buf, file); char *ext = strrchr(buf, '.'); free(file); if (ext && *(ext + 1) && !core_stricmp(ext + 1, "ini")) { ExtraFolderData[count] =(EXFOLDERDATA*) malloc(sizeof(EXFOLDERDATA)); if (ExtraFolderData[count]) { *ext = '\0'; memset(ExtraFolderData[count], 0, sizeof(EXFOLDERDATA)); strncpy(ExtraFolderData[count]->m_szTitle, buf, 63); ExtraFolderData[count]->m_nFolderId = next_folder_id++; ExtraFolderData[count]->m_nParent = -1; ExtraFolderData[count]->m_dwFlags = F_CUSTOM; ExtraFolderData[count]->m_nIconId = icon[0] ? -icon[0] : IDI_FOLDER; ExtraFolderData[count]->m_nSubIconId = icon[1] ? -icon[1] : IDI_FOLDER; count++; } } } } } return count; } void FreeExtraFolders(void) { for (int i = 0; i < numExtraFolders; i++) { if (ExtraFolderData[i]) { free(ExtraFolderData[i]); ExtraFolderData[i] = NULL; } } for (int i = 0; i < numExtraIcons; i++) { free(ExtraFolderIcons[i]); } numExtraIcons = 0; } static void SetExtraIcons(char *name, int *id) { char *p = strchr(name, '.'); if (p != NULL) *p = '\0'; ExtraFolderIcons[numExtraIcons] = (char*)malloc(strlen(name) + 1); if (ExtraFolderIcons[numExtraIcons]) { *id = ICON_MAX + numExtraIcons; strcpy(ExtraFolderIcons[numExtraIcons], name); numExtraIcons++; } } // Called to add child folders of the top level extra folders already created bool TryAddExtraFolderAndChildren(int parent_index) { char fname[MAX_PATH]; char readbuf[256]; char *name = NULL; LPTREEFOLDER lpTemp = NULL; LPTREEFOLDER lpFolder = treeFolders[parent_index]; int current_id = lpFolder->m_nFolderId; int id = lpFolder->m_nFolderId - MAX_FOLDERS; snprintf(fname, std::size(fname), "%s\\%s.ini", GetFolderDir(), ExtraFolderData[id]->m_szTitle); FILE *f = fopen(fname, "r"); if (f == NULL) return false; while (fgets(readbuf, 256, f)) { /* do we have [...] ? */ if (readbuf[0] == '[') { char *p = strchr(readbuf, ']'); if (p == NULL) continue; *p = '\0'; name = &readbuf[1]; /* is it [FOLDER_SETTINGS]? */ if (strcmp(name, "FOLDER_SETTINGS") == 0) { current_id = -1; continue; } else { /* is it [ROOT_FOLDER]? */ if (!strcmp(name, "ROOT_FOLDER")) { current_id = lpFolder->m_nFolderId; lpTemp = lpFolder; } else { /* must be [folder name] */ current_id = next_folder_id++; /* create a new folder with this name, and the flags for this folder as read from the registry */ lpTemp = NewFolder(name, current_id, parent_index, ExtraFolderData[id]->m_nSubIconId, GetFolderFlags(numFolders) | F_CUSTOM); ExtraFolderData[current_id] = (EXFOLDERDATA*)malloc(sizeof(EXFOLDERDATA)); memset(ExtraFolderData[current_id], 0, sizeof(EXFOLDERDATA)); ExtraFolderData[current_id]->m_nFolderId = current_id - MAX_EXTRA_FOLDERS; ExtraFolderData[current_id]->m_nIconId = ExtraFolderData[id]->m_nSubIconId; ExtraFolderData[current_id]->m_nParent = ExtraFolderData[id]->m_nFolderId; ExtraFolderData[current_id]->m_nSubIconId = -1; strcpy(ExtraFolderData[current_id]->m_szTitle, name); ExtraFolderData[current_id]->m_dwFlags = ExtraFolderData[id]->m_dwFlags; AddFolder(lpTemp); } } } else if (current_id != -1) { /* string on a line by itself -- game name */ name = strtok(readbuf, " \t\r\n"); if (name == NULL) { current_id = -1; continue; } /* IMPORTANT: This assumes that all driver names are lowercase! */ for (int i = 0; name[i]; i++) name[i] = tolower(name[i]); if (lpTemp == NULL) { ErrorMessageBox("Error parsing %s: missing [folder name] or [ROOT_FOLDER]", fname); current_id = lpFolder->m_nFolderId; lpTemp = lpFolder; } AddGame(lpTemp, GetGameNameIndex(name)); } } fclose(f); return true; } void GetFolders(TREEFOLDER ***folders,int *num_folders) { *folders = treeFolders; *num_folders = numFolders; } static bool TryRenameCustomFolderIni(LPTREEFOLDER lpFolder, const char *old_name, const char *new_name) { char filename[MAX_PATH]; char new_filename[MAX_PATH]; if (lpFolder->m_nParent >= 0) { //it is a custom SubFolder LPTREEFOLDER lpParent = GetFolder(lpFolder->m_nParent); if(lpParent) { snprintf(filename, std::size(filename), "%s\\%s\\%s.ini", GetIniDir().c_str(), lpParent->m_lpTitle, old_name); snprintf(new_filename, std::size(new_filename), "%s\\%s\\%s.ini", GetIniDir().c_str(), lpParent->m_lpTitle, new_name); winui_move_file_utf8(filename, new_filename); } } else { //Rename the File, if it exists snprintf(filename, std::size(filename), "%s\\%s.ini", GetIniDir().c_str(), old_name); snprintf(new_filename, std::size(new_filename), "%s\\%s.ini", GetIniDir().c_str(), new_name); winui_move_file_utf8(filename, new_filename); //Rename the Directory, if it exists snprintf(filename, std::size(filename), "%s\\%s", GetIniDir().c_str(), old_name); snprintf(new_filename, std::size(new_filename), "%s\\%s", GetIniDir().c_str(), new_name); winui_move_file_utf8(filename, new_filename); } return true; } bool TryRenameCustomFolder(LPTREEFOLDER lpFolder, const char *new_name) { char filename[MAX_PATH]; char new_filename[MAX_PATH]; if (lpFolder->m_nParent >= 0) { // a child extra folder was renamed, so do the rename and save the parent // save old title char *old_title = lpFolder->m_lpTitle; // set new title lpFolder->m_lpTitle = (char *)malloc(strlen(new_name) + 1); strcpy(lpFolder->m_lpTitle, new_name); // try to save if (TrySaveExtraFolder(lpFolder) == false) { // failed, so free newly allocated title and restore old free(lpFolder->m_lpTitle); lpFolder->m_lpTitle = old_title; return false; } TryRenameCustomFolderIni(lpFolder, old_title, new_name); // successful, so free old title free(old_title); return true; } // a parent extra folder was renamed, so rename the file snprintf(new_filename, std::size(new_filename), "%s\\%s.ini", GetFolderDir(), new_name); snprintf(filename, std::size(filename), "%s\\%s.ini", GetFolderDir(), lpFolder->m_lpTitle); bool retval = winui_move_file_utf8(filename, new_filename); if (retval) { TryRenameCustomFolderIni(lpFolder, lpFolder->m_lpTitle, new_name); free(lpFolder->m_lpTitle); lpFolder->m_lpTitle = (char *)malloc(strlen(new_name) + 1); strcpy(lpFolder->m_lpTitle, new_name); } else ErrorMessageBox("Error while renaming custom file %s to %s", filename, new_filename); return retval; } void AddToCustomFolder(LPTREEFOLDER lpFolder, int driver_index) { if ((lpFolder->m_dwFlags & F_CUSTOM) == 0) { ErrorMessageBox("Unable to add game to non-custom folder"); return; } if (TestBit(lpFolder->m_lpGameBits, driver_index) == 0) { AddGame(lpFolder, driver_index); if (TrySaveExtraFolder(lpFolder) == false) RemoveGame(lpFolder, driver_index); // undo on error } } void RemoveFromCustomFolder(LPTREEFOLDER lpFolder, int driver_index) { if ((lpFolder->m_dwFlags & F_CUSTOM) == 0) { ErrorMessageBox("Unable to remove game from non-custom folder"); return; } if (TestBit(lpFolder->m_lpGameBits, driver_index) != 0) { RemoveGame(lpFolder, driver_index); if (TrySaveExtraFolder(lpFolder) == false) AddGame(lpFolder, driver_index); // undo on error } } bool TrySaveExtraFolder(LPTREEFOLDER lpFolder) { char fname[MAX_PATH]; bool error = false; LPTREEFOLDER root_folder = NULL; LPEXFOLDERDATA extra_folder = NULL; for (int i = 0; i < numExtraFolders; i++) { if (ExtraFolderData[i]->m_nFolderId == lpFolder->m_nFolderId) { root_folder = lpFolder; extra_folder = ExtraFolderData[i]; break; } if (lpFolder->m_nParent >= 0 && ExtraFolderData[i]->m_nFolderId == treeFolders[lpFolder->m_nParent]->m_nFolderId) { root_folder = treeFolders[lpFolder->m_nParent]; extra_folder = ExtraFolderData[i]; break; } } if (extra_folder == NULL || root_folder == NULL) { ErrorMessageBox("Error finding custom file name to save"); return false; } snprintf(fname, std::size(fname), "%s\\%s.ini", GetFolderDir(), extra_folder->m_szTitle); wchar_t *temp = ui_wstring_from_utf8(GetFolderDir()); CreateDirectory(temp, NULL); free(temp); FILE *f = fopen(fname, "w"); if (f == NULL) error = true; else { TREEFOLDER *folder_data; fprintf(f, "[FOLDER_SETTINGS]\n"); // negative values for icons means it's custom, so save 'em if (extra_folder->m_nIconId < 0) fprintf(f, "RootFolderIcon %s\n", ExtraFolderIcons[(-extra_folder->m_nIconId) - ICON_MAX]); if (extra_folder->m_nSubIconId < 0) fprintf(f, "SubFolderIcon %s\n", ExtraFolderIcons[(-extra_folder->m_nSubIconId) - ICON_MAX]); /* need to loop over all our TREEFOLDERs--first the root one, then each child. start with the root */ folder_data = root_folder; fprintf(f, "\n[ROOT_FOLDER]\n"); for (int i = 0; i < driver_list::total(); i++) { if (TestBit(folder_data->m_lpGameBits, i)) fprintf(f, "%s\n", driver_list::driver(i).name); } /* look through the custom folders for ones with our root as parent */ for (int j = 0; j < numFolders; j++) { folder_data = treeFolders[j]; if (folder_data->m_nParent >= 0 && treeFolders[folder_data->m_nParent] == root_folder) { fprintf(f, "\n[%s]\n", folder_data->m_lpTitle); for (int i = 0; i < driver_list::total(); i++) { if (TestBit(folder_data->m_lpGameBits, i)) fprintf(f, "%s\n", driver_list::driver(i).name); } } } fclose(f); } if (error) ErrorMessageBox("Error while saving custom file %s", fname); return !error; } HIMAGELIST GetTreeViewIconList(void) { return hTreeSmall; } int GetTreeViewIconIndex(int icon_id) { if (icon_id < 0) return -icon_id; for (int i = 0; i < std::size(treeIconNames); i++) { if (icon_id == treeIconNames[i].nResourceID) return i; } return -1; } #if 0 bool FilterAvailable(int driver_index) { if (!DriverUsesRoms(driver_index)) { if (GetDisplayNoRomsGames()) return true; else return false; } return IsAuditResultYes(GetRomAuditResults(driver_index)); } #endif