]> repos.lx-s.de Git - winmute.git/commitdiff
#26 Added language list to settings
authorlx-s <lx-s@users.noreply.github.com>
Sun, 8 Oct 2023 12:12:22 +0000 (14:12 +0200)
committerlx-s <lx-s@users.noreply.github.com>
Sun, 8 Oct 2023 12:12:22 +0000 (14:12 +0200)
WinMute/Settings_GeneralDlg.cpp
WinMute/WMi18n.cpp [new file with mode: 0644]
WinMute/WMi18n.h [new file with mode: 0644]

index 4b562daa3fcf5b96c463cab33f52815cea975be4..c31413afe5ad8c553592aa409d6404348ac4ce73 100644 (file)
@@ -35,11 +35,27 @@ POSSIBILITY OF SUCH DAMAGE.
 
 namespace fs = std::filesystem;
 
-struct LanguageDll {
-   std::wstring langName;
-   std::wstring file;
+struct SettingsGeneralData {
+   WMSettings *settings;
+   std::vector<LanguageModule> langModules;
 };
 
+static void FillLanguageList(HWND hLanguageList, const SettingsGeneralData& dlgData)
+{
+   SendMessage(hLanguageList,
+               CB_INITSTORAGE,
+               static_cast<WPARAM>(dlgData.langModules.size()), 200);
+   for (const auto &lang : dlgData.langModules) {
+      int itemId = ComboBox_AddString(hLanguageList, lang.langName.c_str());
+      if (itemId == CB_ERR || itemId == CB_ERRSPACE) {
+         WMLog::GetInstance().Write(L"Failed to add language %ls to language selector", lang.langName.c_str());
+      } else {
+         ComboBox_SetItemData(hLanguageList, itemId, lang.fileName.c_str());
+      }
+   }
+   ComboBox_SelectString(hLanguageList, 0, WMi18n::GetInstance().GetcurrentLanguageName().c_str());
+}
+
 INT_PTR CALLBACK Settings_GeneralDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
 {
    switch (msg) {
@@ -52,14 +68,20 @@ INT_PTR CALLBACK Settings_GeneralDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPA
          EnableThemeDialogTexture(hDlg, ETDT_ENABLETAB);
       }
 
-      WMSettings* settings = reinterpret_cast<WMSettings*>(lParam);
-      assert(settings != nullptr);
-      SetWindowLongPtr(hDlg, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(settings));
+      SettingsGeneralData *dlgData = new SettingsGeneralData;
+      memset(dlgData, 0, sizeof(*dlgData));
+      dlgData->langModules = WMi18n::GetInstance().GetAvailableLanguages();
+      dlgData->settings = reinterpret_cast<WMSettings *>(lParam);
+      assert(dlgData->settings != nullptr);
+
+      SetWindowLongPtr(hDlg, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(dlgData));
+     
+      FillLanguageList(GetDlgItem(hDlg, IDC_LANGUAGE), *dlgData);
 
-      DWORD enabled = settings->IsAutostartEnabled();
+      DWORD enabled = dlgData->settings->IsAutostartEnabled();
       Button_SetCheck(hAutostart, enabled ? BST_CHECKED : BST_UNCHECKED);
 
-      enabled = !!settings->QueryValue(SettingsKey::LOGGING_ENABLED);
+      enabled = !!dlgData->settings->QueryValue(SettingsKey::LOGGING_ENABLED);
       Button_SetCheck(hLogging, enabled ? BST_CHECKED : BST_UNCHECKED);
       Button_Enable(hOpenLog, enabled);
       if (enabled) {
@@ -75,6 +97,14 @@ INT_PTR CALLBACK Settings_GeneralDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPA
 
       return TRUE;
    }
+   case WM_DESTROY: {
+      SettingsGeneralData *dlgData = reinterpret_cast<SettingsGeneralData *>(GetWindowLongPtr(hDlg, GWLP_USERDATA));
+      if (dlgData != nullptr) {
+         delete dlgData;
+         SetWindowLongPtr(hDlg, GWLP_USERDATA, 0);
+      }
+      return FALSE;
+   }
    case WM_COMMAND: {
       if (LOWORD(wParam) == IDC_ENABLELOGGING) {
          DWORD checked = Button_GetCheck(GetDlgItem(hDlg, IDC_ENABLELOGGING));
@@ -97,19 +127,20 @@ INT_PTR CALLBACK Settings_GeneralDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPA
       return 0;
    }
    case WM_SAVESETTINGS: {
-      WMSettings* settings = reinterpret_cast<WMSettings*>(GetWindowLongPtr(hDlg, GWLP_USERDATA));
+      SettingsGeneralData *dlgData = reinterpret_cast<SettingsGeneralData *>(GetWindowLongPtr(hDlg, GWLP_USERDATA));
+      assert(dlgData != nullptr);
 
       HWND hAutostart = GetDlgItem(hDlg, IDC_RUNONSTARTUP);
       HWND hLogging = GetDlgItem(hDlg, IDC_ENABLELOGGING);
 
       int enableLog = Button_GetCheck(hLogging) == BST_CHECKED;
-      settings->SetValue(SettingsKey::LOGGING_ENABLED, enableLog);
+      dlgData->settings->SetValue(SettingsKey::LOGGING_ENABLED, enableLog);
       WMLog::GetInstance().SetEnabled(enableLog);
 
       if (Button_GetCheck(hAutostart) == BST_CHECKED) {
-         settings->EnableAutostart(true);
+         dlgData->settings->EnableAutostart(true);
       } else {
-         settings->EnableAutostart(false);
+         dlgData->settings->EnableAutostart(false);
       }
 
       return 0;
diff --git a/WinMute/WMi18n.cpp b/WinMute/WMi18n.cpp
new file mode 100644 (file)
index 0000000..59acd2d
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ WinMute
+           Copyright (c) 2023, Alexander Steinhoefer
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the name of the author nor the names of its contributors may
+      be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+#include "common.h"
+
+extern HINSTANCE hglobInstance;
+
+WMi18n::WMi18n() noexcept
+{
+}
+
+WMi18n::~WMi18n() noexcept
+{
+   UnloadLanguage();
+}
+
+WMi18n& WMi18n::GetInstance()
+{
+   static WMi18n inst;
+   return inst;
+}
+
+std::optional<fs::path> WMi18n::GetLanguageModulesPath() const
+{
+   wchar_t wmPath[MAX_PATH + 1]{ 0 };
+   if (GetModuleFileNameW(nullptr, wmPath, MAX_PATH) <= 0) {
+      WMLog::GetInstance().WriteWindowsError(L"GetModuleFileNameW", GetLastError());
+      return std::nullopt;
+   }
+   fs::path langPath = wmPath;
+   return langPath.remove_filename() / L"lang";
+}
+
+std::vector<LanguageModule> WMi18n::GetAvailableLanguages() const
+{
+   std::vector<LanguageModule> langDlls{ { L"English", L"" } };
+   const auto langPath = GetLanguageModulesPath();
+   if (langPath.has_value()) {
+      const fs::path searchPath = *langPath / L"*.dll";
+      WIN32_FIND_DATAW wfd{ 0 };
+      HANDLE hFindFile = FindFirstFileExW(
+         searchPath.c_str(),
+         FindExInfoBasic,
+         &wfd,
+         FindExSearchNameMatch,
+         nullptr,
+         FIND_FIRST_EX_CASE_SENSITIVE);
+      if (hFindFile != INVALID_HANDLE_VALUE) {
+         do {
+            LanguageModule langMod;
+            const fs::path langFilePath = *langPath / wfd.cFileName;
+            langMod.fileName = wfd.cFileName;
+            HMODULE hLangFile = LoadLibraryExW(
+               langFilePath.c_str(),
+               nullptr,
+               LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
+            if (hLangFile == nullptr) {
+               continue;
+            }
+            wchar_t langName[255 + 1];
+            if (LoadStringW(hLangFile, IDI_LANG_NAME, langName, ARRAY_SIZE(langName)) <= 0) {
+               WMLog &log = WMLog::GetInstance();
+               log.WriteWindowsError(L"LoadStringW", GetLastError());
+               log.Write(L"Failed to load language %ls", wfd.cFileName);
+            } else {
+               langMod.langName = langName;
+               langDlls.push_back(langMod);
+            }
+            FreeLibrary(hLangFile);
+         } while (FindNextFileW(hFindFile, &wfd));
+      }
+   }
+   return langDlls;
+}
+
+std::wstring WMi18n::GetCurrentLanguageModule() const
+{
+   return curModuleName_;
+}
+
+std::wstring WMi18n::GetcurrentLanguageName() const
+{
+   return GetString(IDI_LANG_NAME);
+}
+
+// https://learn.microsoft.com/en-us/windows/win32/intl/creating-a-multilingual-user-interface-application
+
+bool WMi18n::LoadLanguage(const std::wstring &dllName)
+{
+   if (dllName.empty()) {
+      UnloadLanguage();
+      return true;
+   } else {
+      auto langModPath = GetLanguageModulesPath();
+      if (!langModPath.has_value()) {
+         return false;
+      }
+      fs::path pathDllName{ dllName };
+      *langModPath /= pathDllName.filename(); // Sanitize
+      if (!fs::exists(*langModPath)) {
+         // TODO: Error
+      } else {
+         auto newLangModule = LoadLibraryExW(
+            langModPath->c_str(), nullptr,
+            LOAD_LIBRARY_AS_IMAGE_RESOURCE | LOAD_LIBRARY_AS_DATAFILE);
+         if (newLangModule == nullptr) {
+            // TODO: Error
+         } else {
+            UnloadLanguage();
+            langModule_ = newLangModule;
+            curModuleName_ = dllName;
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+void WMi18n::UnloadLanguage() noexcept
+{
+   if (langModule_ != nullptr) {
+      FreeLibrary(langModule_);
+      langModule_ = nullptr;
+      curModuleName_.clear();
+   }
+}
+
+std::wstring WMi18n::GetString(UINT strId) const
+{
+   HMODULE hMod = (langModule_ == nullptr) ? hglobInstance : langModule_;
+   constexpr size_t strBufferSize = 2048 + 1;
+   wchar_t strBuffer[strBufferSize]{ 0 };
+   const int strLength = LoadStringW(hMod, strId, strBuffer, 0);
+   if (strLength == 0) {
+      return L"";
+   } else if (strLength < strBufferSize) {
+      LoadStringW(hMod, strId, strBuffer, ARRAY_SIZE(strBuffer));
+      return std::wstring(strBuffer, strBuffer + strLength);
+   } else if (strLength > 0) {
+      wchar_t *largeBuffer = new wchar_t[strLength + 1];
+      LoadStringW(hMod, strId, largeBuffer, strLength + 1);
+      std::wstring ret(strBuffer, largeBuffer + strLength);
+      delete[] largeBuffer;
+      return ret;
+   } else {
+      WMLog::GetInstance().WriteWindowsError(L"LoadStringW", GetLastError());
+      std::wstring err = L"String resource " + std::to_wstring(strId) + L" not found";
+      return err;
+   }
+}
diff --git a/WinMute/WMi18n.h b/WinMute/WMi18n.h
new file mode 100644 (file)
index 0000000..962249d
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ WinMute
+           Copyright (c) 2023, Alexander Steinhoefer
+
+-----------------------------------------------------------------------------
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the name of the author nor the names of its contributors may
+      be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+-----------------------------------------------------------------------------
+*/
+
+#pragma once
+
+#include "common.h"
+
+struct LanguageModule {
+   std::wstring langName;
+   std::wstring fileName;
+};
+
+class WMi18n {
+public:
+   static WMi18n& GetInstance();
+
+   bool LoadLanguage(const std::wstring &dllName);
+   std::vector<LanguageModule> GetAvailableLanguages() const;
+
+   std::optional<fs::path> GetLanguageModulesPath() const;
+   std::wstring GetCurrentLanguageModule() const;
+   std::wstring GetcurrentLanguageName() const;
+
+   std::wstring GetString(UINT id) const;
+
+private:
+   WMi18n() noexcept;
+   ~WMi18n() noexcept;
+   WMi18n(const WMi18n &) = delete;
+   WMi18n &operator=(const WMi18n &) = delete;
+
+   HMODULE langModule_ = nullptr;
+   std::wstring curModuleName_;
+
+   void UnloadLanguage() noexcept;
+};
+