// WinMerge: an interactive diff/merge utility
// Copyright (C) 1997-2000 Thingamahoochie Software
// Author: Dean Grimm
-//
-// 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., 675 Mass Ave, Cambridge, MA 02139, USA.
-//
+// SPDX-License-Identifier: GPL-2.0-or-later
/////////////////////////////////////////////////////////////////////////////
-/**
+/**
* @file Merge.cpp
*
* @brief Defines the class behaviors for the application.
#include "OptionsMgr.h"
#include "OptionsInit.h"
#include "RegOptionsMgr.h"
+#include "IniOptionsMgr.h"
#include "OpenDoc.h"
#include "OpenFrm.h"
#include "OpenView.h"
#include "HexMergeView.h"
#include "AboutDlg.h"
#include "MainFrm.h"
-#include "ChildFrm.h"
+#include "MergeEditFrm.h"
#include "DirFrame.h"
#include "MergeDoc.h"
#include "DirDoc.h"
#include "DirView.h"
#include "PropBackups.h"
#include "FileOrFolderSelect.h"
-#include "paths.h"
#include "FileFilterHelper.h"
#include "LineFiltersList.h"
-#include "FilterCommentsManager.h"
+#include "SubstitutionFiltersList.h"
#include "SyntaxColors.h"
#include "CCrystalTextMarkers.h"
#include "OptionsSyntaxColors.h"
#include "Plugins.h"
#include "ProjectFile.h"
-#include "MergeEditView.h"
+#include "MergeEditSplitterView.h"
#include "LanguageSelect.h"
#include "OptionsDef.h"
#include "MergeCmdLineInfo.h"
#include "JumpList.h"
#include "stringdiffs.h"
#include "TFile.h"
-#include "SourceControl.h"
#include "paths.h"
+#include "Shell.h"
+#include "CompareStats.h"
#include "TestMain.h"
-
-// For shutdown cleanup
-#include "charsets.h"
+#include "charsets.h" // For shutdown cleanup
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
-
-
/** @brief Location for command line help to open. */
-static TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
-
-// registry dir to WinMerge
-static String f_RegDir = _T("Software\\Thingamahoochie\\WinMerge");
+static const TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
/** @brief Backup file extension. */
static const TCHAR BACKUP_FILE_EXT[] = _T("bak");
-#ifndef WIN64
-/**
- * @brief Turn STL exceptions into MFC exceptions.
- * Based on the article "Visual C++ Exception-Handling Instrumentation"
- * by Eugene Gershnik, published at http://www.drdobbs.com/184416600.
- * Rethrow fix inspired by http://www.spinics.net/lists/wine/msg05996.html.
- */
-/*
-namespace Turn_STL_exceptions_into_MFC_exceptions
-{
-# ifndef _STATIC_CPPLIB
-# error This hack only works with _STATIC_CPPLIB defined.
-# endif
-
- class CDisguisedSTLException : public CException
- {
- private:
- std::exception *m_pSTLException;
- public:
- CDisguisedSTLException(std::exception *pSTLException)
- : m_pSTLException(pSTLException)
- {
- }
- virtual BOOL GetErrorMessage(LPTSTR lpszError, UINT nMaxError, PUINT)
- {
- StringCchPrintf(lpszError, nMaxError, _T("%hs"), m_pSTLException->what());
- return TRUE;
- }
- };
-
- const DWORD CPP_EXCEPTION = 0xE06D7363;
- const DWORD MS_MAGIC = 0x19930520;
-
- extern "C" void __stdcall _CxxThrowException(void *pObject, _s__ThrowInfo const *pObjectInfo)
- {
- __declspec(thread) static ULONG_PTR args[3] = { MS_MAGIC, 0, 0 };
- if (pObject == NULL)
- {
- pObject = reinterpret_cast<void *>(args[1]);
- pObjectInfo = reinterpret_cast<_s__ThrowInfo const *>(args[2]);
- }
- else
- {
- args[1] = (ULONG_PTR)pObject;
- args[2] = (ULONG_PTR)pObjectInfo;
- }
- int i;
- if (pObjectInfo->pCatchableTypeArray && (i = pObjectInfo->pCatchableTypeArray->nCatchableTypes))
- {
- const char *name = typeid(std::exception).raw_name();
- if (pObjectInfo->pCatchableTypeArray->arrayOfCatchableTypes[i - 1]->pType->name == name)
- {
- throw new CDisguisedSTLException(static_cast<std::exception *>(pObject));
- }
- }
- RaiseException(CPP_EXCEPTION, EXCEPTION_NONCONTINUABLE, sizeof(args)/sizeof(args[0]), args);
- }
-}
-*/
-#endif
-
/////////////////////////////////////////////////////////////////////////////
// CMergeApp
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_HELP, OnHelp)
ON_COMMAND_EX_RANGE(ID_FILE_PROJECT_MRU_FIRST, ID_FILE_PROJECT_MRU_LAST, OnOpenRecentFile)
- ON_UPDATE_COMMAND_UI(ID_FILE_PROJECT_MRU_FIRST, &CWinApp::OnUpdateRecentFileMenu)
+ ON_UPDATE_COMMAND_UI(ID_FILE_PROJECT_MRU_FIRST, CWinApp::OnUpdateRecentFileMenu)
ON_COMMAND(ID_FILE_MERGINGMODE, OnMergingMode)
ON_UPDATE_COMMAND_UI(ID_FILE_MERGINGMODE, OnUpdateMergingMode)
ON_UPDATE_COMMAND_UI(ID_STATUS_MERGINGMODE, OnUpdateMergingStatus)
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
-/**
-* @brief Mapping from command line argument name (eg, ignorews) to WinMerge
-* option name (eg, Settings/IgnoreSpace).
-*
-* These arguments take an optional colon and number, like so:
-*
-* "/ignoreblanklines" (makes WinMerge ignore blank lines)
-* "/ignoreblanklines:1" (makes WinMerge ignore blank lines)
-* "/ignoreblanklines:0" (makes WinMerge not ignore blank lines)
-*/
-struct ArgSetting
-{
- LPCTSTR CmdArgName;
- LPCTSTR WinMergeOptionName;
-};
-
-
/////////////////////////////////////////////////////////////////////////////
// CMergeApp construction
CMergeApp::CMergeApp() :
- m_bNeedIdleTimer(FALSE)
-, m_pOpenTemplate(0)
-, m_pDiffTemplate(0)
-, m_pHexMergeTemplate(0)
-, m_pDirTemplate(0)
-, m_mainThreadScripts(NULL)
+ m_bNeedIdleTimer(false)
+, m_pOpenTemplate(nullptr)
+, m_pDiffTemplate(nullptr)
+, m_pHexMergeTemplate(nullptr)
+, m_pDirTemplate(nullptr)
+, 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())
-, m_bEscShutdown(FALSE)
-, m_bClearCaseTool(FALSE)
-, m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
+, m_bEscShutdown(false)
+, m_bExitIfNoDiff(MergeCmdLineInfo::ExitNoDiff::Disabled)
, m_pLineFilters(new LineFiltersList())
-, m_pFilterCommentsManager(new FilterCommentsManager())
+, m_pSubstitutionFiltersList(new SubstitutionFiltersList())
, m_pSyntaxColors(new SyntaxColors())
, m_pMarkers(new CCrystalTextMarkers())
-, m_pSourceControl(new SourceControl())
-, m_bMergingMode(FALSE)
+, m_bMergingMode(false)
{
// add construction code here,
// 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();
// Prevents DLL hijacking
HMODULE hLibrary = GetModuleHandle(_T("kernel32.dll"));
BOOL (WINAPI *pfnSetSearchPathMode)(DWORD) = (BOOL (WINAPI *)(DWORD))GetProcAddress(hLibrary, "SetSearchPathMode");
- if (pfnSetSearchPathMode)
+ if (pfnSetSearchPathMode != nullptr)
pfnSetSearchPathMode(0x00000001L /*BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE*/ | 0x00008000L /*BASE_SEARCH_PATH_PERMANENT*/);
BOOL (WINAPI *pfnSetDllDirectoryA)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))GetProcAddress(hLibrary, "SetDllDirectoryA");
- if (pfnSetDllDirectoryA)
+ if (pfnSetDllDirectoryA != nullptr)
pfnSetDllDirectoryA("");
JumpList::SetCurrentProcessExplicitAppUserModelID(L"Thingamahoochie.WinMerge");
InitCommonControls(); // initialize common control library
CWinApp::InitInstance(); // call parent class method
+ m_imageForInitializingGdiplus.Load((IStream*)nullptr); // initialize GDI+
+
// Runtime switch so programmer may set this in interactive debugger
int dbgmem = 0;
if (dbgmem)
// Load registry keys from WinMerge.reg if existing WinMerge.reg
env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
+ // Parse command-line arguments.
+#ifdef TEST_WINMERGE
+ MergeCmdLineInfo cmdInfo(_T(""));
+#else
+ MergeCmdLineInfo cmdInfo(GetCommandLine());
+#endif
+ if (cmdInfo.m_bNoPrefs)
+ m_pOptions->SetSerializing(false); // Turn off serializing to registry.
+
+ Options::CopyHKLMValues();
Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
+ ApplyCommandLineConfigOptions(cmdInfo);
+ if (cmdInfo.m_sErrorMessages.size() > 0)
+ {
+ if (AttachConsole(ATTACH_PARENT_PROCESS))
+ {
+ DWORD dwWritten;
+ for (auto& msg : cmdInfo.m_sErrorMessages)
+ {
+ String line = _T("WinMerge: ") + msg + _T("\n");
+ WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), line.c_str(), static_cast<DWORD>(line.length()), &dwWritten, nullptr);
+ }
+ FreeConsole();
+ }
+ }
// Initialize temp folder
SetupTempPath();
- // Parse command-line arguments.
- MergeCmdLineInfo cmdInfo(GetCommandLine());
-
// If paths were given to commandline we consider this being an invoke from
// commandline (from other application, shellextension etc).
- BOOL bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
+ bool bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
// WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
// This is the name of the company of the original author (Dean Grimm)
SetRegistryKey(_T("Thingamahoochie"));
- BOOL bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
- (true == cmdInfo.m_bSingleInstance);
+ int nSingleInstance = cmdInfo.m_nSingleInstance.has_value() ?
+ *cmdInfo.m_nSingleInstance : GetOptionsMgr()->GetInt(OPT_SINGLE_INSTANCE);
// 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.
// As the window class name is decorated to distinguish between ANSI and
// UNICODE build, so will be the mutex name.
wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
- HANDLE hMutex = CreateMutex(NULL, FALSE, szMutexName);
- if (hMutex)
+ HANDLE hMutex = CreateMutex(nullptr, FALSE, szMutexName);
+ if (hMutex != nullptr)
WaitForSingleObject(hMutex, INFINITE);
- if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
+ if (nSingleInstance != 0 && GetLastError() == ERROR_ALREADY_EXISTS)
{
// Activate previous instance and send commandline to it
- HWND hWnd = FindWindow(CMainFrame::szClassName, NULL);
- if (hWnd)
+ HWND hWnd = FindWindow(CMainFrame::szClassName, nullptr);
+ if (hWnd != nullptr)
{
if (IsIconic(hWnd))
ShowWindow(hWnd, SW_RESTORE);
SetForegroundWindow(GetLastActivePopup(hWnd));
LPTSTR cmdLine = GetCommandLine();
COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
- if (SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
+ if (::SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
{
ReleaseMutex(hMutex);
CloseHandle(hMutex);
+ if (nSingleInstance > 1)
+ {
+ DWORD dwProcessId = 0;
+ GetWindowThreadProcessId(hWnd, &dwProcessId);
+ HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, dwProcessId);
+ if (hProcess)
+ WaitForSingleObject(hProcess, INFINITE);
+ }
return FALSE;
}
}
// Read last used filter from registry
// If filter fails to set, reset to default
const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
- BOOL bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
+ bool bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
if (!bFilterSet)
{
String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
charsets_init();
UpdateCodepageModule();
- if (m_pSourceControl)
- m_pSourceControl->InitializeSourceControlMembers();
-
- FileTransform::g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
- FileTransform::g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
+ FileTransform::g_UnpackerMode = static_cast<PLUGIN_MODE>(GetOptionsMgr()->GetInt(OPT_PLUGINS_UNPACKER_MODE));
+ FileTransform::g_PredifferMode = static_cast<PLUGIN_MODE>(GetOptionsMgr()->GetInt(OPT_PLUGINS_PREDIFFER_MODE));
NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
m_fontGUI.CreateFontIndirect(&ncm.lfMenuFont);
}
- if (m_pSyntaxColors)
- Options::SyntaxColors::Load(GetOptionsMgr(), m_pSyntaxColors.get());
+ if (m_pSyntaxColors != nullptr)
+ Options::SyntaxColors::Init(GetOptionsMgr(), m_pSyntaxColors.get());
- if (m_pMarkers)
+ if (m_pMarkers != nullptr)
m_pMarkers->LoadFromRegistry();
- if (m_pLineFilters)
+ CCrystalTextView::SetRenderingModeDefault(static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE)));
+
+ if (m_pLineFilters != nullptr)
m_pLineFilters->Initialize(GetOptionsMgr());
// If there are no filters loaded, and there is filter string in previous
m_pLineFilters->Import(oldFilter);
}
+ if (m_pSubstitutionFiltersList != nullptr)
+ m_pSubstitutionFiltersList->Initialize(GetOptionsMgr());
+
// Check if filter folder is set, and create it if not
String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
if (pathMyFolders.empty())
m_pDiffTemplate = new CMultiDocTemplate(
IDR_MERGEDOCTYPE,
RUNTIME_CLASS(CMergeDoc),
- RUNTIME_CLASS(CChildFrame), // custom MDI child frame
- RUNTIME_CLASS(CMergeEditView));
+ RUNTIME_CLASS(CMergeEditFrame), // custom MDI child frame
+ RUNTIME_CLASS(CMergeEditSplitterView));
AddDocTemplate(m_pDiffTemplate);
// Merge Edit view
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
{
- if (hMutex)
+ if (hMutex != nullptr)
{
ReleaseMutex(hMutex);
CloseHandle(hMutex);
// Set the menu
// Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
- pMainFrame->MDISetMenu(pNewMenu, NULL);
+ pMainFrame->MDISetMenu(pNewMenu, nullptr);
- // The main window has been initialized, so activate and update it.
+ // The main window has been initialized, so activate it.
pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
- pMainFrame->UpdateWindow();
// Since this function actually opens paths for compare it must be
// called after initializing CMainFrame!
- BOOL bContinue = TRUE;
- if (ParseArgsAndDoOpen(cmdInfo, pMainFrame) == FALSE && bCommandLineInvoke)
- bContinue = FALSE;
+ bool bContinue = true;
+ if (!ParseArgsAndDoOpen(cmdInfo, pMainFrame) && bCommandLineInvoke)
+ bContinue = false;
- if (hMutex)
+ if (hMutex != nullptr)
ReleaseMutex(hMutex);
- if (m_bNonInteractive)
- {
- bContinue = FALSE;
- }
-
// If user wants to cancel the compare, close WinMerge
- if (bContinue == FALSE)
+ if (!bContinue)
{
pMainFrame->PostMessage(WM_CLOSE, 0, 0);
}
theApp.OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
}
+static void OpenUrl(int&)
+{
+ shell::Open(WinMergeURL);
+}
+
// App command to run the dialog
void CMergeApp::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
+ aboutDlg.m_onclick_url += Poco::delegate(OpenUrl);
aboutDlg.DoModal();
aboutDlg.m_onclick_contributers.clear();
+ aboutDlg.m_onclick_url.clear();
}
/////////////////////////////////////////////////////////////////////////////
* good place to do cleanups.
* @return Application's exit value (returned from WinMain()).
*/
-int CMergeApp::ExitInstance()
+int CMergeApp::ExitInstance()
{
charsets_cleanup();
ClearTempfolder(temp);
// Cleanup left over tempfiles from previous instances.
- // Normally this should not neet to do anything - but if for some reason
+ // Normally this should not need to do anything - but if for some reason
// WinMerge did not delete temp files this makes sure they are removed.
CleanupWMtemp();
return 0;
}
-int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
+int CMergeApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
{
// This is a convenient point for breakpointing !!!
// Create a handle to store the parent window of the message box.
CWnd* pParentWnd = CWnd::GetActiveWindow();
-
+
// Check whether an active window was retrieved successfully.
- if ( pParentWnd == NULL )
+ if (pParentWnd == nullptr)
{
// Try to retrieve a handle to the last active popup.
CWnd * mainwnd = GetMainWnd();
- if (mainwnd)
+ if (mainwnd != nullptr)
pParentWnd = mainwnd->GetLastActivePopup();
}
// (if caller set the style)
if (m_bNonInteractive)
+ {
+ if (AttachConsole(ATTACH_PARENT_PROCESS))
+ {
+ DWORD dwWritten;
+ String line = _T("WinMerge: ") + String(lpszPrompt) + _T("\n");
+ WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), line.c_str(), static_cast<DWORD>(line.length()), &dwWritten, nullptr);
+ FreeConsole();
+ }
return IDCANCEL;
+ }
// Create the message box dialog.
CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
nIDPrompt);
-
+
if (m_pMainWnd->IsIconic())
m_pMainWnd->ShowWindow(SW_RESTORE);
return static_cast<int>(dlgMessage.DoModal());
}
-/**
- * @brief Set flag so that application will broadcast notification at next
- * idle time (via WM_TIMER id=IDLE_TIMER)
- */
-void CMergeApp::SetNeedIdleTimer()
+bool CMergeApp::IsReallyIdle() const
{
- m_bNeedIdleTimer = TRUE;
+ bool idle = true;
+ POSITION pos = m_pDirTemplate->GetFirstDocPosition();
+ while (pos != nullptr)
+ {
+ CDirDoc *pDirDoc = static_cast<CDirDoc *>(m_pDirTemplate->GetNextDoc(pos));
+ if (const CompareStats *pCompareStats = pDirDoc->GetCompareStats())
+ {
+ if (!pCompareStats->IsCompareDone() || pDirDoc->GetGeneratingReport())
+ idle = false;
+ }
+ }
+ return idle;
}
-BOOL CMergeApp::OnIdle(LONG lCount)
+BOOL CMergeApp::OnIdle(LONG lCount)
{
if (CWinApp::OnIdle(lCount))
return TRUE;
// If anyone has requested notification when next idle occurs, send it
if (m_bNeedIdleTimer)
{
- m_bNeedIdleTimer = FALSE;
+ m_bNeedIdleTimer = false;
m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
}
+
+ if (m_bNonInteractive && IsReallyIdle())
+ m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
+
+ if (typeid(*GetOptionsMgr()) == typeid(CRegOptionsMgr))
+ {
+ static_cast<CRegOptionsMgr*>(GetOptionsMgr())->CloseKeys();
+ }
+
return FALSE;
}
*/
void CMergeApp::InitializeFileFilters()
{
- CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
+ String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
- if (!filterPath.IsEmpty())
+ if (!filterPath.empty())
{
- m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
+ m_pGlobalFileFilter->SetUserFilterPath(filterPath);
}
m_pGlobalFileFilter->LoadAllFileFilters();
}
+void CMergeApp::ApplyCommandLineConfigOptions(MergeCmdLineInfo& cmdInfo)
+{
+ if (cmdInfo.m_bNoPrefs)
+ m_pOptions->SetSerializing(false); // Turn off serializing to registry.
+
+ for (const auto& it : cmdInfo.m_Options)
+ {
+ if (m_pOptions->Set(it.first, it.second) == COption::OPT_NOTFOUND)
+ {
+ String longname = m_pOptions->ExpandShortName(it.first);
+ if (!longname.empty())
+ {
+ m_pOptions->Set(longname, it.second);
+ }
+ else
+ {
+ cmdInfo.m_sErrorMessages.push_back(strutils::format_string1(_T("Invalid key '%1' specified in /config option"), it.first));
+ }
+ }
+ }
+}
+
/** @brief Read command line arguments and open files for comparison.
*
* The name of the function is a legacy code from the time that this function
* MergeCmdLineInfo class.
* @param [in] cmdInfo Commandline parameters info.
* @param [in] pMainFrame Pointer to application main frame.
- * @return TRUE if we opened the compare, FALSE if the compare was canceled.
+ * @return `true` if we opened the compare, `false` if the compare was canceled.
*/
-BOOL CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
+bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
{
- BOOL bCompared = FALSE;
+ bool bCompared = false;
String strDesc[3];
+ std::unique_ptr<PackingInfo> infoUnpacker;
+
m_bNonInteractive = cmdInfo.m_bNonInteractive;
+ if (!cmdInfo.m_sUnpacker.empty())
+ {
+ infoUnpacker.reset(new PackingInfo(PLUGIN_MODE::PLUGIN_MANUAL));
+ infoUnpacker->m_PluginName = cmdInfo.m_sUnpacker;
+ }
+
// Set the global file filter.
if (!cmdInfo.m_sFileFilter.empty())
{
UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
}
+ // Set compare method
+ if (cmdInfo.m_nCompMethod.has_value())
+ GetOptionsMgr()->Set(OPT_CMP_METHOD, *cmdInfo.m_nCompMethod);
+
// Unless the user has requested to see WinMerge's usage open files for
// comparison.
if (cmdInfo.m_bShowUsage)
{
// Set the required information we need from the command line:
- m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
m_bEscShutdown = cmdInfo.m_bEscShutdown;
cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
- dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
- cmdInfo.m_sPreDiffer);
+ dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
+ cmdInfo.m_sPreDiffer, infoUnpacker.get());
}
else if (cmdInfo.m_Files.GetSize() > 1)
{
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, NULL,
- cmdInfo.m_sPreDiffer);
+ dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
+ cmdInfo.m_sPreDiffer, infoUnpacker.get());
}
else if (cmdInfo.m_Files.GetSize() == 1)
{
String sFilepath = cmdInfo.m_Files[0];
- if (IsProjectFile(sFilepath))
+ if (cmdInfo.m_bSelfCompare)
+ {
+ strDesc[0] = cmdInfo.m_sLeftDesc;
+ strDesc[1] = cmdInfo.m_sRightDesc;
+ bCompared = pMainFrame->DoSelfCompare(IDOK, sFilepath, strDesc);
+ }
+ else if (IsProjectFile(sFilepath))
{
bCompared = LoadAndOpenProjectFile(sFilepath);
}
{
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, NULL,
- cmdInfo.m_sPreDiffer);
+ dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
+ cmdInfo.m_sPreDiffer, infoUnpacker.get());
}
}
else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
{
- BOOL showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
+ bool showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
if (showFiles)
pMainFrame->DoFileOpen();
}
break;
default:
// no other valid option
- assert (0);
+ assert (false);
ucr::setDefaultCodepage(GetACP());
}
}
sCmd += _T("\"");
}
- BOOL retVal = FALSE;
+ bool retVal = false;
STARTUPINFO stInfo = { sizeof STARTUPINFO };
PROCESS_INFORMATION processInfo;
- retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
- NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
+ retVal = !!CreateProcess(nullptr, (LPTSTR)sCmd.c_str(),
+ nullptr, nullptr, FALSE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
&stInfo, &processInfo);
if (!retVal)
}
/**
- * @brief Open file, if it exists, else open url
- */
-void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
-{
- if (paths::DoesPathExist(szFile) == paths::IS_EXISTING_FILE)
- ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
- else
- ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
-}
-
-/**
* @brief Show Help - this is for opening help from outside mainframe.
- * @param [in] helpLocation Location inside help, if NULL main help is opened.
+ * @param [in] helpLocation Location inside help, if `nullptr` main help is opened.
*/
-void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
+void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= nullptr*/)
{
- String sPath = env::GetProgPath();
+ String name, ext;
LANGID LangId = GetLangId();
- if (PRIMARYLANGID(LangId) == LANG_JAPANESE)
- sPath = paths::ConcatPath(sPath, DocsPath_ja);
- else
- sPath = paths::ConcatPath(sPath, DocsPath);
- if (helpLocation == NULL)
+ paths::SplitFilename(m_pLangDlg->GetFileName(LangId), nullptr, &name, &ext);
+ String sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, name.c_str()));
+ if (paths::DoesPathExist(sPath) != paths::IS_EXISTING_FILE)
+ sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, _T("")));
+ if (helpLocation == nullptr)
{
if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
- ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
+ ::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOC, NULL);
else
- ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
+ shell::Open(DocsURL);
}
else
{
if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
{
sPath += helpLocation;
- ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
+ ::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
}
}
}
* succeeded or failed.
* @param [in] bFolder Are we creating backup in folder compare?
* @param [in] pszPath Full path to file to backup.
- * @return TRUE if backup succeeds, or isn't just done.
+ * @return `true` if backup succeeds, or isn't just done.
*/
-BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
+bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath)
{
// If user doesn't want to backups in folder compare, return
// success so operations don't abort.
if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
- return TRUE;
+ return true;
// Likewise if user doesn't want backups in file compare
else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
- return TRUE;
+ return true;
// create backup copy of file if destination file exists
if (paths::DoesPathExist(pszPath) == paths::IS_EXISTING_FILE)
String path;
String filename;
String ext;
-
+
paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
// Determine backup folder
_RPTF0(_CRT_ERROR, "Unknown backup location!");
}
- BOOL success = FALSE;
+ bool success = false;
if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
{
// Don't add dot if there is no existing extension
// nice way to add a real time (invalid chars etc).
if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
{
- struct tm *tm;
+ struct tm tm;
time_t curtime = 0;
time(&curtime);
- tm = localtime(&curtime);
+ ::localtime_s(&tm, &curtime);
CString timestr;
- timestr.Format(_T("%04d%02d%02d%02d%02d%02d"), tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
+ timestr.Format(_T("%04d%02d%02d%02d%02d%02d"), tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
filename += _T("-");
filename += timestr;
}
// Append filename and extension (+ optional .bak) to path
if ((bakPath.length() + filename.length() + ext.length())
- < MAX_PATH)
+ < MAX_PATH_FULL)
{
- success = TRUE;
+ success = true;
bakPath = paths::ConcatPath(bakPath, filename);
bakPath += _T(".");
bakPath += ext;
}
if (success)
- success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
-
+ {
+ success = !!CopyFileW(TFile(pszPath).wpath().c_str(), TFile(bakPath).wpath().c_str(), FALSE);
+ }
+
if (!success)
{
String msg = strutils::format_string1(
_("Unable to backup original file:\n%1\n\nContinue anyway?"),
pszPath);
if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
- return FALSE;
+ return false;
}
- return TRUE;
+ return true;
}
// we got here because we're either not backing up of there was nothing to backup
- return TRUE;
-}
-
-/**
- * @brief Sync file to Version Control System
- * @param pszDest [in] Where to copy (incl. filename)
- * @param bApplyToAll [in,out] Apply user selection to all items
- * @param psError [out] Error string that can be shown to user in caller func
- * @return User selection or -1 if error happened
- * @sa CMainFrame::HandleReadonlySave()
- * @sa CDirView::PerformActionList()
- */
-int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
- String& sError)
-{
- String sActionError;
- String strSavePath(pszDest);
- int nVerSys = 0;
-
- nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
-
- int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
- if (nRetVal == IDCANCEL || nRetVal == IDNO)
- return nRetVal;
-
- // If VC project opened from VSS sync and version control used
- if ((nVerSys == SourceControl::VCS_VSS4 || nVerSys == SourceControl::VCS_VSS5) && m_pSourceControl->m_bVCProjSync)
- {
- if (!m_pSourceControl->m_vssHelper.ReLinkVCProj(strSavePath, sError))
- nRetVal = -1;
- }
- return nRetVal;
+ return true;
}
/**
* @sa CMainFrame::SyncFileToVCS()
* @sa CMergeDoc::DoSave()
*/
-int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
- BOOL &bApplyToAll)
+int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
+ bool &bApplyToAll)
{
CFileStatus status;
int nRetVal = IDOK;
- BOOL bFileRO = FALSE;
- BOOL bFileExists = FALSE;
+ bool bFileRO = false;
+ bool bFileExists = false;
String s;
String str;
CString title;
- int nVerSys = 0;
if (!strSavePath.empty())
{
}
}
- nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
-
if (bFileExists && bFileRO)
{
UINT userChoice = 0;
- // Version control system used?
- // Checkout file from VCS and modify, don't ask about overwriting
- // RO files etc.
- if (nVerSys != SourceControl::VCS_NONE)
- {
- bool bRetVal = m_pSourceControl->SaveToVersionControl(strSavePath);
- if (bRetVal)
- return IDYES;
- else
- return IDCANCEL;
- }
-
+
// Don't ask again if its already asked
if (bApplyToAll)
userChoice = IDYES;
else
{
// Single file
- str = strutils::format_string1(_("%1 is marked read-only. Would you like to override the read-only file ? (No to save as new filename.)"), strSavePath);
+ str = strutils::format_string1(_("%1 is marked read-only. Would you like to override the read-only file? (No to save as new filename.)"), strSavePath);
userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
IDS_SAVEREADONLY_FMT);
{
// Overwrite read-only file
case IDYESTOALL:
- bApplyToAll = TRUE; // Don't ask again (no break here)
+ bApplyToAll = true; // Don't ask again (no break here)
+ [[fallthrough]];
case IDYES:
CFile::GetStatus(strSavePath.c_str(), status);
status.m_mtime = 0; // Avoid unwanted changes
CFile::SetStatus(strSavePath.c_str(), status);
nRetVal = IDYES;
break;
-
+
// Save to new filename (single) /skip this item (multiple)
case IDNO:
if (!bMultiFile)
{
- if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), _("Save As"), _T(""), FALSE))
+ if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, strSavePath.c_str()))
{
strSavePath = s;
nRetVal = IDNO;
}
/**
- * @brief Shows VSS error from exception and writes log.
- */
-void CMergeApp::ShowVSSError(CException *e, const String& strItem)
-{
- TCHAR errStr[1024] = {0};
- if (e->GetErrorMessage(errStr, 1024))
- {
- String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
- String logMsg = errMsg;
- errMsg += _T("\n");
- errMsg += errStr;
- logMsg += _T(" ");
- logMsg += errStr;
- if (!strItem.empty())
- {
- errMsg += _T("\n\n");
- errMsg += strItem;
- logMsg += _T(": ");
- logMsg += strItem;
- }
- LogErrorString(logMsg);
- AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
- }
- else
- {
- LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
- e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
- }
-}
-
-/**
* @brief Is specified file a project file?
* @param [in] filepath Full path to file to check.
* @return true if file is a projectfile.
bool CMergeApp::IsProjectFile(const String& filepath) const
{
String ext;
- paths::SplitFilename(filepath, NULL, NULL, &ext);
+ paths::SplitFilename(filepath, nullptr, nullptr, &ext);
if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
return true;
else
}
catch (Poco::Exception& e)
{
- String sErr = _("Unknown error attempting to open project file");
+ String sErr = _("Unknown error attempting to open project file.");
sErr += ucr::toTString(e.displayText());
String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
AfxMessageBox(msg.c_str(), MB_ICONSTOP);
}
catch (Poco::Exception& e)
{
- String sErr = _("Unknown error attempting to save project file");
+ String sErr = _("Unknown error attempting to save project file.");
sErr += ucr::toTString(e.displayText());
String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
AfxMessageBox(msg.c_str(), MB_ICONSTOP);
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.
+ * @return `true` if loading project file and starting compare succeeded.
*/
bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sReportFile)
{
ProjectFile project;
if (!LoadProjectFile(sProject, project))
return false;
-
- PathContext files;
- BOOL bLeftReadOnly = FALSE;
- BOOL bMiddleReadOnly = FALSE;
- BOOL bRightReadOnly = FALSE;
- bool bRecursive = FALSE;
- project.GetPaths(files, bRecursive);
- bLeftReadOnly = project.GetLeftReadOnly();
- bMiddleReadOnly = project.GetMiddleReadOnly();
- bRightReadOnly = project.GetRightReadOnly();
- if (project.HasFilter())
- {
- String filter = project.GetFilter();
- filter = strutils::trim_ws(filter);
- m_pGlobalFileFilter->SetFilter(filter);
- }
- if (project.HasSubfolders())
- bRecursive = project.GetSubfolders() > 0;
-
- DWORD dwFlags[3] = {
- static_cast<DWORD>(files.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
- static_cast<DWORD>(files.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
- static_cast<DWORD>(files.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
- };
- if (bLeftReadOnly)
- dwFlags[0] |= FFILEOPEN_READONLY;
- if (files.GetSize() == 2)
- {
- if (bRightReadOnly)
- dwFlags[1] |= FFILEOPEN_READONLY;
- }
- else
+
+ bool rtn = true;
+ for (auto& projItem : project.Items())
{
- if (bMiddleReadOnly)
- dwFlags[1] |= FFILEOPEN_READONLY;
- if (bRightReadOnly)
- dwFlags[2] |= FFILEOPEN_READONLY;
- }
+ PathContext tFiles;
+ bool bRecursive = false;
+ projItem.GetPaths(tFiles, bRecursive);
+ for (int i = 0; i < tFiles.GetSize(); ++i)
+ {
+ if (!paths::IsPathAbsolute(tFiles[i]))
+ {
+ String sProjectDir = paths::GetParentPath(sProject);
+ if (tFiles[i].substr(0, 1) == _T("\\"))
+ {
+ if (sProjectDir.length() > 1 && sProjectDir[1] == ':')
+ tFiles[i] = paths::ConcatPath(sProjectDir.substr(0, 2), tFiles[i]);
+ }
+ else
+ tFiles[i] = paths::ConcatPath(sProjectDir, tFiles[i]);
+ }
+ }
+ bool bLeftReadOnly = projItem.GetLeftReadOnly();
+ bool bMiddleReadOnly = projItem.GetMiddleReadOnly();
+ bool bRightReadOnly = projItem.GetRightReadOnly();
+ if (projItem.HasFilter())
+ {
+ String filter = projItem.GetFilter();
+ filter = strutils::trim_ws(filter);
+ m_pGlobalFileFilter->SetFilter(filter);
+ }
+ if (projItem.HasSubfolders())
+ bRecursive = projItem.GetSubfolders() > 0;
+
+ DWORD dwFlags[3] = {
+ static_cast<DWORD>(tFiles.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
+ static_cast<DWORD>(tFiles.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
+ static_cast<DWORD>(tFiles.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
+ };
+ if (bLeftReadOnly)
+ dwFlags[0] |= FFILEOPEN_READONLY;
+ if (tFiles.GetSize() == 2)
+ {
+ if (bRightReadOnly)
+ dwFlags[1] |= FFILEOPEN_READONLY;
+ }
+ else
+ {
+ if (bMiddleReadOnly)
+ dwFlags[1] |= FFILEOPEN_READONLY;
+ if (bRightReadOnly)
+ dwFlags[2] |= FFILEOPEN_READONLY;
+ }
+
+ GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
- GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
-
- BOOL rtn = GetMainFrame()->DoFileOpen(&files, dwFlags, NULL, sReportFile, bRecursive);
+ rtn &= GetMainFrame()->DoFileOpen(&tFiles, dwFlags, nullptr, sReportFile, bRecursive);
+ }
AddToRecentProjectsMRU(sProject.c_str());
- return !!rtn;
+ return rtn;
}
/**
*/
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 != NULL) {
+ if (m_pRecentFileList != nullptr) {
m_pRecentFileList->Add(sPathName);
m_pRecentFileList->WriteList();
}
pCmdUI->Enable(GetMergingMode());
}
+UINT CMergeApp::GetProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nDefault)
+{
+ COptionsMgr *pOptions = GetOptionsMgr();
+ String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
+ if (!pOptions->Get(name).IsInt())
+ pOptions->InitOption(name, nDefault);
+ return pOptions->GetInt(name);
+}
+
+BOOL CMergeApp::WriteProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nValue)
+{
+ COptionsMgr *pOptions = GetOptionsMgr();
+ String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
+ if (!pOptions->Get(name).IsInt())
+ pOptions->InitOption(name, nValue);
+ return pOptions->SaveOption(name, nValue) == COption::OPT_OK;
+}
+
+CString CMergeApp::GetProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszDefault)
+{
+ COptionsMgr *pOptions = GetOptionsMgr();
+ String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
+ if (!pOptions->Get(name).IsString())
+ pOptions->InitOption(name, lpszDefault ? lpszDefault : _T(""));
+ return pOptions->GetString(name).c_str();
+}
+
+BOOL CMergeApp::WriteProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue)
+{
+ COptionsMgr *pOptions = GetOptionsMgr();
+ if (lpszEntry != nullptr)
+ {
+ String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
+ if (!pOptions->Get(name).IsString())
+ pOptions->InitOption(name, lpszValue ? lpszValue : _T(""));
+ return pOptions->SaveOption(name, lpszValue ? lpszValue : _T("")) == COption::OPT_OK;
+ }
+ else
+ {
+ String name = strutils::format(_T("%s/"), lpszSection);
+ pOptions->RemoveOption(name);
+ }
+ return TRUE;
+}