diff --git a/Src/Common/CIniOptionsMgr.cpp b/Src/Common/CIniOptionsMgr.cpp new file mode 100644 index 00000000000..948349e3416 --- /dev/null +++ b/Src/Common/CIniOptionsMgr.cpp @@ -0,0 +1,353 @@ +/** + * @file CIniOptionsMgr.cpp + * + * @brief Implementation of Ini file Options management class. + * + */ + +#include "pch.h" +#include "CIniOptionsMgr.h" +#include "OptionsMgr.h" +#include +#include +#include +#include +#include + +using std::filesystem::current_path; + +LPCWSTR CIniOptionsMgr::lpFilePath = NULL; + +LPCWSTR lpAppName = TEXT("WinMerge"); +LPCWSTR lpFileName = TEXT("\\winmerge.ini"); + +CIniOptionsMgr::CIniOptionsMgr() +{ + InitializeCriticalSection(&m_cs); +} + +CIniOptionsMgr::~CIniOptionsMgr() +{ + DeleteCriticalSection(&m_cs); + delete[] CIniOptionsMgr::lpFilePath; +} + +/** + * @brief Checks wheter INI file exists. + * @return TRUE if INI file exist, + * FALSE otherwise. + */ +bool CIniOptionsMgr::CheckIfIniFileExist() +{ + std::ifstream f(GetFilePath()); + return f.good(); +} + +/** + * @brief Get path to INI file. + * @return path to INI file + */ +LPCWSTR CIniOptionsMgr::GetFilePath() +{ + if (CIniOptionsMgr::lpFilePath == NULL) + { + // create path + std::filesystem::path p = current_path(); + p += lpFileName; + + // change type + std::wstring str = p.wstring(); + size_t length = str.length() + 1; + wchar_t* strCp = new wchar_t[length]; + wcscpy_s(strCp, length, str.c_str()); + + // set path + CIniOptionsMgr::lpFilePath = strCp; + } + + return CIniOptionsMgr::lpFilePath; +} + +int CIniOptionsMgr::InitOption(const String& name, const varprop::VariantValue& defaultValue) +{ + // Check type & bail if null + int valType = defaultValue.GetType(); + if (valType == varprop::VT_NULL) + return COption::OPT_ERR; + + // If we're not loading & saving options, bail + if (!m_serializing) + return AddOption(name, defaultValue); + + EnterCriticalSection(&m_cs); + + // check if value exist + String textValue = ReadValueFromFile(name); + bool found = textValue.size() != 0; + + // Actually save value into our in-memory options table + int retVal = AddOption(name, defaultValue); + + // Update registry if successfully saved to in-memory table + if (retVal == COption::OPT_OK) + { + if (found) + { + varprop::VariantValue value(defaultValue); + retVal = ParseValue(name, textValue, value); + if (retVal == COption::OPT_OK) + { + retVal = Set(name, value); + } + } + } + + LeaveCriticalSection(&m_cs); + return retVal; +} + +int CIniOptionsMgr::InitOption(const String& name, const String& defaultValue) +{ + varprop::VariantValue defValue; + defValue.SetString(defaultValue); + return InitOption(name, defValue); +} + +int CIniOptionsMgr::InitOption(const String& name, const TCHAR* defaultValue) +{ + return InitOption(name, String(defaultValue)); +} + +int CIniOptionsMgr::InitOption(const String& name, int defaultValue, bool serializable) +{ + varprop::VariantValue defValue; + int retVal = COption::OPT_OK; + + defValue.SetInt(defaultValue); + if (serializable) + retVal = InitOption(name, defValue); + else + AddOption(name, defValue); + return retVal; +} + +int CIniOptionsMgr::InitOption(const String& name, bool defaultValue) +{ + varprop::VariantValue defValue; + defValue.SetBool(defaultValue); + return InitOption(name, defValue); +} + +int CIniOptionsMgr::SaveOption(const String& name) +{ + if (!m_serializing) return COption::OPT_OK; + + varprop::VariantValue value; + int retVal = COption::OPT_OK; + + value = Get(name); + int valType = value.GetType(); + if (valType == varprop::VT_NULL) + retVal = COption::OPT_NOTFOUND; + + if (retVal == COption::OPT_OK) + { + if (valType == varprop::VT_STRING) + { + String strVal = value.GetString(); + LPCWSTR text = strVal.c_str(); + WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath()); + } + else if (valType == varprop::VT_INT) + { + DWORD dwordVal = value.GetInt(); + String strVal = strutils::to_str(dwordVal); + LPCWSTR text = strVal.c_str(); + WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath()); + } + else if (valType == varprop::VT_BOOL) + { + DWORD dwordVal = value.GetBool() ? 1 : 0; + String strVal = strutils::to_str(dwordVal); + LPCWSTR text = strVal.c_str(); + WritePrivateProfileString(lpAppName, name.c_str(), text, GetFilePath()); + } + else + { + retVal = COption::OPT_UNKNOWN_TYPE; + } + } + return retVal; +} + +/** + * @brief Set new value for option and save option to file + */ +int CIniOptionsMgr::SaveOption(const String& name, const varprop::VariantValue& value) +{ + int retVal = Set(name, value); + if (retVal == COption::OPT_OK) + retVal = SaveOption(name); + return retVal; +} + +/** + * @brief Set new string value for option and save option to file + */ +int CIniOptionsMgr::SaveOption(const String& name, const String& value) +{ + varprop::VariantValue val; + val.SetString(value); + int retVal = Set(name, val); + if (retVal == COption::OPT_OK) + retVal = SaveOption(name); + return retVal; +} + +/** + * @brief Set new string value for option and save option to file + */ +int CIniOptionsMgr::SaveOption(const String& name, const TCHAR* value) +{ + return SaveOption(name, String(value)); +} + +int CIniOptionsMgr::SaveOption(const String& name, int value) +{ + varprop::VariantValue val; + val.SetInt(value); + int retVal = Set(name, val); + if (retVal == COption::OPT_OK) + retVal = SaveOption(name); + return retVal; +} + +int CIniOptionsMgr::SaveOption(const String& name, bool value) +{ + varprop::VariantValue val; + val.SetBool(value); + int retVal = Set(name, val); + if (retVal == COption::OPT_OK) + retVal = SaveOption(name); + return retVal; +} + +int CIniOptionsMgr::RemoveOption(const String& name) +{ + int retVal = COption::OPT_OK; + + String strPath; + String strValueName; + + SplitName(name, strPath, strValueName); + + if (!strValueName.empty()) + { + retVal = COptionsMgr::RemoveOption(name); + } + else + { + for (auto it = m_optionsMap.begin(); it != m_optionsMap.end(); ) + { + if (it->first.find(strPath) == 0) + it = m_optionsMap.erase(it); + else + ++it; + } + retVal = COption::OPT_OK; + } + + EnterCriticalSection(&m_cs); + + WritePrivateProfileString(lpAppName, name.c_str(), NULL, GetFilePath()); + + LeaveCriticalSection(&m_cs); + + return retVal; +} + +int CIniOptionsMgr::ExportOptions(const String& filename, const bool bHexColor) const +{ + if (std::filesystem::copy_file(CIniOptionsMgr::GetFilePath(), filename)) + { + return COption::OPT_OK; + } + else + { + return COption::OPT_ERR; + } +} + +int CIniOptionsMgr::ImportOptions(const String& filename) +{ + if (std::filesystem::copy_file(filename, CIniOptionsMgr::GetFilePath())) + { + return COption::OPT_OK; + } + else + { + return COption::OPT_ERR; + } +} + +String CIniOptionsMgr::ReadValueFromFile(const String& name) +{ + const int size = 100; + LPWSTR buffor = new TCHAR[size]; + DWORD result = GetPrivateProfileString(lpAppName, name.c_str(), NULL, buffor, size, GetFilePath()); + return buffor; +} + +int CIniOptionsMgr::ParseValue(const String& strName, String& textValue, varprop::VariantValue& value) +{ + int valType = value.GetType(); + int retVal = COption::OPT_OK; + + if (valType == varprop::VT_STRING) + { + value.SetString(textValue); + retVal = Set(strName, value); + } + else if (valType == varprop::VT_INT) + { + value.SetInt(std::stoi(textValue)); + retVal = Set(strName, value); + } + else if (valType == varprop::VT_BOOL) + { + value.SetBool(textValue[0] == '1' ? true : false); + retVal = Set(strName, value); + } + else + retVal = COption::OPT_WRONG_TYPE; + + return retVal; +} + +/** + * @brief Split option name to path (in registry) and + * valuename (in registry). + * + * Option names are given as "full path", e.g. "Settings/AutomaticRescan". + * This function splits that to path "Settings/" and valuename + * "AutomaticRescan". + * @param [in] strName Option name + * @param [out] srPath Path (key) in registry + * @param [out] strValue Value in registry + */ +void CIniOptionsMgr::SplitName(const String& strName, String& strPath, + String& strValue) const +{ + size_t pos = strName.rfind('/'); + if (pos != String::npos) + { + size_t len = strName.length(); + strValue = strName.substr(pos + 1, len - pos - 1); //Right(len - pos - 1); + strPath = strName.substr(0, pos); //Left(pos); + } + else + { + strValue = strName; + strPath.erase(); + } +} \ No newline at end of file diff --git a/Src/Common/CIniOptionsMgr.h b/Src/Common/CIniOptionsMgr.h new file mode 100644 index 00000000000..a4ba5dcb251 --- /dev/null +++ b/Src/Common/CIniOptionsMgr.h @@ -0,0 +1,56 @@ +/** + * @file CIniOptionsMgr.h + * + * @brief Implementation of Ini file Options management class. + * + */ +#pragma once + +#include +#include "OptionsMgr.h" + +class COptionsMgr; + +/** + * @brief Ini-based implementation of OptionsMgr interface (q.v.). + */ +class CIniOptionsMgr : public COptionsMgr +{ +public: + CIniOptionsMgr(); + virtual ~CIniOptionsMgr(); + + static bool CheckIfIniFileExist(); + + static LPCWSTR GetFilePath(); + + virtual int InitOption(const String& name, const varprop::VariantValue& defaultValue) override; + virtual int InitOption(const String& name, const String& defaultValue) override; + virtual int InitOption(const String& name, const TCHAR* defaultValue) override; + virtual int InitOption(const String& name, int defaultValue, bool serializable = true) override; + virtual int InitOption(const String& name, bool defaultValue) override; + + virtual int SaveOption(const String& name) override; + virtual int SaveOption(const String& name, const varprop::VariantValue& value) override; + virtual int SaveOption(const String& name, const String& value) override; + virtual int SaveOption(const String& name, const TCHAR* value) override; + virtual int SaveOption(const String& name, int value) override; + virtual int SaveOption(const String& name, bool value) override; + + virtual int RemoveOption(const String& name) override; + + virtual void SetSerializing(bool serializing = true) override { m_serializing = serializing; } + + virtual int ExportOptions(const String& filename, const bool bHexColor = false) const override; + virtual int ImportOptions(const String& filename) override; + +private: + CRITICAL_SECTION m_cs; + bool m_serializing; + static LPCWSTR lpFilePath; + + String ReadValueFromFile(const String& name); + int ParseValue(const String& strName, String& textValue, varprop::VariantValue& value); + + void SplitName(const String& strName, String& strPath, String& strValue) const; +}; \ No newline at end of file diff --git a/Src/Merge.cpp b/Src/Merge.cpp index 0419419f3be..6d3397c8834 100644 --- a/Src/Merge.cpp +++ b/Src/Merge.cpp @@ -4,7 +4,7 @@ // Author: Dean Grimm // SPDX-License-Identifier: GPL-2.0-or-later ///////////////////////////////////////////////////////////////////////////// -/** +/** * @file Merge.cpp * * @brief Defines the class behaviors for the application. @@ -20,6 +20,7 @@ #include "OptionsMgr.h" #include "OptionsInit.h" #include "RegOptionsMgr.h" +#include "CIniOptionsMgr.h" #include "OpenDoc.h" #include "OpenFrm.h" #include "OpenView.h" @@ -99,7 +100,7 @@ CMergeApp::CMergeApp() : , m_mainThreadScripts(nullptr) , m_nLastCompareResult(0) , m_bNonInteractive(false) -, m_pOptions(new CRegOptionsMgr()) +, m_pOptions(CreateOptionManager()) , m_pGlobalFileFilter(new FileFilterHelper()) , m_nActiveOperations(0) , m_pLangDlg(new CLanguageSelect()) @@ -115,6 +116,23 @@ CMergeApp::CMergeApp() : // Place all significant initialization in InitInstance } +/** + * @brief Chose which options manager should be initialized. + * @return IniOptionsMgr if initial config file exists, + * CRegOptionsMgr otherwise. + */ +COptionsMgr *CreateOptionManager() +{ + if (CIniOptionsMgr::CheckIfIniFileExist()) + { + return new CIniOptionsMgr(); + } + else + { + return new CRegOptionsMgr(); + } +} + CMergeApp::~CMergeApp() { strdiff::Close(); @@ -228,7 +246,7 @@ BOOL CMergeApp::InitInstance() // Create exclusion mutex name TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop"); DWORD dwLengthNeeded; - GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, + GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, szDesktopName, sizeof(szDesktopName), &dwLengthNeeded); TCHAR szMutexName[MAX_PATH + 40]; // Combine window class name and desktop name to form a unique mutex name. @@ -465,7 +483,7 @@ void CMergeApp::OnAppAbout() * good place to do cleanups. * @return Application's exit value (returned from WinMain()). */ -int CMergeApp::ExitInstance() +int CMergeApp::ExitInstance() { charsets_cleanup(); @@ -521,7 +539,7 @@ int CMergeApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt) // Create the message box dialog. CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN, nIDPrompt); - + if (m_pMainWnd->IsIconic()) m_pMainWnd->ShowWindow(SW_RESTORE); @@ -545,7 +563,7 @@ bool CMergeApp::IsReallyIdle() const return idle; } -BOOL CMergeApp::OnIdle(LONG lCount) +BOOL CMergeApp::OnIdle(LONG lCount) { if (CWinApp::OnIdle(lCount)) return TRUE; @@ -560,7 +578,10 @@ BOOL CMergeApp::OnIdle(LONG lCount) if (m_bNonInteractive && IsReallyIdle()) m_pMainWnd->PostMessage(WM_CLOSE, 0, 0); - static_cast(GetOptionsMgr())->CloseKeys(); + if (typeid(*GetOptionsMgr()) == typeid(CRegOptionsMgr)) + { + static_cast(GetOptionsMgr())->CloseKeys(); + } return FALSE; } @@ -711,7 +732,7 @@ bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainF { DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE}; bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files, - dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr, + dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr, cmdInfo.m_sPreDiffer, infoUnpacker.get()); } } @@ -881,7 +902,7 @@ bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath) String path; String filename; String ext; - + paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext); // Determine backup folder @@ -944,7 +965,7 @@ bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath) { success = !!CopyFileW(TFile(pszPath).wpath().c_str(), TFile(bakPath).wpath().c_str(), FALSE); } - + if (!success) { String msg = strutils::format_string1( @@ -1002,7 +1023,7 @@ int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile, if (bFileExists && bFileRO) { UINT userChoice = 0; - + // Don't ask again if its already asked if (bApplyToAll) userChoice = IDYES; @@ -1039,7 +1060,7 @@ int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile, CFile::SetStatus(strSavePath.c_str(), status); nRetVal = IDYES; break; - + // Save to new filename (single) /skip this item (multiple) case IDNO: if (!bMultiFile) @@ -1119,7 +1140,7 @@ bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &proje return true; } -/** +/** * @brief Read project and perform comparison specified * @param [in] sProject Full path to project file. * @return `true` if loading project file and starting compare succeeded. @@ -1129,7 +1150,7 @@ bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sRe ProjectFile project; if (!LoadProjectFile(sProject, project)) return false; - + bool rtn = true; for (auto& projItem : project.Items()) { @@ -1254,7 +1275,7 @@ std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const */ void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName) { - // sPathName will be added to the top of the MRU list. + // sPathName will be added to the top of the MRU list. // If sPathName already exists in the MRU list, it will be moved to the top if (m_pRecentFileList != nullptr) { m_pRecentFileList->Add(sPathName); diff --git a/Src/Merge.h b/Src/Merge.h index a598b2db71d..dd987cc695e 100644 --- a/Src/Merge.h +++ b/Src/Merge.h @@ -3,7 +3,7 @@ // Copyright (C) 1997 Dean P. Grimm // SPDX-License-Identifier: GPL-2.0-or-later ///////////////////////////////////////////////////////////////////////////// -/** +/** * @file Merge.h * * @brief main header file for the MERGE application @@ -45,7 +45,7 @@ class CCrystalTextMarkers; enum { IDLE_TIMER = 9754 }; -/** +/** * @brief WinMerge application class */ class CMergeApp : public CWinApp @@ -73,7 +73,7 @@ class CMergeApp : public CWinApp void TranslateMenu(HMENU) const; void TranslateDialog(HWND) const; String LoadString(UINT) const; - bool TranslateString(const std::string&, String&) const; + bool TranslateString(const std::string&, String&) const; std::wstring LoadDialogCaption(LPCTSTR) const; CMergeApp(); @@ -174,12 +174,13 @@ class CMergeApp : public CWinApp extern CMergeApp theApp; -/** +/** * @brief Set flag so that application will broadcast notification at next * idle time (via WM_TIMER id=IDLE_TIMER) */ inline void CMergeApp::SetNeedIdleTimer() { - m_bNeedIdleTimer = true; + m_bNeedIdleTimer = true; } +COptionsMgr* CreateOptionManager(); diff --git a/Src/Merge.vcxproj b/Src/Merge.vcxproj index e91dbcdf8ba..d7c62cf636c 100644 --- a/Src/Merge.vcxproj +++ b/Src/Merge.vcxproj @@ -644,6 +644,10 @@ + + pch.h + $(IntDir)$(TargetName)2.pch + pch.h $(IntDir)$(TargetName)2.pch @@ -1146,6 +1150,7 @@ + diff --git a/Src/Merge.vcxproj.filters b/Src/Merge.vcxproj.filters index f337f44e2a9..4e4b40153e8 100644 --- a/Src/Merge.vcxproj.filters +++ b/Src/Merge.vcxproj.filters @@ -616,6 +616,9 @@ Common\Source Files + + Source Files + @@ -1212,6 +1215,9 @@ Common\Header Files + + Header Files + diff --git a/Src/OptionsInit.cpp b/Src/OptionsInit.cpp index e66992c6363..d908f923a64 100644 --- a/Src/OptionsInit.cpp +++ b/Src/OptionsInit.cpp @@ -6,6 +6,7 @@ #include "pch.h" #include +#include #include "OptionsDef.h" #include "OptionsMgr.h" #include "RegOptionsMgr.h" @@ -38,7 +39,10 @@ namespace Options */ void Init(COptionsMgr *pOptions) { - static_cast(pOptions)->SetRegRootKey(_T("Thingamahoochie\\WinMerge\\")); + if (typeid(*pOptions) == typeid(CRegOptionsMgr)) + { + static_cast(pOptions)->SetRegRootKey(_T("Thingamahoochie\\WinMerge\\")); + } LANGID LangId = GetUserDefaultLangID(); pOptions->InitOption(OPT_SELECTED_LANGUAGE, static_cast(LangId));