Refactor skin scanning and skin folder indexing into SkinRegistry.cpp

This also adds tests.
This commit is contained in:
Birunthan Mohanathas 2013-06-16 19:14:59 +03:00
parent 6332702f75
commit 5c91ab365d
20 changed files with 565 additions and 310 deletions

View File

@ -577,26 +577,11 @@ void CommandHandler::DoActivateSkinBang(std::vector<std::wstring>& 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<int, int> 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");

View File

@ -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<int, int> 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<int, int> 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<int, MeterWindow*> windows;
GetRainmeter().GetMeterWindowsByLoadOrder(windows);

View File

@ -218,6 +218,12 @@
<ClCompile Include="Section.cpp">
<PrecompiledHeader>Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="SkinRegistry.cpp">
<PrecompiledHeader>Use</PrecompiledHeader>
</ClCompile>
<ClCompile Include="SkinRegistry_Test.cpp">
<ExcludedFromBuild>$(ExcludeTests)</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="StdAfx.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -352,6 +358,7 @@
<ClInclude Include="RainmeterQuery.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="Section.h" />
<ClInclude Include="SkinRegistry.h" />
<ClInclude Include="StdAfx.h" />
<ClInclude Include="System.h" />
<ClInclude Include="TintedImage.h" />

View File

@ -381,6 +381,12 @@
<ClCompile Include="CommandHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SkinRegistry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SkinRegistry_Test.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ConfigParser.h">
@ -653,6 +659,9 @@
<ClInclude Include="CommandHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SkinRegistry.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Library.rc">

View File

@ -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<int, int>::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<std::wstring, MeterWindow*>::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<int, int> 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<int, int> 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<std::wstring, MeterWindow*>::const_iterator iter = m_MeterWindows.begin();
@ -1119,69 +1128,6 @@ void Rainmeter::GetMeterWindowsByLoadOrder(std::multimap<int, MeterWindow*>& 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<int, int>::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<int, int>::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<std::wstring> 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<std::wstring>::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)

View File

@ -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<std::wstring> 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<int, int> GetMeterWindowIndex(const std::wstring& folderPath, const std::wstring& file);
std::pair<int, int> GetMeterWindowIndex(MeterWindow* meterWindow) { return GetMeterWindowIndex(meterWindow->GetFolderPath(), meterWindow->GetFileName()); }
std::pair<int, int> GetMeterWindowIndex(UINT menuCommand);
MeterWindow* GetMeterWindow(HWND hwnd);
void GetMeterWindowsByLoadOrder(std::multimap<int, MeterWindow*>& windows, const std::wstring& group = std::wstring());
std::map<std::wstring, MeterWindow*>& GetAllMeterWindows() { return m_MeterWindows; }
std::wstring GetFolderPath(int folderIndex);
int FindSkinFolderIndex(const std::wstring& folderPath);
const std::vector<SkinFolder>& GetFolders() { return m_SkinFolders; }
const std::vector<std::wstring>& 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<SkinFolder> m_SkinFolders;
std::multimap<int, int> m_SkinOrders;
std::map<std::wstring, MeterWindow*> m_MeterWindows;
std::list<MeterWindow*> m_UnmanagedMeterWindows;
@ -295,6 +259,8 @@ private:
CommandHandler m_CommandHandler;
SkinRegistry m_SkinRegistry;
ConfigParser* m_CurrentParser;
HWND m_Window;

243
Library/SkinRegistry.cpp Normal file
View File

@ -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<std::wstring> 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<std::wstring>::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;
}

101
Library/SkinRegistry.h Normal file
View File

@ -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 <Windows.h>
#include <string>
#include <vector>
#include <cstdint>
// 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<std::wstring> 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<Folder> m_Folders;
};
#endif

View File

@ -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 <CppUnitTest.h>
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<std::wstring> files1;
files1.push_back(L"1.ini");
std::vector<std::wstring> 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;
};

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -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<int, int> indexes = GetRainmeter().GetMeterWindowIndex(mID);
if (indexes.first != -1 && indexes.second != -1)
{
GetRainmeter().ToggleSkin(indexes.first, indexes.second);
}
GetRainmeter().ToggleSkinWithID(mID);
}
else
{