1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
10 * @brief Defines the class behaviors for the application.
16 #include "Constants.h"
17 #include "UnicodeString.h"
19 #include "Environment.h"
20 #include "OptionsMgr.h"
21 #include "OptionsInit.h"
22 #include "RegOptionsMgr.h"
23 #include "IniOptionsMgr.h"
27 #include "HexMergeDoc.h"
28 #include "HexMergeFrm.h"
29 #include "HexMergeView.h"
32 #include "MergeEditFrm.h"
37 #include "PropBackups.h"
38 #include "FileOrFolderSelect.h"
39 #include "FileFilterHelper.h"
40 #include "LineFiltersList.h"
41 #include "SubstitutionFiltersList.h"
42 #include "SyntaxColors.h"
43 #include "CCrystalTextMarkers.h"
44 #include "OptionsSyntaxColors.h"
46 #include "ProjectFile.h"
47 #include "MergeEditSplitterView.h"
48 #include "LanguageSelect.h"
49 #include "OptionsDef.h"
50 #include "MergeCmdLineInfo.h"
51 #include "ConflictFileParser.h"
53 #include "stringdiffs.h"
57 #include "CompareStats.h"
59 #include "charsets.h" // For shutdown cleanup
65 /** @brief Location for command line help to open. */
66 static const TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
68 /** @brief Backup file extension. */
69 static const TCHAR BACKUP_FILE_EXT[] = _T("bak");
71 /////////////////////////////////////////////////////////////////////////////
74 BEGIN_MESSAGE_MAP(CMergeApp, CWinApp)
75 //{{AFX_MSG_MAP(CMergeApp)
76 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
77 ON_COMMAND(ID_HELP, OnHelp)
78 ON_COMMAND_EX_RANGE(ID_FILE_PROJECT_MRU_FIRST, ID_FILE_PROJECT_MRU_LAST, OnOpenRecentFile)
79 ON_UPDATE_COMMAND_UI(ID_FILE_PROJECT_MRU_FIRST, CWinApp::OnUpdateRecentFileMenu)
80 ON_COMMAND(ID_FILE_MERGINGMODE, OnMergingMode)
81 ON_UPDATE_COMMAND_UI(ID_FILE_MERGINGMODE, OnUpdateMergingMode)
82 ON_UPDATE_COMMAND_UI(ID_STATUS_MERGINGMODE, OnUpdateMergingStatus)
83 ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
85 // Standard file based document commands
86 //ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
87 //ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
88 // Standard print setup command
91 /////////////////////////////////////////////////////////////////////////////
92 // CMergeApp construction
94 CMergeApp::CMergeApp() :
95 m_bNeedIdleTimer(false)
96 , m_pOpenTemplate(nullptr)
97 , m_pDiffTemplate(nullptr)
98 , m_pHexMergeTemplate(nullptr)
99 , m_pDirTemplate(nullptr)
100 , m_mainThreadScripts(nullptr)
101 , m_nLastCompareResult(-1)
102 , m_bNonInteractive(false)
103 , m_pOptions(nullptr)
104 , m_pGlobalFileFilter(nullptr)
105 , m_nActiveOperations(0)
106 , m_pLangDlg(new CLanguageSelect())
107 , m_bEscShutdown(false)
108 , m_bExitIfNoDiff(MergeCmdLineInfo::ExitNoDiff::Disabled)
109 , m_pLineFilters(new LineFiltersList())
110 , m_pSubstitutionFiltersList(new SubstitutionFiltersList())
111 , m_pSyntaxColors(new SyntaxColors())
112 , m_pMarkers(new CCrystalTextMarkers())
113 , m_bMergingMode(false)
115 // add construction code here,
116 // Place all significant initialization in InitInstance
120 * @brief Chose which options manager should be initialized.
121 * @return IniOptionsMgr if initial config file exists,
122 * CRegOptionsMgr otherwise.
124 static COptionsMgr *CreateOptionManager(const MergeCmdLineInfo& cmdInfo)
126 String iniFilePath = cmdInfo.m_sIniFilepath;
127 if (!iniFilePath.empty())
129 iniFilePath = paths::GetLongPath(iniFilePath);
130 if (paths::CreateIfNeeded(paths::GetParentPath(iniFilePath)))
131 return new CIniOptionsMgr(iniFilePath);
133 iniFilePath = paths::ConcatPath(env::GetProgPath(), _T("winmerge.ini"));
134 if (paths::DoesPathExist(iniFilePath) == paths::IS_EXISTING_FILE)
135 return new CIniOptionsMgr(iniFilePath);
136 return new CRegOptionsMgr();
139 static HANDLE CreateMutexHandle()
141 // Create exclusion mutex name
142 TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
143 DWORD dwLengthNeeded;
144 GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME,
145 szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
146 TCHAR szMutexName[MAX_PATH + 40];
147 // Combine window class name and desktop name to form a unique mutex name.
148 // As the window class name is decorated to distinguish between ANSI and
149 // UNICODE build, so will be the mutex name.
150 wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
151 return CreateMutex(nullptr, FALSE, szMutexName);
154 static HWND ActivatePreviousInstanceAndSendCommandline(LPTSTR cmdLine)
156 HWND hWnd = FindWindow(CMainFrame::szClassName, nullptr);
160 ShowWindow(hWnd, SW_RESTORE);
161 SetForegroundWindow(GetLastActivePopup(hWnd));
162 COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine };
163 if (!SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
168 static void WaitForExitPreviousInstance(HWND hWnd)
170 DWORD dwProcessId = 0;
171 GetWindowThreadProcessId(hWnd, &dwProcessId);
172 HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, dwProcessId);
174 WaitForSingleObject(hProcess, INFINITE);
177 static int ConvertLastCompareResultToExitCode(int nLastCompareResult)
179 if (nLastCompareResult == 0)
181 else if (nLastCompareResult > 0)
186 CMergeApp::~CMergeApp()
190 /////////////////////////////////////////////////////////////////////////////
191 // The one and only CMergeApp object
195 /////////////////////////////////////////////////////////////////////////////
196 // CMergeApp initialization
199 * @brief Initialize WinMerge application instance.
200 * @return TRUE if application initialization succeeds (and we'll run it),
201 * FALSE if something failed and we exit the instance.
202 * @todo We could handle these failure situations more gratefully, i.e. show
203 * at least some error message to the user..
205 BOOL CMergeApp::InitInstance()
207 // Prevents DLL hijacking
208 HMODULE hLibrary = GetModuleHandle(_T("kernel32.dll"));
209 BOOL (WINAPI *pfnSetSearchPathMode)(DWORD) = (BOOL (WINAPI *)(DWORD))GetProcAddress(hLibrary, "SetSearchPathMode");
210 if (pfnSetSearchPathMode != nullptr)
211 pfnSetSearchPathMode(0x00000001L /*BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE*/ | 0x00008000L /*BASE_SEARCH_PATH_PERMANENT*/);
212 BOOL (WINAPI *pfnSetDllDirectoryA)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))GetProcAddress(hLibrary, "SetDllDirectoryA");
213 if (pfnSetDllDirectoryA != nullptr)
214 pfnSetDllDirectoryA("");
216 JumpList::SetCurrentProcessExplicitAppUserModelID(L"Thingamahoochie.WinMerge");
218 InitCommonControls(); // initialize common control library
219 CWinApp::InitInstance(); // call parent class method
221 m_imageForInitializingGdiplus.Load((IStream*)nullptr); // initialize GDI+
223 // Runtime switch so programmer may set this in interactive debugger
227 // get current setting
228 int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
229 // Keep freed memory blocks in the heap's linked list and mark them as freed
230 tmpFlag |= _CRTDBG_DELAY_FREE_MEM_DF;
231 // Call _CrtCheckMemory at every allocation and deallocation request.
232 // WARNING: This slows down WinMerge *A LOT*
233 tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
234 // Set the new state for the flag
235 _CrtSetDbgFlag( tmpFlag );
238 // CCrystalEdit Drag and Drop functionality needs AfxOleInit.
241 TRACE(_T("AfxOleInitFailed. OLE functionality disabled"));
244 // Standard initialization
245 // If you are not using these features and wish to reduce the size
246 // of your final executable, you should remove from the following
247 // the specific initialization routines you do not need.
249 // Revoke the standard OLE Message Filter to avoid drawing frame while loading files.
250 COleMessageFilter* pOldFilter = AfxOleGetMessageFilter();
251 pOldFilter->Revoke();
253 // Load registry keys from WinMerge.reg if existing WinMerge.reg
254 env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
256 // Parse command-line arguments.
258 MergeCmdLineInfo cmdInfo(_T(""));
260 MergeCmdLineInfo cmdInfo(GetCommandLine());
262 m_pOptions.reset(CreateOptionManager(cmdInfo));
263 if (cmdInfo.m_bNoPrefs)
264 m_pOptions->SetSerializing(false); // Turn off serializing to registry.
266 if (dynamic_cast<CRegOptionsMgr*>(m_pOptions.get()) != nullptr)
267 Options::CopyHKLMValues();
269 Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
270 ApplyCommandLineConfigOptions(cmdInfo);
271 if (cmdInfo.m_sErrorMessages.size() > 0)
273 if (AttachConsole(ATTACH_PARENT_PROCESS))
276 for (auto& msg : cmdInfo.m_sErrorMessages)
278 String line = _T("WinMerge: ") + msg + _T("\n");
279 WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), line.c_str(), static_cast<DWORD>(line.length()), &dwWritten, nullptr);
285 // Initialize temp folder
288 // If paths were given to commandline we consider this being an invoke from
289 // commandline (from other application, shellextension etc).
290 bool bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
292 // WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
293 // This is the name of the company of the original author (Dean Grimm)
294 SetRegistryKey(_T("Thingamahoochie"));
296 int nSingleInstance = cmdInfo.m_nSingleInstance.has_value() ?
297 *cmdInfo.m_nSingleInstance : GetOptionsMgr()->GetInt(OPT_SINGLE_INSTANCE);
299 HANDLE hMutex = CreateMutexHandle();
300 if (hMutex != nullptr)
301 WaitForSingleObject(hMutex, INFINITE);
302 if (nSingleInstance != 0 && GetLastError() == ERROR_ALREADY_EXISTS)
304 // Activate previous instance and send commandline to it
305 HWND hWnd = ActivatePreviousInstanceAndSendCommandline(GetCommandLine());
308 ReleaseMutex(hMutex);
310 if (nSingleInstance != 1)
311 WaitForExitPreviousInstance(hWnd);
316 LoadStdProfileSettings(GetOptionsMgr()->GetInt(OPT_MRU_MAX)); // Load standard INI file options (including MRU)
319 UpdateCodepageModule();
321 FileTransform::AutoUnpacking = GetOptionsMgr()->GetBool(OPT_PLUGINS_UNPACKER_MODE);
322 FileTransform::AutoPrediffing = GetOptionsMgr()->GetBool(OPT_PLUGINS_PREDIFFER_MODE);
324 NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
325 if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
327 const int lfHeight = -MulDiv(9, CClientDC(CWnd::GetDesktopWindow()).GetDeviceCaps(LOGPIXELSY), 72);
328 if (abs(ncm.lfMenuFont.lfHeight) > abs(lfHeight))
329 ncm.lfMenuFont.lfHeight = lfHeight;
330 if (wcscmp(ncm.lfMenuFont.lfFaceName, L"Meiryo") == 0 || wcscmp(ncm.lfMenuFont.lfFaceName, L"\U000030e1\U000030a4\U000030ea\U000030aa"/* "Meiryo" in Japanese */) == 0)
331 wcscpy_s(ncm.lfMenuFont.lfFaceName, L"Meiryo UI");
332 m_fontGUI.CreateFontIndirect(&ncm.lfMenuFont);
335 if (m_pSyntaxColors != nullptr)
336 Options::SyntaxColors::Init(GetOptionsMgr(), m_pSyntaxColors.get());
338 if (m_pMarkers != nullptr)
339 m_pMarkers->LoadFromRegistry();
341 CCrystalTextView::SetRenderingModeDefault(static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE)));
343 if (m_pLineFilters != nullptr)
344 m_pLineFilters->Initialize(GetOptionsMgr());
346 // If there are no filters loaded, and there is filter string in previous
347 // option string, import old filters to new place.
348 if (m_pLineFilters->GetCount() == 0)
350 String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
351 if (!oldFilter.empty())
352 m_pLineFilters->Import(oldFilter);
355 if (m_pSubstitutionFiltersList != nullptr)
356 m_pSubstitutionFiltersList->Initialize(GetOptionsMgr());
358 // Check if filter folder is set, and create it if not
359 String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
360 if (pathMyFolders.empty())
362 // No filter path, set it to default and make sure it exists.
363 pathMyFolders = GetOptionsMgr()->GetDefault<String>(OPT_FILTER_USERPATH);
364 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathMyFolders);
365 theApp.GetGlobalFileFilter()->SetUserFilterPath(pathMyFolders);
367 if (!paths::CreateIfNeeded(pathMyFolders))
369 // Failed to create a folder, check it didn't already
371 DWORD errCode = GetLastError();
372 if (errCode != ERROR_ALREADY_EXISTS)
374 // Failed to create a folder for filters, fallback to
375 // "My Documents"-folder. It is not worth the trouble to
376 // bother user about this or user more clever solutions.
377 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, env::GetMyDocuments());
381 strdiff::Init(); // String diff init
382 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
384 m_bMergingMode = GetOptionsMgr()->GetBool(OPT_MERGE_MODE);
386 // Initialize i18n (multiple language) support
388 m_pLangDlg->InitializeLanguage((WORD)GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
390 m_mainThreadScripts = new CAssureScriptsForThread;
392 // Register the application's document templates. Document templates
393 // serve as the connection between documents, frame windows and views.
396 m_pOpenTemplate = new CMultiDocTemplate(
398 RUNTIME_CLASS(COpenDoc),
399 RUNTIME_CLASS(COpenFrame), // custom MDI child frame
400 RUNTIME_CLASS(COpenView));
401 AddDocTemplate(m_pOpenTemplate);
404 m_pDiffTemplate = new CMultiDocTemplate(
406 RUNTIME_CLASS(CMergeDoc),
407 RUNTIME_CLASS(CMergeEditFrame), // custom MDI child frame
408 RUNTIME_CLASS(CMergeEditSplitterView));
409 AddDocTemplate(m_pDiffTemplate);
412 m_pHexMergeTemplate = new CMultiDocTemplate(
414 RUNTIME_CLASS(CHexMergeDoc),
415 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
416 RUNTIME_CLASS(CHexMergeView));
417 AddDocTemplate(m_pHexMergeTemplate);
420 m_pDirTemplate = new CMultiDocTemplate(
422 RUNTIME_CLASS(CDirDoc),
423 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
424 RUNTIME_CLASS(CDirView));
425 AddDocTemplate(m_pDirTemplate);
427 // create main MDI Frame window
428 CMainFrame* pMainFrame = new CMainFrame;
429 if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
431 if (hMutex != nullptr)
433 ReleaseMutex(hMutex);
438 m_pMainWnd = pMainFrame;
440 // Init menus -- hMenuDefault is for MainFrame
441 pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
444 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
445 CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
446 pMainFrame->MDISetMenu(pNewMenu, nullptr);
448 // The main window has been initialized, so activate it.
449 pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
451 // Since this function actually opens paths for compare it must be
452 // called after initializing CMainFrame!
453 bool bContinue = true;
454 if (!ParseArgsAndDoOpen(cmdInfo, pMainFrame) && bCommandLineInvoke)
457 if (hMutex != nullptr)
458 ReleaseMutex(hMutex);
460 // If user wants to cancel the compare, close WinMerge
463 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
467 WinMergeTest::TestAll();
473 static void OpenContributersFile(int&)
475 CMergeApp::OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
478 static void OpenUrl(int&)
480 shell::Open(WinMergeURL);
483 // App command to run the dialog
484 void CMergeApp::OnAppAbout()
487 aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
488 aboutDlg.m_onclick_url += Poco::delegate(OpenUrl);
490 aboutDlg.m_onclick_contributers.clear();
491 aboutDlg.m_onclick_url.clear();
494 /////////////////////////////////////////////////////////////////////////////
495 // CMergeApp commands
498 * @brief Called when application is about to exit.
499 * This functions is called when application is exiting, so this is
500 * good place to do cleanups.
501 * @return Application's exit value (returned from WinMain()).
503 int CMergeApp::ExitInstance()
507 // Save registry keys if existing WinMerge.reg
508 env::SaveRegistryToFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")), RegDir);
511 const String temp = env::GetTemporaryPath();
512 ClearTempfolder(temp);
514 // Cleanup left over tempfiles from previous instances.
515 // Normally this should not need to do anything - but if for some reason
516 // WinMerge did not delete temp files this makes sure they are removed.
519 delete m_mainThreadScripts;
520 CWinApp::ExitInstance();
523 // There is a problem that OleUninitialize() in mfc/oleinit.cpp, which is called just before the process exits,
524 // hangs in rare cases.
525 // To deal with this problem, force the process to exit
526 // if the process does not exit within 2 seconds after the call to CMergeApp::ExitInstance().
528 [](void*) -> unsigned int {
531 }, nullptr, 0, nullptr);
534 return ConvertLastCompareResultToExitCode(m_nLastCompareResult);
537 int CMergeApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
539 // This is a convenient point for breakpointing !!!
541 // Create a handle to store the parent window of the message box.
542 CWnd* pParentWnd = CWnd::GetActiveWindow();
544 // Check whether an active window was retrieved successfully.
545 if (pParentWnd == nullptr)
547 // Try to retrieve a handle to the last active popup.
548 CWnd * mainwnd = GetMainWnd();
549 if (mainwnd != nullptr)
550 pParentWnd = mainwnd->GetLastActivePopup();
553 // Use our own message box implementation, which adds the
554 // do not show again checkbox, and implements it on subsequent calls
555 // (if caller set the style)
557 if (m_bNonInteractive)
559 if (AttachConsole(ATTACH_PARENT_PROCESS))
562 String line = _T("WinMerge: ") + String(lpszPrompt) + _T("\n");
563 WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), line.c_str(), static_cast<DWORD>(line.length()), &dwWritten, nullptr);
569 // Create the message box dialog.
570 CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
573 if (m_pMainWnd->IsIconic())
574 m_pMainWnd->ShowWindow(SW_RESTORE);
576 // Display the message box dialog and return the result.
577 return static_cast<int>(dlgMessage.DoModal());
580 bool CMergeApp::IsReallyIdle() const
583 POSITION pos = m_pDirTemplate->GetFirstDocPosition();
584 while (pos != nullptr)
586 CDirDoc *pDirDoc = static_cast<CDirDoc *>(m_pDirTemplate->GetNextDoc(pos));
587 if (const CompareStats *pCompareStats = pDirDoc->GetCompareStats())
589 if (!pCompareStats->IsCompareDone() || pDirDoc->GetGeneratingReport())
596 BOOL CMergeApp::OnIdle(LONG lCount)
598 if (CWinApp::OnIdle(lCount))
601 // If anyone has requested notification when next idle occurs, send it
602 if (m_bNeedIdleTimer)
604 m_bNeedIdleTimer = false;
605 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
608 if (m_bNonInteractive && IsReallyIdle())
609 m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
611 if (typeid(*GetOptionsMgr()) == typeid(CRegOptionsMgr))
613 static_cast<CRegOptionsMgr*>(GetOptionsMgr())->CloseKeys();
620 * @brief Load any known file filters.
622 * This function loads filter files from paths we know contain them.
623 * @note User's filter location may not be set yet.
625 void CMergeApp::InitializeFileFilters()
627 assert(m_pGlobalFileFilter != nullptr);
628 String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
630 if (!filterPath.empty())
632 m_pGlobalFileFilter->SetUserFilterPath(filterPath);
634 m_pGlobalFileFilter->LoadAllFileFilters();
637 void CMergeApp::ApplyCommandLineConfigOptions(MergeCmdLineInfo& cmdInfo)
639 if (cmdInfo.m_bNoPrefs)
640 m_pOptions->SetSerializing(false); // Turn off serializing to registry.
642 for (const auto& it : cmdInfo.m_Options)
644 if (m_pOptions->Set(it.first, it.second) == COption::OPT_NOTFOUND)
646 String longname = m_pOptions->ExpandShortName(it.first);
647 if (!longname.empty())
649 m_pOptions->Set(longname, it.second);
653 cmdInfo.m_sErrorMessages.push_back(strutils::format_string1(_T("Invalid key '%1' specified in /config option"), it.first));
659 /** @brief Read command line arguments and open files for comparison.
661 * The name of the function is a legacy code from the time that this function
662 * actually parsed the command line. Today the parsing is done using the
663 * MergeCmdLineInfo class.
664 * @param [in] cmdInfo Commandline parameters info.
665 * @param [in] pMainFrame Pointer to application main frame.
666 * @return `true` if we opened the compare, `false` if the compare was canceled.
668 bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
670 bool bCompared = false;
672 std::unique_ptr<PackingInfo> infoUnpacker;
673 std::unique_ptr<PrediffingInfo> infoPrediffer;
674 unsigned nID = cmdInfo.m_nWindowType == MergeCmdLineInfo::AUTOMATIC ?
675 0 : static_cast<unsigned>(cmdInfo.m_nWindowType) + ID_MERGE_COMPARE_TEXT - 1;
677 m_bNonInteractive = cmdInfo.m_bNonInteractive;
679 if (!cmdInfo.m_sUnpacker.empty())
680 infoUnpacker.reset(new PackingInfo(cmdInfo.m_sUnpacker));
682 if (!cmdInfo.m_sPreDiffer.empty())
683 infoPrediffer.reset(new PrediffingInfo(cmdInfo.m_sPreDiffer));
685 // Set the global file filter.
686 if (!cmdInfo.m_sFileFilter.empty())
688 GetGlobalFileFilter()->SetFilter(cmdInfo.m_sFileFilter);
692 if (cmdInfo.m_nCodepage)
694 UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
697 // Set compare method
698 if (cmdInfo.m_nCompMethod.has_value())
699 GetOptionsMgr()->Set(OPT_CMP_METHOD, *cmdInfo.m_nCompMethod);
701 // Unless the user has requested to see WinMerge's usage open files for
703 if (cmdInfo.m_bShowUsage)
705 ShowHelp(CommandLineHelpLocation);
709 // Set the required information we need from the command line:
711 m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
712 m_bEscShutdown = cmdInfo.m_bEscShutdown;
714 m_strSaveAsPath = cmdInfo.m_sOutputpath;
716 strDesc[0] = cmdInfo.m_sLeftDesc;
717 if (cmdInfo.m_Files.GetSize() < 3)
719 strDesc[1] = cmdInfo.m_sRightDesc;
723 strDesc[1] = cmdInfo.m_sMiddleDesc;
724 strDesc[2] = cmdInfo.m_sRightDesc;
727 std::unique_ptr<CMainFrame::OpenFileParams> pOpenParams;
728 if (cmdInfo.m_nWindowType == MergeCmdLineInfo::TEXT)
729 pOpenParams.reset(new CMainFrame::OpenTextFileParams());
730 else if (cmdInfo.m_nWindowType == MergeCmdLineInfo::TABLE)
731 pOpenParams.reset(new CMainFrame::OpenTableFileParams());
733 pOpenParams.reset(static_cast<CMainFrame::OpenTableFileParams *>(new CMainFrame::OpenAutoFileParams()));
734 if (auto* pOpenTextFileParams = dynamic_cast<CMainFrame::OpenTextFileParams*>(pOpenParams.get()))
736 pOpenTextFileParams->m_line = cmdInfo.m_nLineIndex;
737 pOpenTextFileParams->m_char = cmdInfo.m_nCharIndex;
738 pOpenTextFileParams->m_fileExt = cmdInfo.m_sFileExt;
740 if (auto* pOpenTableFileParams = dynamic_cast<CMainFrame::OpenTableFileParams*>(pOpenParams.get()))
742 pOpenTableFileParams->m_tableDelimiter = cmdInfo.m_cTableDelimiter;
743 pOpenTableFileParams->m_tableQuote = cmdInfo.m_cTableQuote;
744 pOpenTableFileParams->m_tableAllowNewlinesInQuotes = cmdInfo.m_bTableAllowNewlinesInQuotes;
746 if (cmdInfo.m_Files.GetSize() > 2)
748 cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
749 cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
750 cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
751 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
752 bCompared = pMainFrame->DoFileOrFolderOpen(&cmdInfo.m_Files,
753 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
754 infoUnpacker.get(), infoPrediffer.get(), nID, pOpenParams.get());
756 else if (cmdInfo.m_Files.GetSize() > 1)
758 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
759 bCompared = pMainFrame->DoFileOrFolderOpen(&cmdInfo.m_Files,
760 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
761 infoUnpacker.get(), infoPrediffer.get(), nID, pOpenParams.get());
763 else if (cmdInfo.m_Files.GetSize() == 1)
765 String sFilepath = cmdInfo.m_Files[0];
766 if (cmdInfo.m_bSelfCompare)
768 strDesc[0] = cmdInfo.m_sLeftDesc;
769 strDesc[1] = cmdInfo.m_sRightDesc;
770 bCompared = pMainFrame->DoSelfCompare(nID, sFilepath, strDesc,
771 infoUnpacker.get(), infoPrediffer.get(), pOpenParams.get());
773 else if (IsProjectFile(sFilepath))
775 bCompared = LoadAndOpenProjectFile(sFilepath);
777 else if (IsConflictFile(sFilepath))
779 //For a conflict file, load the descriptions in their respective positions: (they will be reordered as needed)
780 strDesc[0] = cmdInfo.m_sLeftDesc;
781 strDesc[1] = cmdInfo.m_sMiddleDesc;
782 strDesc[2] = cmdInfo.m_sRightDesc;
783 bCompared = pMainFrame->DoOpenConflict(sFilepath, strDesc);
787 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
788 bCompared = pMainFrame->DoFileOrFolderOpen(&cmdInfo.m_Files,
789 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
790 infoUnpacker.get(), infoPrediffer.get(), nID, pOpenParams.get());
793 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
795 if (!cmdInfo.m_bNewCompare)
797 bool showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
799 pMainFrame->DoFileOrFolderOpen();
803 bCompared = pMainFrame->DoFileNew(nID, 2, strDesc, infoPrediffer.get(), pOpenParams.get());
810 void CMergeApp::UpdateDefaultCodepage(int cpDefaultMode, int cpCustomCodepage)
814 switch (cpDefaultMode)
817 ucr::setDefaultCodepage(GetACP());
821 wLangId = GetLangId();
822 if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
823 ucr::setDefaultCodepage(_ttol(buff));
825 ucr::setDefaultCodepage(GetACP());
828 ucr::setDefaultCodepage(cpCustomCodepage);
831 // no other valid option
833 ucr::setDefaultCodepage(GetACP());
838 * @brief Send current option settings into codepage module
840 void CMergeApp::UpdateCodepageModule()
842 // Get current codepage settings from the options module
843 // and push them into the codepage module
844 UpdateDefaultCodepage(GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_MODE), GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_CUSTOM));
847 /** @brief Open help from mainframe when user presses F1*/
848 void CMergeApp::OnHelp()
854 * @brief Open given file to external editor specified in options.
855 * @param [in] file Full path to file to open.
857 * Opens file to defined (in Options/system), Notepad by default,
858 * external editor. Path is decorated with quotation marks if needed
859 * (contains spaces). Also '$file' in editor path is replaced by
861 * @param [in] file Full path to file to open.
862 * @param [in] nLineNumber Line number to go to.
864 void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
866 String sCmd = env::ExpandEnvironmentVariables(GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD));
868 strutils::replace(sCmd, _T("$linenum"), strutils::to_str(nLineNumber));
870 size_t nIndex = sCmd.find(_T("$file"));
871 if (nIndex != String::npos)
873 sFile.insert(0, _T("\""));
874 strutils::replace(sCmd, _T("$file"), sFile);
875 nIndex = sCmd.find(' ', nIndex + sFile.length());
876 if (nIndex != String::npos)
877 sCmd.insert(nIndex, _T("\""));
889 STARTUPINFO stInfo = { sizeof STARTUPINFO };
890 PROCESS_INFORMATION processInfo;
892 retVal = !!CreateProcess(nullptr, (LPTSTR)sCmd.c_str(),
893 nullptr, nullptr, FALSE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
894 &stInfo, &processInfo);
898 // Error invoking external editor
899 String msg = strutils::format_string1(_("Failed to execute external editor: %1"), sCmd);
900 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
904 CloseHandle(processInfo.hThread);
905 CloseHandle(processInfo.hProcess);
909 /** @brief Returns pointer to global file filter */
910 FileFilterHelper* CMergeApp::GetGlobalFileFilter()
912 if (!m_pGlobalFileFilter)
914 m_pGlobalFileFilter.reset(new FileFilterHelper());
916 InitializeFileFilters();
918 // Read last used filter from registry
919 // If filter fails to set, reset to default
920 const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
921 bool bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
924 String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
925 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
929 return m_pGlobalFileFilter.get();
933 * @brief Show Help - this is for opening help from outside mainframe.
934 * @param [in] helpLocation Location inside help, if `nullptr` main help is opened.
936 void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= nullptr*/)
938 String sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, GetLangName()));
939 if (paths::DoesPathExist(sPath) != paths::IS_EXISTING_FILE)
940 sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, _T("")));
941 if (helpLocation == nullptr)
943 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
944 ::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOC, NULL);
946 shell::Open(DocsURL);
950 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
952 sPath += helpLocation;
953 ::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
959 * @brief Creates backup before file is saved or copied over.
960 * This function handles formatting correct path and filename for
961 * backup file. Formatting is done based on several options available
962 * for users in Options/Backups dialog. After path is formatted, file
963 * is simply just copied. Not much error checking, just if copying
964 * succeeded or failed.
965 * @param [in] bFolder Are we creating backup in folder compare?
966 * @param [in] pszPath Full path to file to backup.
967 * @return `true` if backup succeeds, or isn't just done.
969 bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath)
971 // If user doesn't want to backups in folder compare, return
972 // success so operations don't abort.
973 if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
975 // Likewise if user doesn't want backups in file compare
976 else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
979 // create backup copy of file if destination file exists
980 if (paths::DoesPathExist(pszPath) == paths::IS_EXISTING_FILE)
987 paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
989 // Determine backup folder
990 if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
991 PropBackups::FOLDER_ORIGINAL)
993 // Put backups to same folder than original file
996 else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
997 PropBackups::FOLDER_GLOBAL)
999 // Put backups to global folder defined in options
1000 bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
1001 if (bakPath.empty())
1004 bakPath = paths::GetLongPath(bakPath);
1008 _RPTF0(_CRT_ERROR, "Unknown backup location!");
1011 bool success = false;
1012 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
1014 // Don't add dot if there is no existing extension
1017 ext += BACKUP_FILE_EXT;
1020 // Append time to filename if wanted so
1021 // NOTE just adds timestamp at the moment as I couldn't figure out
1022 // nice way to add a real time (invalid chars etc).
1023 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
1028 ::localtime_s(&tm, &curtime);
1030 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);
1031 filename += _T("-");
1032 filename += timestr;
1035 // Append filename and extension (+ optional .bak) to path
1036 if ((bakPath.length() + filename.length() + ext.length())
1040 bakPath = paths::ConcatPath(bakPath, filename);
1047 success = !!CopyFileW(TFile(pszPath).wpath().c_str(), TFile(bakPath).wpath().c_str(), FALSE);
1052 String msg = strutils::format_string1(
1053 _("Unable to backup original file:\n%1\n\nContinue anyway?"),
1055 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_BACKUP_FAILED_PROMPT) != IDYES)
1061 // we got here because we're either not backing up of there was nothing to backup
1066 * @brief Checks if path (file/folder) is read-only and asks overwriting it.
1068 * @param strSavePath [in,out] Path where to save (file or folder)
1069 * @param bMultiFile [in] Single file or multiple files/folder
1070 * @param bApplyToAll [in,out] Apply last user selection for all items?
1071 * @return Users selection:
1072 * - IDOK: Item was not readonly, no actions
1073 * - IDYES/IDYESTOALL: Overwrite readonly item
1074 * - IDNO: User selected new filename (single file) or user wants to skip
1075 * - IDCANCEL: Cancel operation
1076 * @sa CMainFrame::SyncFileToVCS()
1077 * @sa CMergeDoc::DoSave()
1079 int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
1084 bool bFileRO = false;
1085 bool bFileExists = false;
1090 if (!strSavePath.empty())
1094 TFile file(strSavePath);
1095 bFileExists = file.exists();
1097 bFileRO = !file.canWrite();
1104 if (bFileExists && bFileRO)
1106 UINT userChoice = 0;
1108 // Don't ask again if its already asked
1113 // Prompt for user choice
1116 // Multiple files or folder
1117 str = strutils::format_string1(_("%1\nis marked read-only. Would you like to override the read-only item?"), strSavePath);
1118 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1119 MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
1120 MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
1125 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);
1126 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1127 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
1128 IDS_SAVEREADONLY_FMT);
1133 // Overwrite read-only file
1135 bApplyToAll = true; // Don't ask again (no break here)
1138 CFile::GetStatus(strSavePath.c_str(), status);
1139 status.m_mtime = 0; // Avoid unwanted changes
1140 status.m_attribute &= ~CFile::readOnly;
1141 CFile::SetStatus(strSavePath.c_str(), status);
1145 // Save to new filename (single) /skip this item (multiple)
1149 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, strSavePath.c_str()))
1170 String CMergeApp::GetPackingErrorMessage(int pane, int paneCount, const String& path, const PackingInfo& plugin)
1172 String pluginName = plugin.GetPluginPipeline();
1173 return strutils::format_string2(
1175 _("Plugin '%2' cannot pack your changes to the left file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?")
1176 : (pane == paneCount - 1) ?
1177 _("Plugin '%2' cannot pack your changes to the right file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?")
1178 : _("Plugin '%2' cannot pack your changes to the middle file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?"),
1183 * @brief Is specified file a project file?
1184 * @param [in] filepath Full path to file to check.
1185 * @return true if file is a projectfile.
1187 bool CMergeApp::IsProjectFile(const String& filepath) const
1190 paths::SplitFilename(filepath, nullptr, nullptr, &ext);
1191 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1197 bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
1199 if (sProject.empty())
1204 project.Read(sProject);
1206 catch (Poco::Exception& e)
1208 String sErr = _("Unknown error attempting to open project file.");
1209 sErr += ucr::toTString(e.displayText());
1210 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1211 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1218 bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &project)
1222 project.Save(sProject);
1224 catch (Poco::Exception& e)
1226 String sErr = _("Unknown error attempting to save project file.");
1227 sErr += ucr::toTString(e.displayText());
1228 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1229 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1237 * @brief Read project and perform comparison specified
1238 * @param [in] sProject Full path to project file.
1239 * @return `true` if loading project file and starting compare succeeded.
1241 bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sReportFile)
1243 ProjectFile project;
1244 if (!LoadProjectFile(sProject, project))
1248 for (auto& projItem : project.Items())
1250 std::unique_ptr<PrediffingInfo> pInfoPrediffer;
1251 std::unique_ptr<PackingInfo> pInfoUnpacker;
1253 bool bRecursive = false;
1254 projItem.GetPaths(tFiles, bRecursive);
1255 for (int i = 0; i < tFiles.GetSize(); ++i)
1257 if (!paths::IsPathAbsolute(tFiles[i]))
1259 String sProjectDir = paths::GetParentPath(sProject);
1260 if (tFiles[i].substr(0, 1) == _T("\\"))
1262 if (sProjectDir.length() > 1 && sProjectDir[1] == ':')
1263 tFiles[i] = paths::ConcatPath(sProjectDir.substr(0, 2), tFiles[i]);
1266 tFiles[i] = paths::ConcatPath(sProjectDir, tFiles[i]);
1269 bool bLeftReadOnly = projItem.GetLeftReadOnly();
1270 bool bMiddleReadOnly = projItem.GetMiddleReadOnly();
1271 bool bRightReadOnly = projItem.GetRightReadOnly();
1272 if (projItem.HasFilter())
1274 String filter = projItem.GetFilter();
1275 filter = strutils::trim_ws(filter);
1276 GetGlobalFileFilter()->SetFilter(filter);
1278 if (projItem.HasSubfolders())
1279 bRecursive = projItem.GetSubfolders() > 0;
1280 if (projItem.HasUnpacker())
1281 pInfoUnpacker.reset(new PackingInfo(projItem.GetUnpacker()));
1282 if (projItem.HasPrediffer())
1283 pInfoPrediffer.reset(new PrediffingInfo(projItem.GetPrediffer()));
1285 DWORD dwFlags[3] = {
1286 static_cast<DWORD>(tFiles.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1287 static_cast<DWORD>(tFiles.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1288 static_cast<DWORD>(tFiles.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
1291 dwFlags[0] |= FFILEOPEN_READONLY;
1292 if (tFiles.GetSize() == 2)
1295 dwFlags[1] |= FFILEOPEN_READONLY;
1299 if (bMiddleReadOnly)
1300 dwFlags[1] |= FFILEOPEN_READONLY;
1302 dwFlags[2] |= FFILEOPEN_READONLY;
1305 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
1307 if (projItem.HasIgnoreWhite())
1308 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, projItem.GetIgnoreWhite());
1309 if (projItem.HasIgnoreBlankLines())
1310 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, projItem.GetIgnoreBlankLines());
1311 if (projItem.HasIgnoreCase())
1312 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, projItem.GetIgnoreCase());
1313 if (projItem.HasIgnoreEol())
1314 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, projItem.GetIgnoreEol());
1315 if (projItem.HasIgnoreCodepage())
1316 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, projItem.GetIgnoreCodepage());
1317 if (projItem.HasFilterCommentsLines())
1318 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, projItem.GetFilterCommentsLines());
1319 if (projItem.HasCompareMethod())
1320 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, projItem.GetCompareMethod());
1322 rtn &= GetMainFrame()->DoFileOrFolderOpen(&tFiles, dwFlags, nullptr, sReportFile, bRecursive,
1323 nullptr, pInfoUnpacker.get(), pInfoPrediffer.get());
1326 AddToRecentProjectsMRU(sProject.c_str());
1331 * @brief Return windows language ID of current WinMerge GUI language
1333 WORD CMergeApp::GetLangId() const
1335 return m_pLangDlg->GetLangId();
1338 String CMergeApp::GetLangName() const
1341 paths::SplitFilename(theApp.m_pLangDlg->GetFileName(theApp.GetLangId()), nullptr, &name, &ext);
1346 * @brief Lang aware version of CStatusBar::SetIndicators()
1348 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
1350 m_pLangDlg->SetIndicators(sb, rgid, n);
1354 * @brief Translate menu to current WinMerge GUI language
1356 void CMergeApp::TranslateMenu(HMENU h) const
1358 m_pLangDlg->TranslateMenu(h);
1362 * @brief Translate dialog to current WinMerge GUI language
1364 void CMergeApp::TranslateDialog(HWND h) const
1366 CWnd *pWnd = CWnd::FromHandle(h);
1367 pWnd->SetFont(const_cast<CFont *>(&m_fontGUI));
1368 pWnd->SendMessageToDescendants(WM_SETFONT, (WPARAM)m_fontGUI.m_hObject, MAKELPARAM(FALSE, 0), TRUE);
1370 m_pLangDlg->TranslateDialog(h);
1374 * @brief Load string and translate to current WinMerge GUI language
1376 String CMergeApp::LoadString(UINT id) const
1378 return m_pLangDlg->LoadString(id);
1381 bool CMergeApp::TranslateString(const std::string& str, String& translated_str) const
1383 return m_pLangDlg->TranslateString(str, translated_str);
1387 * @brief Load dialog caption and translate to current WinMerge GUI language
1389 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1391 return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
1395 * @brief Adds specified file to the recent projects list.
1396 * @param [in] sPathName Path to project file
1398 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
1400 // sPathName will be added to the top of the MRU list.
1401 // If sPathName already exists in the MRU list, it will be moved to the top
1402 if (m_pRecentFileList != nullptr) {
1403 m_pRecentFileList->Add(sPathName);
1404 m_pRecentFileList->WriteList();
1408 void CMergeApp::SetupTempPath()
1410 String instTemp = env::GetPerInstanceString(TempFolderPrefix);
1411 if (GetOptionsMgr()->GetBool(OPT_USE_SYSTEM_TEMP_PATH))
1412 env::SetTemporaryPath(paths::ConcatPath(env::GetSystemTempPath(), instTemp));
1414 env::SetTemporaryPath(paths::ConcatPath(GetOptionsMgr()->GetString(OPT_CUSTOM_TEMP_PATH), instTemp));
1418 * @brief Handles menu selection from recent projects list
1419 * @param [in] nID Menu ID of the selected item
1421 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
1423 return LoadAndOpenProjectFile(static_cast<const TCHAR *>(m_pRecentFileList->m_arrNames[nID-ID_FILE_PROJECT_MRU_FIRST]));
1427 * @brief Return if doc is in Merging/Editing mode
1429 bool CMergeApp::GetMergingMode() const
1431 return m_bMergingMode;
1435 * @brief Set doc to Merging/Editing mode
1437 void CMergeApp::SetMergingMode(bool bMergingMode)
1439 m_bMergingMode = bMergingMode;
1440 GetOptionsMgr()->SaveOption(OPT_MERGE_MODE, m_bMergingMode);
1444 * @brief Switch Merging/Editing mode and update
1445 * buffer read-only states accordingly
1447 void CMergeApp::OnMergingMode()
1449 bool bMergingMode = GetMergingMode();
1452 LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN, IDS_MERGE_MODE);
1453 SetMergingMode(!bMergingMode);
1457 * @brief Update Menuitem for Merging Mode
1459 void CMergeApp::OnUpdateMergingMode(CCmdUI* pCmdUI)
1461 pCmdUI->Enable(true);
1462 pCmdUI->SetCheck(GetMergingMode());
1466 * @brief Update MergingMode UI in statusbar
1468 void CMergeApp::OnUpdateMergingStatus(CCmdUI *pCmdUI)
1470 String text = theApp.LoadString(IDS_MERGEMODE_MERGING);
1471 pCmdUI->SetText(text.c_str());
1472 pCmdUI->Enable(GetMergingMode());
1475 UINT CMergeApp::GetProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nDefault)
1477 COptionsMgr *pOptions = GetOptionsMgr();
1478 String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
1479 if (!pOptions->Get(name).IsInt())
1480 pOptions->InitOption(name, nDefault);
1481 return pOptions->GetInt(name);
1484 BOOL CMergeApp::WriteProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nValue)
1486 COptionsMgr *pOptions = GetOptionsMgr();
1487 String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
1488 if (!pOptions->Get(name).IsInt())
1489 pOptions->InitOption(name, nValue);
1490 return pOptions->SaveOption(name, nValue) == COption::OPT_OK;
1493 CString CMergeApp::GetProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszDefault)
1495 COptionsMgr *pOptions = GetOptionsMgr();
1496 String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
1497 if (!pOptions->Get(name).IsString())
1498 pOptions->InitOption(name, lpszDefault ? lpszDefault : _T(""));
1499 return pOptions->GetString(name).c_str();
1502 BOOL CMergeApp::WriteProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue)
1504 COptionsMgr *pOptions = GetOptionsMgr();
1505 if (lpszEntry != nullptr)
1507 String name = strutils::format(_T("%s/%s"), lpszSection, lpszEntry);
1508 if (!pOptions->Get(name).IsString())
1509 pOptions->InitOption(name, lpszValue ? lpszValue : _T(""));
1510 return pOptions->SaveOption(name, lpszValue ? lpszValue : _T("")) == COption::OPT_OK;
1514 String name = strutils::format(_T("%s/"), lpszSection);
1515 pOptions->RemoveOption(name);