From 5c91ab365d6e6fa6dec7848aceb59fdc352e6d83 Mon Sep 17 00:00:00 2001 From: Birunthan Mohanathas Date: Sun, 16 Jun 2013 19:14:59 +0300 Subject: [PATCH] Refactor skin scanning and skin folder indexing into SkinRegistry.cpp This also adds tests. --- Library/CommandHandler.cpp | 19 +- Library/DialogManage.cpp | 24 +- Library/Library.vcxproj | 7 + Library/Library.vcxproj.filters | 9 + Library/Rainmeter.cpp | 315 +++++------------- Library/Rainmeter.h | 46 +-- Library/SkinRegistry.cpp | 243 ++++++++++++++ Library/SkinRegistry.h | 101 ++++++ Library/SkinRegistry_Test.cpp | 105 ++++++ Library/Test/SkinRegistry/@Backup/1.ini | 0 Library/Test/SkinRegistry/A1/B1/1.ini | 0 Library/Test/SkinRegistry/A1/B2/1.ini | 0 Library/Test/SkinRegistry/A1/B2/C1/1.ini | 0 Library/Test/SkinRegistry/A1/B3/.gitkeep | 0 Library/Test/SkinRegistry/A2/1.ini | 0 Library/Test/SkinRegistry/A2/2.ini | 0 Library/Test/SkinRegistry/A2/3.ini | 0 Library/Test/SkinRegistry/A2/@Resources/1.ini | 0 Library/Test/SkinRegistry/A2/B1/.gitkeep | 0 Library/TrayWindow.cpp | 6 +- 20 files changed, 565 insertions(+), 310 deletions(-) create mode 100644 Library/SkinRegistry.cpp create mode 100644 Library/SkinRegistry.h create mode 100644 Library/SkinRegistry_Test.cpp create mode 100644 Library/Test/SkinRegistry/@Backup/1.ini create mode 100644 Library/Test/SkinRegistry/A1/B1/1.ini create mode 100644 Library/Test/SkinRegistry/A1/B2/1.ini create mode 100644 Library/Test/SkinRegistry/A1/B2/C1/1.ini create mode 100644 Library/Test/SkinRegistry/A1/B3/.gitkeep create mode 100644 Library/Test/SkinRegistry/A2/1.ini create mode 100644 Library/Test/SkinRegistry/A2/2.ini create mode 100644 Library/Test/SkinRegistry/A2/3.ini create mode 100644 Library/Test/SkinRegistry/A2/@Resources/1.ini create mode 100644 Library/Test/SkinRegistry/A2/B1/.gitkeep diff --git a/Library/CommandHandler.cpp b/Library/CommandHandler.cpp index 08393804..9f1f3284 100644 --- a/Library/CommandHandler.cpp +++ b/Library/CommandHandler.cpp @@ -577,26 +577,11 @@ void CommandHandler::DoActivateSkinBang(std::vector& args, MeterWi { if (args.size() == 1) { - int index = GetRainmeter().FindSkinFolderIndex(args[0]); - if (index != -1) - { - const Rainmeter::SkinFolder& skinFolder = GetRainmeter().m_SkinFolders[index]; - if (!(skinFolder.active == 1 && skinFolder.files.size() == 1)) - { - // Activate the next index. - GetRainmeter().ActivateSkin(index, (skinFolder.active < skinFolder.files.size()) ? skinFolder.active : 0); - } - return; - } + if (GetRainmeter().ActivateSkin(args[0])) return; } else if (args.size() > 1) { - std::pair indexes = GetRainmeter().GetMeterWindowIndex(args[0], args[1]); - if (indexes.first != -1 && indexes.second != -1) - { - GetRainmeter().ActivateSkin(indexes.first, indexes.second); - return; - } + if (GetRainmeter().ActivateSkin(args[0], args[1])) return; } LogError(L"!ActivateConfig: Invalid parameters"); diff --git a/Library/DialogManage.cpp b/Library/DialogManage.cpp index b9a42ab1..fcfb3c99 100644 --- a/Library/DialogManage.cpp +++ b/Library/DialogManage.cpp @@ -570,7 +570,7 @@ void DialogManage::TabSkins::Update(MeterWindow* meterWindow, bool deleted) tvi.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvi.item.iImage = tvi.item.iSelectedImage = 0; - if (!GetRainmeter().m_SkinFolders.empty()) + if (!GetRainmeter().m_SkinRegistry.IsEmpty()) { PopulateTree(item, tvi); } @@ -881,12 +881,12 @@ std::wstring DialogManage::TabSkins::GetTreeSelectionPath(HWND tree) */ int DialogManage::TabSkins::PopulateTree(HWND tree, TVINSERTSTRUCT& tvi, int index) { - int initialLevel = GetRainmeter().m_SkinFolders[index].level; + int initialLevel = GetRainmeter().m_SkinRegistry.GetFolder(index).level; - const size_t max = GetRainmeter().m_SkinFolders.size(); + const size_t max = GetRainmeter().m_SkinRegistry.GetFolderCount(); while (index < max) { - const Rainmeter::SkinFolder& skinFolder = GetRainmeter().m_SkinFolders[index]; + const auto& skinFolder = GetRainmeter().m_SkinRegistry.GetFolder(index); if (skinFolder.level != initialLevel) { return index - 1; @@ -901,7 +901,7 @@ int DialogManage::TabSkins::PopulateTree(HWND tree, TVINSERTSTRUCT& tvi, int ind // Add subfolders if ((index + 1) < max && - GetRainmeter().m_SkinFolders[index + 1].level == initialLevel + 1) + GetRainmeter().m_SkinRegistry.GetFolder(index + 1).level == initialLevel + 1) { index = PopulateTree(tree, tvi, index + 1); } @@ -1048,11 +1048,12 @@ INT_PTR DialogManage::TabSkins::OnCommand(WPARAM wParam, LPARAM lParam) if (!m_SkinWindow) { // Skin not active, load - std::pair indexes = GetRainmeter().GetMeterWindowIndex(m_SkinFolderPath, m_SkinFileName); - if (indexes.first != -1 && indexes.second != -1) + const SkinRegistry::Indexes indexes = + GetRainmeter().m_SkinRegistry.FindIndexes(m_SkinFolderPath, m_SkinFileName); + if (indexes.IsValid()) { m_HandleCommands = false; - GetRainmeter().ActivateSkin(indexes.first, indexes.second); + GetRainmeter().ActivateSkin(indexes.folder, indexes.file); m_HandleCommands = true; // Fake selection change to update controls @@ -1147,10 +1148,11 @@ INT_PTR DialogManage::TabSkins::OnCommand(WPARAM wParam, LPARAM lParam) Edit_SetSel((HWND)lParam, LOWORD(sel), HIWORD(sel)); WritePrivateProfileString(m_SkinFolderPath.c_str(), L"LoadOrder", buffer, GetRainmeter().GetIniFile().c_str()); - std::pair indexes = GetRainmeter().GetMeterWindowIndex(m_SkinWindow); - if (indexes.first != -1) + const SkinRegistry::Indexes indexes = GetRainmeter().m_SkinRegistry.FindIndexes( + m_SkinWindow->GetFolderPath(), m_SkinWindow->GetFileName()); + if (indexes.IsValid()) { - GetRainmeter().SetLoadOrder(indexes.first, value); + GetRainmeter().SetLoadOrder(indexes.folder, value); std::multimap windows; GetRainmeter().GetMeterWindowsByLoadOrder(windows); diff --git a/Library/Library.vcxproj b/Library/Library.vcxproj index 007e4772..02836968 100644 --- a/Library/Library.vcxproj +++ b/Library/Library.vcxproj @@ -218,6 +218,12 @@ Use + + Use + + + $(ExcludeTests) + Create @@ -352,6 +358,7 @@ + diff --git a/Library/Library.vcxproj.filters b/Library/Library.vcxproj.filters index 1595f546..bf430c30 100644 --- a/Library/Library.vcxproj.filters +++ b/Library/Library.vcxproj.filters @@ -381,6 +381,12 @@ Source Files + + Source Files + + + Source Files + @@ -653,6 +659,9 @@ Header Files + + Header Files + diff --git a/Library/Rainmeter.cpp b/Library/Rainmeter.cpp index 4671265a..04ce7fe8 100644 --- a/Library/Rainmeter.cpp +++ b/Library/Rainmeter.cpp @@ -375,7 +375,7 @@ int Rainmeter::Initialize(LPCWSTR iniPath, LPCWSTR layout) ReloadSettings(); - if (m_SkinFolders.empty()) + if (m_SkinRegistry.IsEmpty()) { std::wstring error = GetFormattedString(ID_STR_NOAVAILABLESKINS, m_SkinPath.c_str()); ShowMessage(nullptr, error.c_str(), MB_OK | MB_ICONERROR); @@ -794,24 +794,63 @@ void Rainmeter::ActivateActiveSkins() std::multimap::const_iterator iter = m_SkinOrders.begin(); for ( ; iter != m_SkinOrders.end(); ++iter) { - const SkinFolder& skinFolder = m_SkinFolders[(*iter).second]; - if (skinFolder.active > 0 && skinFolder.active <= (int)skinFolder.files.size()) + const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder((*iter).second); + if (skinFolder.active > 0 && skinFolder.active <= (uint16_t)skinFolder.files.size()) { ActivateSkin((*iter).second, skinFolder.active - 1); } } } +/* +** Activates the skin, or, if it is already active, the next variant of the skin. Returns true +** if the skin was activated (or was already active). +*/ +bool Rainmeter::ActivateSkin(const std::wstring& folderPath) +{ + const int index = m_SkinRegistry.FindFolderIndex(folderPath); + if (index != -1) + { + const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); + if (!(skinFolder.active == 1 && skinFolder.files.size() == 1)) + { + // Activate the next index. + ActivateSkin( + index, (skinFolder.active < skinFolder.files.size()) ? skinFolder.active : 0); + } + + return true; + } + + return false; +} + +/* +** Activates the skin, or, if it is already active, the next variant of the skin. Returns true +** if the skin was activated (or was already active). +*/ +bool Rainmeter::ActivateSkin(const std::wstring& folderPath, const std::wstring& file) +{ + const SkinRegistry::Indexes indexes = m_SkinRegistry.FindIndexes(folderPath, file); + if (indexes.IsValid()) + { + ActivateSkin(indexes.folder, indexes.file); + return true; + } + + return false; +} + void Rainmeter::ActivateSkin(int folderIndex, int fileIndex) { - if (folderIndex >= 0 && folderIndex < (int)m_SkinFolders.size() && - fileIndex >= 0 && fileIndex < (int)m_SkinFolders[folderIndex].files.size()) + if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount() && + fileIndex >= 0 && fileIndex < m_SkinRegistry.GetFolder(folderIndex).files.size()) { - SkinFolder& skinFolder = m_SkinFolders[folderIndex]; + auto& skinFolder = m_SkinRegistry.GetFolder(folderIndex); const std::wstring& file = skinFolder.files[fileIndex]; const WCHAR* fileSz = file.c_str(); - std::wstring folderPath = GetFolderPath(folderIndex); + std::wstring folderPath = m_SkinRegistry.GetFolderPath(folderIndex); // Verify that the skin is not already active std::map::const_iterator iter = m_MeterWindows.find(folderPath); @@ -854,16 +893,16 @@ void Rainmeter::ActivateSkin(int folderIndex, int fileIndex) void Rainmeter::DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool save) { - if (folderIndex >= 0 && folderIndex < (int)m_SkinFolders.size()) + if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount()) { - m_SkinFolders[folderIndex].active = 0; // Deactivate the skin + m_SkinRegistry.GetFolder(folderIndex).active = 0; // Deactivate the skin } else if (folderIndex == -1 && meterWindow) { - folderIndex = FindSkinFolderIndex(meterWindow->GetFolderPath()); - if (folderIndex != -1) + SkinRegistry::Folder* folder = m_SkinRegistry.FindFolder(meterWindow->GetFolderPath()); + if (folder) { - m_SkinFolders[folderIndex].active = 0; + folder->active = 0; } } @@ -881,12 +920,12 @@ void Rainmeter::DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool s void Rainmeter::ToggleSkin(int folderIndex, int fileIndex) { - if (folderIndex >= 0 && folderIndex < (int)m_SkinFolders.size() && - fileIndex >= 0 && fileIndex < (int)m_SkinFolders[folderIndex].files.size()) + if (folderIndex >= 0 && folderIndex < m_SkinRegistry.GetFolderCount() && + fileIndex >= 0 && fileIndex < m_SkinRegistry.GetFolder(folderIndex).files.size()) { - if (m_SkinFolders[folderIndex].active == fileIndex + 1) + if (m_SkinRegistry.GetFolder(folderIndex).active == fileIndex + 1) { - MeterWindow* meterWindow = GetRainmeter().GetMeterWindow(GetFolderPath(folderIndex)); + MeterWindow* meterWindow = GetMeterWindow(m_SkinRegistry.GetFolderPath(folderIndex)); DeactivateSkin(meterWindow, folderIndex); } else @@ -896,6 +935,15 @@ void Rainmeter::ToggleSkin(int folderIndex, int fileIndex) } } +void Rainmeter::ToggleSkinWithID(UINT id) +{ + const SkinRegistry::Indexes indexes = m_SkinRegistry.FindIndexesForID(id); + if (indexes.IsValid()) + { + ToggleSkin(indexes.folder, indexes.file); + } +} + void Rainmeter::SetSkinPath(const std::wstring& skinPath) { WritePrivateProfileString(L"Rainmeter", L"SkinPath", skinPath.c_str(), m_IniFile.c_str()); @@ -1053,45 +1101,6 @@ MeterWindow* Rainmeter::GetMeterWindowByINI(const std::wstring& ini_searching) return nullptr; } -std::pair Rainmeter::GetMeterWindowIndex(const std::wstring& folderPath, const std::wstring& file) -{ - int index = FindSkinFolderIndex(folderPath); - if (index != -1) - { - const SkinFolder& skinFolder = m_SkinFolders[index]; - - const WCHAR* fileSz = file.c_str(); - for (size_t i = 0, isize = skinFolder.files.size(); i < isize; ++i) - { - if (_wcsicmp(skinFolder.files[i].c_str(), fileSz) == 0) - { - return std::make_pair(index, (int)i); - } - } - } - - return std::make_pair(-1, -1); // Error -} - -std::pair Rainmeter::GetMeterWindowIndex(UINT menuCommand) -{ - if (menuCommand >= ID_CONFIG_FIRST && menuCommand <= ID_CONFIG_LAST) - { - // Check which skin was selected - for (size_t i = 0, isize = m_SkinFolders.size(); i < isize; ++i) - { - const SkinFolder& skinFolder = m_SkinFolders[i]; - if (menuCommand >= skinFolder.commandBase && - menuCommand < (skinFolder.commandBase + skinFolder.files.size())) - { - return std::make_pair((int)i, (int)(menuCommand - skinFolder.commandBase)); - } - } - } - - return std::make_pair(-1, -1); // error; -} - MeterWindow* Rainmeter::GetMeterWindow(HWND hwnd) { std::map::const_iterator iter = m_MeterWindows.begin(); @@ -1119,69 +1128,6 @@ void Rainmeter::GetMeterWindowsByLoadOrder(std::multimap& win } } -/* -** Returns the skin folder path relative to the skin folder (e.g. illustro\Clock). -** -*/ -std::wstring Rainmeter::GetFolderPath(int folderIndex) -{ - const SkinFolder& skinFolder = m_SkinFolders[folderIndex]; - std::wstring path = skinFolder.name; - for (int i = skinFolder.level - 1, index = folderIndex; i >= 1; --i) - { - while (m_SkinFolders[index].level != i) - { - --index; - } - - path.insert(0, L"\\"); - path.insert(0, m_SkinFolders[index].name); - } - return path; -} - -int Rainmeter::FindSkinFolderIndex(const std::wstring& folderPath) -{ - if (!folderPath.empty()) - { - const WCHAR* path = folderPath.c_str(); - int len = 0; - while (path[len] && path[len] != L'\\') ++len; - - int level = 1; - for (int i = 0, isize = (int)m_SkinFolders.size(); i < isize; ++i) - { - const SkinFolder& skinFolder = m_SkinFolders[i]; - if (skinFolder.level == level) - { - if (skinFolder.name.length() == len && _wcsnicmp(skinFolder.name.c_str(), path, len) == 0) - { - path += len; - if (*path) - { - ++path; // Skip backslash - len = 0; - while (path[len] && path[len] != L'\\') ++len; - } - else - { - // Match found - return i; - } - - ++level; - } - } - else if (skinFolder.level < level) - { - break; - } - } - } - - return -1; -} - void Rainmeter::SetLoadOrder(int folderIndex, int order) { std::multimap::iterator iter = m_SkinOrders.begin(); @@ -1206,7 +1152,7 @@ void Rainmeter::SetLoadOrder(int folderIndex, int order) int Rainmeter::GetLoadOrder(const std::wstring& folderPath) { - int index = FindSkinFolderIndex(folderPath); + const int index = m_SkinRegistry.FindFolderIndex(folderPath); if (index != -1) { std::multimap::const_iterator iter = m_SkinOrders.begin(); @@ -1228,112 +1174,8 @@ int Rainmeter::GetLoadOrder(const std::wstring& folderPath) */ void Rainmeter::ScanForSkins() { - m_SkinFolders.clear(); + m_SkinRegistry.Populate(m_SkinPath); m_SkinOrders.clear(); - - ScanForSkinsRecursive(m_SkinPath, L"", 0, 0); -} - -int Rainmeter::ScanForSkinsRecursive(const std::wstring& path, std::wstring base, int index, UINT level) -{ - WIN32_FIND_DATA fileData; // Data structure describes the file found - HANDLE hSearch; // Search handle returned by FindFirstFile - std::list subfolders; - - // Find all .ini files and subfolders - std::wstring filter = path + base; - filter += L"\\*"; - - hSearch = FindFirstFileEx( - filter.c_str(), - (Platform::IsAtLeastWin7()) ? FindExInfoBasic : FindExInfoStandard, - &fileData, - FindExSearchNameMatch, - nullptr, - 0); - - bool foundFiles = false; - if (hSearch != INVALID_HANDLE_VALUE) - { - SkinFolder skinFolder; - skinFolder.commandBase = ID_CONFIG_FIRST + index; - skinFolder.active = 0; - skinFolder.level = level; - - do - { - const std::wstring filename = fileData.cFileName; - - if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - if (!PathUtil::IsDotOrDotDot(fileData.cFileName) && - !(level == 0 && wcscmp(L"@Backup", fileData.cFileName) == 0) && - !(level == 0 && wcscmp(L"Backup", fileData.cFileName) == 0) && - !(level == 1 && wcscmp(L"@Resources", fileData.cFileName) == 0)) - { - subfolders.push_back(filename); - } - } - else if (level != 0) - { - // Check whether the extension is ".ini" - size_t filenameLen = filename.size(); - if (filenameLen >= 4 && _wcsicmp(fileData.cFileName + (filenameLen - 4), L".ini") == 0) - { - foundFiles = true; - skinFolder.files.push_back(filename); - ++index; - } - } - } - while (FindNextFile(hSearch, &fileData)); - - FindClose(hSearch); - - if (level > 0 && (foundFiles || !subfolders.empty())) - { - if (level == 1) - { - skinFolder.name = base; - } - else - { - std::wstring::size_type pos = base.rfind(L'\\') + 1; - skinFolder.name.assign(base, pos, base.length() - pos); - } - - m_SkinFolders.push_back(std::move(skinFolder)); - } - } - - if (level != 0) - { - base += L'\\'; - } - - if (!subfolders.empty()) - { - bool popFolder = !foundFiles; - - std::list::const_iterator iter = subfolders.begin(); - for ( ; iter != subfolders.end(); ++iter) - { - int newIndex = ScanForSkinsRecursive(path, base + (*iter), index, level + 1); - if (newIndex != index) - { - popFolder = false; - } - - index = newIndex; - } - - if (popFolder) - { - m_SkinFolders.pop_back(); - } - } - - return index; } /* @@ -1489,13 +1331,13 @@ void Rainmeter::ReadGeneralSettings(const std::wstring& iniFile) continue; } - int index = FindSkinFolderIndex(*iter); + const int index = m_SkinRegistry.FindFolderIndex(*iter); if (index == -1) { continue; } - SkinFolder& skinFolder = m_SkinFolders[index]; + SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); // Make sure there is a ini file available int active = parser.ReadInt(section, L"Active", 0); @@ -1540,11 +1382,10 @@ void Rainmeter::RefreshAll() if (mw) { // Verify whether the cached information is valid - int index = FindSkinFolderIndex(mw->GetFolderPath()); + const int index = m_SkinRegistry.FindFolderIndex(mw->GetFolderPath()); if (index != -1) { - SkinFolder& skinFolder = m_SkinFolders[index]; - + SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); const WCHAR* skinIniFile = mw->GetFileName().c_str(); bool found = false; @@ -1934,7 +1775,7 @@ void Rainmeter::ShowContextMenu(POINT pos, MeterWindow* meterWindow) HMENU allSkinsMenu = GetSubMenu(menu, 4); if (allSkinsMenu) { - if (!m_SkinFolders.empty()) + if (!m_SkinRegistry.IsEmpty()) { DeleteMenu(allSkinsMenu, 0, MF_BYPOSITION); // "No skins available" menuitem CreateAllSkinsMenu(allSkinsMenu); @@ -2035,13 +1876,13 @@ void Rainmeter::ShowContextMenu(POINT pos, MeterWindow* meterWindow) int Rainmeter::CreateAllSkinsMenuRecursive(HMENU skinMenu, int index) { - int initialLevel = m_SkinFolders[index].level; + const int initialLevel = m_SkinRegistry.GetFolder(index).level; int menuIndex = 0; - const size_t max = GetRainmeter().m_SkinFolders.size(); + const size_t max = m_SkinRegistry.GetFolderCount(); while (index < max) { - const SkinFolder& skinFolder = GetRainmeter().m_SkinFolders[index]; + const SkinRegistry::Folder& skinFolder = m_SkinRegistry.GetFolder(index); if (skinFolder.level != initialLevel) { return index - 1; @@ -2053,7 +1894,7 @@ int Rainmeter::CreateAllSkinsMenuRecursive(HMENU skinMenu, int index) InsertMenu(skinMenu, menuIndex, MF_POPUP | MF_BYPOSITION, (UINT_PTR)subMenu, skinFolder.name.c_str()); // Add subfolders - const bool hasSubfolder = (index + 1) < max && m_SkinFolders[index + 1].level == initialLevel + 1; + const bool hasSubfolder = (index + 1) < max && m_SkinRegistry.GetFolder(index + 1).level == initialLevel + 1; if (hasSubfolder) { index = CreateAllSkinsMenuRecursive(subMenu, index + 1); @@ -2065,7 +1906,7 @@ int Rainmeter::CreateAllSkinsMenuRecursive(HMENU skinMenu, int index) int fileCount = (int)skinFolder.files.size(); for ( ; fileIndex < fileCount; ++fileIndex) { - InsertMenu(subMenu, fileIndex, MF_STRING | MF_BYPOSITION, skinFolder.commandBase + fileIndex, skinFolder.files[fileIndex].c_str()); + InsertMenu(subMenu, fileIndex, MF_STRING | MF_BYPOSITION, skinFolder.baseID + fileIndex, skinFolder.files[fileIndex].c_str()); } if (skinFolder.active) @@ -2258,10 +2099,10 @@ HMENU Rainmeter::CreateSkinMenu(MeterWindow* meterWindow, int index, HMENU menu) // Add the variants menu if (variantsMenu) { - const SkinFolder& skinFolder = m_SkinFolders[FindSkinFolderIndex(skinName)]; + const SkinRegistry::Folder& skinFolder = *m_SkinRegistry.FindFolder(skinName); for (int i = 0, isize = (int)skinFolder.files.size(); i < isize; ++i) { - InsertMenu(variantsMenu, i, MF_BYPOSITION, skinFolder.commandBase + i, skinFolder.files[i].c_str()); + InsertMenu(variantsMenu, i, MF_BYPOSITION, skinFolder.baseID + i, skinFolder.files[i].c_str()); } if (skinFolder.active) diff --git a/Library/Rainmeter.h b/Library/Rainmeter.h index 8ac4f943..c2ad7fa2 100644 --- a/Library/Rainmeter.h +++ b/Library/Rainmeter.h @@ -27,6 +27,7 @@ #include "CommandHandler.h" #include "Logger.h" #include "MeterWindow.h" +#include "SkinRegistry.h" #define MAX_LINE_LENGTH 4096 @@ -59,37 +60,6 @@ class TrayWindow; class Rainmeter { public: - struct SkinFolder - { - std::wstring name; - std::vector files; - UINT commandBase; - int16_t active; - int16_t level; - - SkinFolder() {} - ~SkinFolder() {} - - SkinFolder(SkinFolder&& r) : - name(std::move(r.name)), - files(std::move(r.files)), - commandBase(r.commandBase), - active(r.active), - level(r.level) - { - } - - SkinFolder& operator=(SkinFolder&& r) - { - name = std::move(r.name); - files = std::move(r.files); - commandBase = r.commandBase; - active = r.active; - level = r.level; - return *this; - } - }; - static Rainmeter& GetInstance(); int Initialize(LPCWSTR iniPath, LPCWSTR layout); @@ -109,27 +79,23 @@ public: MeterWindow* GetMeterWindow(const std::wstring& folderPath); MeterWindow* GetMeterWindowByINI(const std::wstring& ini_searching); - std::pair GetMeterWindowIndex(const std::wstring& folderPath, const std::wstring& file); - std::pair GetMeterWindowIndex(MeterWindow* meterWindow) { return GetMeterWindowIndex(meterWindow->GetFolderPath(), meterWindow->GetFileName()); } - std::pair GetMeterWindowIndex(UINT menuCommand); MeterWindow* GetMeterWindow(HWND hwnd); void GetMeterWindowsByLoadOrder(std::multimap& windows, const std::wstring& group = std::wstring()); std::map& GetAllMeterWindows() { return m_MeterWindows; } - std::wstring GetFolderPath(int folderIndex); - int FindSkinFolderIndex(const std::wstring& folderPath); - - const std::vector& GetFolders() { return m_SkinFolders; } const std::vector& GetAllLayouts() { return m_Layouts; } void RemoveMeterWindow(MeterWindow* meterWindow); void AddUnmanagedMeterWindow(MeterWindow* meterWindow); void RemoveUnmanagedMeterWindow(MeterWindow* meterWindow); + bool ActivateSkin(const std::wstring& folderPath); + bool ActivateSkin(const std::wstring& folderPath, const std::wstring& file); void ActivateSkin(int folderIndex, int fileIndex); void DeactivateSkin(MeterWindow* meterWindow, int folderIndex, bool save = true); void ToggleSkin(int folderIndex, int fileIndex); + void ToggleSkinWithID(UINT id); const std::wstring& GetPath() { return m_Path; } const std::wstring& GetIniFile() { return m_IniFile; } @@ -235,7 +201,6 @@ private: void UpdateDesktopWorkArea(bool reset); HMENU CreateSkinMenu(MeterWindow* meterWindow, int index, HMENU menu); void ChangeSkinIndex(HMENU subMenu, int index); - int ScanForSkinsRecursive(const std::wstring& path, std::wstring base, int index, UINT level); void CreateAllSkinsMenu(HMENU skinMenu) { CreateAllSkinsMenuRecursive(skinMenu, 0); } int CreateAllSkinsMenuRecursive(HMENU skinMenu, int index); @@ -249,7 +214,6 @@ private: TrayWindow* m_TrayWindow; - std::vector m_SkinFolders; std::multimap m_SkinOrders; std::map m_MeterWindows; std::list m_UnmanagedMeterWindows; @@ -295,6 +259,8 @@ private: CommandHandler m_CommandHandler; + SkinRegistry m_SkinRegistry; + ConfigParser* m_CurrentParser; HWND m_Window; diff --git a/Library/SkinRegistry.cpp b/Library/SkinRegistry.cpp new file mode 100644 index 00000000..b401d50d --- /dev/null +++ b/Library/SkinRegistry.cpp @@ -0,0 +1,243 @@ +/* + Copyright (C) 2013 Rainmeter Team + + This program 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 2 + of the License, or (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "StdAfx.h" +#include "../Common/PathUtil.h" +#include "../Common/Platform.h" +#include "SkinRegistry.h" +#include "resource.h" + +/* +** Returns the skin folder path relative to the skin folder (e.g. illustro\Clock). +*/ +std::wstring SkinRegistry::GetFolderPath(int folderIndex) const +{ + // Traverse |m_Folders| backwards until level 1 is reached. + const auto& skinFolder = m_Folders[folderIndex]; + std::wstring path = skinFolder.name; + for (int i = skinFolder.level - 1, index = folderIndex; i >= 1; --i) + { + while (m_Folders[index].level != i) + { + --index; + } + + path.insert(0, L"\\"); + path.insert(0, m_Folders[index].name); + } + return path; +} + +/* +** Finds the skin index for the specified skin folder path. +*/ +int SkinRegistry::FindFolderIndex(const std::wstring& folderPath) const +{ + if (folderPath.empty()) return -1; + + const WCHAR* path = folderPath.c_str(); + int len = 0; + while (path[len] && path[len] != L'\\') ++len; + + int level = 1; + for (int i = 0, isize = (int)m_Folders.size(); i < isize; ++i) + { + const auto& skinFolder = m_Folders[i]; + if (skinFolder.level == level) + { + if (skinFolder.name.length() == len && _wcsnicmp(skinFolder.name.c_str(), path, len) == 0) + { + path += len; + if (*path) + { + ++path; // Skip backslash + len = 0; + while (path[len] && path[len] != L'\\') ++len; + } + else + { + // Match found + return i; + } + + ++level; + } + } + else if (skinFolder.level < level) + { + break; + } + } + + return -1; +} + +SkinRegistry::Folder* SkinRegistry::FindFolder(const std::wstring& folderPath) +{ + const int folderIndex = FindFolderIndex(folderPath); + return (folderIndex != -1) ? &m_Folders[folderIndex] : nullptr; +} + +SkinRegistry::Indexes SkinRegistry::FindIndexes(const std::wstring& folderPath, const std::wstring& file) +{ + const int folderIndex = FindFolderIndex(folderPath); + if (folderIndex != -1) + { + const Folder& skinFolder = m_Folders[folderIndex]; + const WCHAR* fileSz = file.c_str(); + for (size_t i = 0, isize = skinFolder.files.size(); i < isize; ++i) + { + if (_wcsicmp(skinFolder.files[i].c_str(), fileSz) == 0) + { + return Indexes(folderIndex, (int)i); + } + } + } + + return Indexes::Invalid(); // Not found. +} + +SkinRegistry::Indexes SkinRegistry::FindIndexesForID(UINT id) +{ + if (id >= ID_CONFIG_FIRST && id <= ID_CONFIG_LAST) + { + // Check which skin was selected + for (size_t i = 0, isize = m_Folders.size(); i < isize; ++i) + { + const Folder& skinFolder = m_Folders[i]; + if (id >= skinFolder.baseID && + id < (skinFolder.baseID + skinFolder.files.size())) + { + return Indexes((int)i, (int)(id - skinFolder.baseID)); + } + } + } + + return Indexes::Invalid(); // Not found. +} + +/* +** Re-scans all the subfolders of |path| for .ini files and populates |m_Folders|. +*/ +void SkinRegistry::Populate(const std::wstring& path) +{ + m_Folders.clear(); + PopulateRecursive(path, L"", 0, 0); +} + +int SkinRegistry::PopulateRecursive(const std::wstring& path, std::wstring base, int index, UINT level) +{ + WIN32_FIND_DATA fileData; // Data structure describes the file found + HANDLE hSearch; // Search handle returned by FindFirstFile + std::list subfolders; + + // Find all .ini files and subfolders + std::wstring filter = path + base; + filter += L"\\*"; + + hSearch = FindFirstFileEx( + filter.c_str(), + (Platform::IsAtLeastWin7()) ? FindExInfoBasic : FindExInfoStandard, + &fileData, + FindExSearchNameMatch, + nullptr, + 0); + + bool foundFiles = false; + if (hSearch != INVALID_HANDLE_VALUE) + { + Folder folder; + folder.baseID = ID_CONFIG_FIRST + index; + folder.active = 0; + folder.level = level; + + do + { + const std::wstring filename = fileData.cFileName; + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (!PathUtil::IsDotOrDotDot(fileData.cFileName) && + !(level == 0 && wcscmp(L"@Backup", fileData.cFileName) == 0) && + !(level == 0 && wcscmp(L"Backup", fileData.cFileName) == 0) && + !(level == 1 && wcscmp(L"@Resources", fileData.cFileName) == 0)) + { + subfolders.push_back(filename); + } + } + else if (level != 0) + { + // Check whether the extension is ".ini" + size_t filenameLen = filename.size(); + if (filenameLen >= 4 && _wcsicmp(fileData.cFileName + (filenameLen - 4), L".ini") == 0) + { + foundFiles = true; + folder.files.push_back(filename); + ++index; + } + } + } + while (FindNextFile(hSearch, &fileData)); + + FindClose(hSearch); + + if (level > 0 && (foundFiles || !subfolders.empty())) + { + if (level == 1) + { + folder.name = base; + } + else + { + std::wstring::size_type pos = base.rfind(L'\\') + 1; + folder.name.assign(base, pos, base.length() - pos); + } + + m_Folders.push_back(std::move(folder)); + } + } + + if (level != 0) + { + base += L'\\'; + } + + if (!subfolders.empty()) + { + bool popFolder = !foundFiles; + + std::list::const_iterator iter = subfolders.begin(); + for ( ; iter != subfolders.end(); ++iter) + { + int newIndex = PopulateRecursive(path, base + (*iter), index, level + 1); + if (newIndex != index) + { + popFolder = false; + } + + index = newIndex; + } + + if (popFolder) + { + m_Folders.pop_back(); + } + } + + return index; +} diff --git a/Library/SkinRegistry.h b/Library/SkinRegistry.h new file mode 100644 index 00000000..3d37fba0 --- /dev/null +++ b/Library/SkinRegistry.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2013 Rainmeter Team + + This program 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 2 + of the License, or (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef RM_LIBRARY_SKINDIRECTORY_H_ +#define RM_LIBRARY_SKINDIRECTORY_H_ + +#include +#include +#include +#include + +// Reprsents a hierarchy of skin folders (reprsented by the Folder struct) and the names of their +// respective files. +class SkinRegistry +{ +public: + struct Folder + { + std::wstring name; + std::vector files; + UINT baseID; + + int16_t active; + int16_t level; + + Folder() {} + ~Folder() {} + + Folder(Folder&& r) : + name(std::move(r.name)), + files(std::move(r.files)), + baseID(r.baseID), + active(r.active), + level(r.level) + { + } + + Folder& operator=(Folder&& r) + { + name = std::move(r.name); + files = std::move(r.files); + baseID = r.baseID; + active = r.active; + level = r.level; + return *this; + } + }; + + struct Indexes + { + int folder; + int file; + + Indexes(int folderIndex = 0, int fileIndex = 0) : folder(folderIndex), file(fileIndex) {} + + bool IsValid() const { return folder != -1; } + + static Indexes Invalid() { return Indexes(-1, 0); } + }; + + int FindFolderIndex(const std::wstring& folderPath) const; + Folder* FindFolder(const std::wstring& folderPath); + + Indexes FindIndexes(const std::wstring& folderPath, const std::wstring& file); + Indexes FindIndexesForID(UINT id); + + std::wstring GetFolderPath(int folderIndex) const; + + Folder& GetFolder(int index) { return m_Folders[index]; } + int GetFolderCount() const { return (int)m_Folders.size(); } + bool IsEmpty() const { return m_Folders.empty(); } + + void Populate(const std::wstring& path); + +private: + int PopulateRecursive(const std::wstring& path, std::wstring base, int index, UINT level); + + // Contains a sequential list of Folders. The folders are arranged as follows: + // A (index: 0, level: 1) + // B (index: 1, level: 2) + // C (index: 2, level: 3) + // D (index: 3, level: 2) + std::vector m_Folders; +}; + +#endif diff --git a/Library/SkinRegistry_Test.cpp b/Library/SkinRegistry_Test.cpp new file mode 100644 index 00000000..a145d5c8 --- /dev/null +++ b/Library/SkinRegistry_Test.cpp @@ -0,0 +1,105 @@ +/* + Copyright (C) 2013 Rainmeter Team + + This program 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 2 + of the License, or (at your option) any later version. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "SkinRegistry.h" +#include + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +TEST_CLASS(Library_SkinRegistry_Test) +{ +public: + Library_SkinRegistry_Test() + { + m_SkinRegistry.Populate(L"..\\..\\..\\Library\\Test\\SkinRegistry\\"); + } + + TEST_METHOD(TestContents) + { + std::vector files1; + files1.push_back(L"1.ini"); + + std::vector files3; + files3.push_back(L"1.ini"); + files3.push_back(L"2.ini"); + files3.push_back(L"3.ini"); + + Assert::AreEqual(5, m_SkinRegistry.GetFolderCount()); + + const auto& folderA1 = m_SkinRegistry.GetFolder(0); + Assert::AreEqual(L"A1", folderA1.name.c_str()); + Assert::AreEqual((int16_t)1, folderA1.level); + Assert::IsTrue(folderA1.files.empty()); + + const auto& folderA1_B1 = m_SkinRegistry.GetFolder(1); + Assert::AreEqual(L"B1", folderA1_B1.name.c_str()); + Assert::AreEqual((int16_t)2, folderA1_B1.level); + Assert::IsTrue(files1 == folderA1_B1.files); + + const auto& folderA1_B2 = m_SkinRegistry.GetFolder(2); + Assert::AreEqual(L"B2", folderA1_B2.name.c_str()); + Assert::AreEqual((int16_t)2, folderA1_B2.level); + Assert::IsTrue(files1 == folderA1_B2.files); + + const auto& folderA1_B2_C1 = m_SkinRegistry.GetFolder(3); + Assert::AreEqual(L"C1", folderA1_B2_C1.name.c_str()); + Assert::AreEqual((int16_t)3, folderA1_B2_C1.level); + Assert::IsTrue(files1 == folderA1_B2_C1.files); + + const auto& folderA2 = m_SkinRegistry.GetFolder(4); + Assert::AreEqual(L"A2", folderA2.name.c_str()); + Assert::AreEqual((int16_t)1, folderA2.level); + Assert::IsTrue(files3 == folderA2.files); + } + + TEST_METHOD(TestFindFolderIndex) + { + Assert::AreEqual(3, m_SkinRegistry.FindFolderIndex(L"A1\\B2\\C1")); + Assert::AreEqual(-1, m_SkinRegistry.FindFolderIndex(L"A1\\B5\\C1")); + } + + TEST_METHOD(TestFindIndexes) + { + const auto indexes1 = m_SkinRegistry.FindIndexes(L"A1\\B2", L"1.ini"); + Assert::IsTrue(indexes1.folder == 2 && indexes1.file == 0); + + const auto indexes2 = m_SkinRegistry.FindIndexes(L"A2", L"2.ini"); + Assert::IsTrue(indexes2.folder == 4 && indexes2.file == 1); + + const auto indexes3 = m_SkinRegistry.FindIndexes(L"A3", L"1.ini"); + Assert::IsFalse(indexes3.IsValid()); + } + + TEST_METHOD(TestFindIndexesForID) + { + const auto indexes1 = m_SkinRegistry.FindIndexesForID(30002); + Assert::IsTrue(indexes1.folder == 2 && indexes1.file == 0); + + const auto indexes2 = m_SkinRegistry.FindIndexesForID(30005); + Assert::IsTrue(indexes2.folder == 4 && indexes2.file == 1); + } + + TEST_METHOD(TestGetFolderPath) + { + Assert::AreEqual(L"A1\\B2\\C1", m_SkinRegistry.GetFolderPath(3).c_str()); + Assert::AreEqual(L"A2", m_SkinRegistry.GetFolderPath(4).c_str()); + } + +private: + SkinRegistry m_SkinRegistry; +}; diff --git a/Library/Test/SkinRegistry/@Backup/1.ini b/Library/Test/SkinRegistry/@Backup/1.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A1/B1/1.ini b/Library/Test/SkinRegistry/A1/B1/1.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A1/B2/1.ini b/Library/Test/SkinRegistry/A1/B2/1.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A1/B2/C1/1.ini b/Library/Test/SkinRegistry/A1/B2/C1/1.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A1/B3/.gitkeep b/Library/Test/SkinRegistry/A1/B3/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A2/1.ini b/Library/Test/SkinRegistry/A2/1.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A2/2.ini b/Library/Test/SkinRegistry/A2/2.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A2/3.ini b/Library/Test/SkinRegistry/A2/3.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A2/@Resources/1.ini b/Library/Test/SkinRegistry/A2/@Resources/1.ini new file mode 100644 index 00000000..e69de29b diff --git a/Library/Test/SkinRegistry/A2/B1/.gitkeep b/Library/Test/SkinRegistry/A2/B1/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Library/TrayWindow.cpp b/Library/TrayWindow.cpp index da7bc662..8e43d229 100644 --- a/Library/TrayWindow.cpp +++ b/Library/TrayWindow.cpp @@ -536,11 +536,7 @@ LRESULT CALLBACK TrayWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM } else if (mID >= ID_CONFIG_FIRST && mID <= ID_CONFIG_LAST) { - std::pair indexes = GetRainmeter().GetMeterWindowIndex(mID); - if (indexes.first != -1 && indexes.second != -1) - { - GetRainmeter().ToggleSkin(indexes.first, indexes.second); - } + GetRainmeter().ToggleSkinWithID(mID); } else {