OSDN Git Service

Merge pull request #101 from GreyMerlin/master
[winmerge-jp/winmerge-jp.git] / Src / Merge.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
6 //    This program is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program; if not, write to the Free Software
18 //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /** 
22  * @file  Merge.cpp
23  *
24  * @brief Defines the class behaviors for the application.
25  *
26  */
27
28 #include "stdafx.h"
29 #include "Merge.h"
30 #include "Constants.h"
31 #include "UnicodeString.h"
32 #include "unicoder.h"
33 #include "Environment.h"
34 #include "OptionsMgr.h"
35 #include "OptionsInit.h"
36 #include "RegOptionsMgr.h"
37 #include "OpenDoc.h"
38 #include "OpenFrm.h"
39 #include "OpenView.h"
40 #include "HexMergeDoc.h"
41 #include "HexMergeFrm.h"
42 #include "HexMergeView.h"
43 #include "AboutDlg.h"
44 #include "MainFrm.h"
45 #include "ChildFrm.h"
46 #include "DirFrame.h"
47 #include "MergeDoc.h"
48 #include "DirDoc.h"
49 #include "DirView.h"
50 #include "PropBackups.h"
51 #include "FileOrFolderSelect.h"
52 #include "paths.h"
53 #include "FileFilterHelper.h"
54 #include "LineFiltersList.h"
55 #include "FilterCommentsManager.h"
56 #include "SyntaxColors.h"
57 #include "CCrystalTextMarkers.h"
58 #include "OptionsSyntaxColors.h"
59 #include "Plugins.h"
60 #include "ProjectFile.h"
61 #include "MergeEditSplitterView.h"
62 #include "LanguageSelect.h"
63 #include "OptionsDef.h"
64 #include "MergeCmdLineInfo.h"
65 #include "ConflictFileParser.h"
66 #include "JumpList.h"
67 #include "stringdiffs.h"
68 #include "TFile.h"
69 #include "paths.h"
70 #include "CompareStats.h"
71 #include "TestMain.h"
72 #include "charsets.h" // For shutdown cleanup
73
74 #ifdef _DEBUG
75 #define new DEBUG_NEW
76 #endif
77
78 /** @brief Location for command line help to open. */
79 static const TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
80
81 /** @brief Backup file extension. */
82 static const TCHAR BACKUP_FILE_EXT[] = _T("bak");
83
84 /////////////////////////////////////////////////////////////////////////////
85 // CMergeApp
86
87 BEGIN_MESSAGE_MAP(CMergeApp, CWinApp)
88         //{{AFX_MSG_MAP(CMergeApp)
89         ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
90         ON_COMMAND(ID_HELP, OnHelp)
91         ON_COMMAND_EX_RANGE(ID_FILE_PROJECT_MRU_FIRST, ID_FILE_PROJECT_MRU_LAST, OnOpenRecentFile)
92         ON_UPDATE_COMMAND_UI(ID_FILE_PROJECT_MRU_FIRST, CWinApp::OnUpdateRecentFileMenu)
93         ON_COMMAND(ID_FILE_MERGINGMODE, OnMergingMode)
94         ON_UPDATE_COMMAND_UI(ID_FILE_MERGINGMODE, OnUpdateMergingMode)
95         ON_UPDATE_COMMAND_UI(ID_STATUS_MERGINGMODE, OnUpdateMergingStatus)
96         //}}AFX_MSG_MAP
97         // Standard file based document commands
98         //ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
99         //ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
100         // Standard print setup command
101         ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
102 END_MESSAGE_MAP()
103
104 /////////////////////////////////////////////////////////////////////////////
105 // CMergeApp construction
106
107 CMergeApp::CMergeApp() :
108   m_bNeedIdleTimer(false)
109 , m_pOpenTemplate(nullptr)
110 , m_pDiffTemplate(nullptr)
111 , m_pHexMergeTemplate(nullptr)
112 , m_pDirTemplate(nullptr)
113 , m_mainThreadScripts(nullptr)
114 , m_nLastCompareResult(0)
115 , m_bNonInteractive(false)
116 , m_pOptions(new CRegOptionsMgr())
117 , m_pGlobalFileFilter(new FileFilterHelper())
118 , m_nActiveOperations(0)
119 , m_pLangDlg(new CLanguageSelect())
120 , m_bEscShutdown(false)
121 , m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
122 , m_pLineFilters(new LineFiltersList())
123 , m_pFilterCommentsManager(new FilterCommentsManager())
124 , m_pSyntaxColors(new SyntaxColors())
125 , m_pMarkers(new CCrystalTextMarkers())
126 , m_bMergingMode(false)
127 {
128         // add construction code here,
129         // Place all significant initialization in InitInstance
130 }
131
132 CMergeApp::~CMergeApp()
133 {
134         strdiff::Close();
135 }
136 /////////////////////////////////////////////////////////////////////////////
137 // The one and only CMergeApp object
138
139 CMergeApp theApp;
140
141 /////////////////////////////////////////////////////////////////////////////
142 // CMergeApp initialization
143
144 /**
145  * @brief Initialize WinMerge application instance.
146  * @return TRUE if application initialization succeeds (and we'll run it),
147  *   FALSE if something failed and we exit the instance.
148  * @todo We could handle these failure situations more gratefully, i.e. show
149  *  at least some error message to the user..
150  */
151 BOOL CMergeApp::InitInstance()
152 {
153         // Prevents DLL hijacking
154         HMODULE hLibrary = GetModuleHandle(_T("kernel32.dll"));
155         BOOL (WINAPI *pfnSetSearchPathMode)(DWORD) = (BOOL (WINAPI *)(DWORD))GetProcAddress(hLibrary, "SetSearchPathMode");
156         if (pfnSetSearchPathMode != nullptr)
157                 pfnSetSearchPathMode(0x00000001L /*BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE*/ | 0x00008000L /*BASE_SEARCH_PATH_PERMANENT*/);
158         BOOL (WINAPI *pfnSetDllDirectoryA)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))GetProcAddress(hLibrary, "SetDllDirectoryA");
159         if (pfnSetDllDirectoryA != nullptr)
160                 pfnSetDllDirectoryA("");
161
162         JumpList::SetCurrentProcessExplicitAppUserModelID(L"Thingamahoochie.WinMerge");
163
164         InitCommonControls();    // initialize common control library
165         CWinApp::InitInstance(); // call parent class method
166
167         // Runtime switch so programmer may set this in interactive debugger
168         int dbgmem = 0;
169         if (dbgmem)
170         {
171                 // get current setting
172                 int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
173                 // Keep freed memory blocks in the heap's linked list and mark them as freed
174                 tmpFlag |= _CRTDBG_DELAY_FREE_MEM_DF;
175                 // Call _CrtCheckMemory at every allocation and deallocation request.
176                 // WARNING: This slows down WinMerge *A LOT*
177                 tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
178                 // Set the new state for the flag
179                 _CrtSetDbgFlag( tmpFlag );
180         }
181
182         // CCrystalEdit Drag and Drop functionality needs AfxOleInit.
183         if(!AfxOleInit())
184         {
185                 TRACE(_T("AfxOleInitFailed. OLE functionality disabled"));
186         }
187
188         // Standard initialization
189         // If you are not using these features and wish to reduce the size
190         //  of your final executable, you should remove from the following
191         //  the specific initialization routines you do not need.
192
193         // Revoke the standard OLE Message Filter to avoid drawing frame while loading files.
194         COleMessageFilter* pOldFilter = AfxOleGetMessageFilter();
195         pOldFilter->Revoke();
196
197         // Load registry keys from WinMerge.reg if existing WinMerge.reg
198         env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
199
200         // Parse command-line arguments.
201         MergeCmdLineInfo cmdInfo(GetCommandLine());
202         if (cmdInfo.m_bNoPrefs)
203                 m_pOptions->SetSerializing(false); // Turn off serializing to registry.
204
205         Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
206         ApplyCommandLineConfigOptions(cmdInfo);
207         if (cmdInfo.m_sErrorMessages.size() > 0)
208         {
209                 if (AttachConsole(static_cast<DWORD>(-1)))
210                 {
211                         DWORD dwWritten;
212                         for (auto& msg : cmdInfo.m_sErrorMessages)
213                         {
214                                 String line = _T("WinMerge: ") + msg + _T("\n");
215                                 WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), line.c_str(), static_cast<DWORD>(line.length()), &dwWritten, nullptr);
216                         }
217                         FreeConsole();
218                 }
219         }
220
221         // Initialize temp folder
222         SetupTempPath();
223
224         // If paths were given to commandline we consider this being an invoke from
225         // commandline (from other application, shellextension etc).
226         bool bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
227
228         // WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
229         // This is the name of the company of the original author (Dean Grimm)
230         SetRegistryKey(_T("Thingamahoochie"));
231
232         bool bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
233                 (true == cmdInfo.m_bSingleInstance);
234
235         // Create exclusion mutex name
236         TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
237         DWORD dwLengthNeeded;
238         GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, 
239                 szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
240         TCHAR szMutexName[MAX_PATH + 40];
241         // Combine window class name and desktop name to form a unique mutex name.
242         // As the window class name is decorated to distinguish between ANSI and
243         // UNICODE build, so will be the mutex name.
244         wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
245         HANDLE hMutex = CreateMutex(nullptr, FALSE, szMutexName);
246         if (hMutex != nullptr)
247                 WaitForSingleObject(hMutex, INFINITE);
248         if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
249         {
250                 // Activate previous instance and send commandline to it
251                 HWND hWnd = FindWindow(CMainFrame::szClassName, nullptr);
252                 if (hWnd != nullptr)
253                 {
254                         if (IsIconic(hWnd))
255                                 ShowWindow(hWnd, SW_RESTORE);
256                         SetForegroundWindow(GetLastActivePopup(hWnd));
257                         LPTSTR cmdLine = GetCommandLine();
258                         COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
259                         if (::SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
260                         {
261                                 ReleaseMutex(hMutex);
262                                 CloseHandle(hMutex);
263                                 return FALSE;
264                         }
265                 }
266         }
267
268         LoadStdProfileSettings(GetOptionsMgr()->GetInt(OPT_MRU_MAX));  // Load standard INI file options (including MRU)
269
270         InitializeFileFilters();
271
272         // Read last used filter from registry
273         // If filter fails to set, reset to default
274         const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
275         bool bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
276         if (!bFilterSet)
277         {
278                 String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
279                 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
280         }
281
282         charsets_init();
283         UpdateCodepageModule();
284
285         FileTransform::g_UnpackerMode = static_cast<PLUGIN_MODE>(theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL));
286         FileTransform::g_PredifferMode = static_cast<PLUGIN_MODE>(theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL));
287
288         NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
289         if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
290         {
291                 const int lfHeight = -MulDiv(9, CClientDC(CWnd::GetDesktopWindow()).GetDeviceCaps(LOGPIXELSY), 72);
292                 if (abs(ncm.lfMenuFont.lfHeight) > abs(lfHeight))
293                         ncm.lfMenuFont.lfHeight = lfHeight;
294                 if (wcscmp(ncm.lfMenuFont.lfFaceName, L"Meiryo") == 0 || wcscmp(ncm.lfMenuFont.lfFaceName, L"\U000030e1\U000030a4\U000030ea\U000030aa"/* "Meiryo" in Japanese */) == 0)
295                         wcscpy_s(ncm.lfMenuFont.lfFaceName, L"Meiryo UI");
296                 m_fontGUI.CreateFontIndirect(&ncm.lfMenuFont);
297         }
298
299         if (m_pSyntaxColors != nullptr)
300                 Options::SyntaxColors::Load(GetOptionsMgr(), m_pSyntaxColors.get());
301
302         if (m_pMarkers != nullptr)
303                 m_pMarkers->LoadFromRegistry();
304
305         if (m_pLineFilters != nullptr)
306                 m_pLineFilters->Initialize(GetOptionsMgr());
307
308         // If there are no filters loaded, and there is filter string in previous
309         // option string, import old filters to new place.
310         if (m_pLineFilters->GetCount() == 0)
311         {
312                 String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
313                 if (!oldFilter.empty())
314                         m_pLineFilters->Import(oldFilter);
315         }
316
317         // Check if filter folder is set, and create it if not
318         String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
319         if (pathMyFolders.empty())
320         {
321                 // No filter path, set it to default and make sure it exists.
322                 pathMyFolders = GetOptionsMgr()->GetDefault<String>(OPT_FILTER_USERPATH);
323                 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathMyFolders);
324                 theApp.m_pGlobalFileFilter->SetUserFilterPath(pathMyFolders);
325         }
326         if (!paths::CreateIfNeeded(pathMyFolders))
327         {
328                 // Failed to create a folder, check it didn't already
329                 // exist.
330                 DWORD errCode = GetLastError();
331                 if (errCode != ERROR_ALREADY_EXISTS)
332                 {
333                         // Failed to create a folder for filters, fallback to
334                         // "My Documents"-folder. It is not worth the trouble to
335                         // bother user about this or user more clever solutions.
336                         GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, env::GetMyDocuments());
337                 }
338         }
339
340         strdiff::Init(); // String diff init
341         strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
342
343         m_bMergingMode = GetOptionsMgr()->GetBool(OPT_MERGE_MODE);
344
345         // Initialize i18n (multiple language) support
346
347         m_pLangDlg->InitializeLanguage((WORD)GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
348
349         m_mainThreadScripts = new CAssureScriptsForThread;
350
351         // Register the application's document templates.  Document templates
352         //  serve as the connection between documents, frame windows and views.
353
354         // Open view
355         m_pOpenTemplate = new CMultiDocTemplate(
356                 IDR_MAINFRAME,
357                 RUNTIME_CLASS(COpenDoc),
358                 RUNTIME_CLASS(COpenFrame), // custom MDI child frame
359                 RUNTIME_CLASS(COpenView));
360         AddDocTemplate(m_pOpenTemplate);
361
362         // Merge Edit view
363         m_pDiffTemplate = new CMultiDocTemplate(
364                 IDR_MERGEDOCTYPE,
365                 RUNTIME_CLASS(CMergeDoc),
366                 RUNTIME_CLASS(CChildFrame), // custom MDI child frame
367                 RUNTIME_CLASS(CMergeEditSplitterView));
368         AddDocTemplate(m_pDiffTemplate);
369
370         // Merge Edit view
371         m_pHexMergeTemplate = new CMultiDocTemplate(
372                 IDR_MERGEDOCTYPE,
373                 RUNTIME_CLASS(CHexMergeDoc),
374                 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
375                 RUNTIME_CLASS(CHexMergeView));
376         AddDocTemplate(m_pHexMergeTemplate);
377
378         // Directory view
379         m_pDirTemplate = new CMultiDocTemplate(
380                 IDR_DIRDOCTYPE,
381                 RUNTIME_CLASS(CDirDoc),
382                 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
383                 RUNTIME_CLASS(CDirView));
384         AddDocTemplate(m_pDirTemplate);
385
386         // create main MDI Frame window
387         CMainFrame* pMainFrame = new CMainFrame;
388         if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
389         {
390                 if (hMutex != nullptr)
391                 {
392                         ReleaseMutex(hMutex);
393                         CloseHandle(hMutex);
394                 }
395                 return FALSE;
396         }
397         m_pMainWnd = pMainFrame;
398
399         // Init menus -- hMenuDefault is for MainFrame
400         pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
401
402         // Set the menu
403         // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
404         CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
405         pMainFrame->MDISetMenu(pNewMenu, nullptr);
406
407         // The main window has been initialized, so activate and update it.
408         pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
409         pMainFrame->UpdateWindow();
410
411         // Since this function actually opens paths for compare it must be
412         // called after initializing CMainFrame!
413         bool bContinue = true;
414         if (!ParseArgsAndDoOpen(cmdInfo, pMainFrame) && bCommandLineInvoke)
415                 bContinue = false;
416
417         if (hMutex != nullptr)
418                 ReleaseMutex(hMutex);
419
420         // If user wants to cancel the compare, close WinMerge
421         if (!bContinue)
422         {
423                 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
424         }
425
426 #ifdef TEST_WINMERGE
427         WinMergeTest::TestAll();
428 #endif
429
430         return bContinue;
431 }
432
433 static void OpenContributersFile(int&)
434 {
435         theApp.OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
436 }
437
438 // App command to run the dialog
439 void CMergeApp::OnAppAbout()
440 {
441         CAboutDlg aboutDlg;
442         aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
443         aboutDlg.DoModal();
444         aboutDlg.m_onclick_contributers.clear();
445 }
446
447 /////////////////////////////////////////////////////////////////////////////
448 // CMergeApp commands
449
450 /**
451  * @brief Called when application is about to exit.
452  * This functions is called when application is exiting, so this is
453  * good place to do cleanups.
454  * @return Application's exit value (returned from WinMain()).
455  */
456 int CMergeApp::ExitInstance() 
457 {
458         charsets_cleanup();
459
460         //  Save registry keys if existing WinMerge.reg
461         env::SaveRegistryToFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")), RegDir);
462
463         // Remove tempfolder
464         const String temp = env::GetTemporaryPath();
465         ClearTempfolder(temp);
466
467         // Cleanup left over tempfiles from previous instances.
468         // Normally this should not neet to do anything - but if for some reason
469         // WinMerge did not delete temp files this makes sure they are removed.
470         CleanupWMtemp();
471
472         delete m_mainThreadScripts;
473         CWinApp::ExitInstance();
474         return 0;
475 }
476
477 int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
478 {
479         // This is a convenient point for breakpointing !!!
480
481         // Create a handle to store the parent window of the message box.
482         CWnd* pParentWnd = CWnd::GetActiveWindow();
483         
484         // Check whether an active window was retrieved successfully.
485         if ( pParentWnd == nullptr )
486         {
487                 // Try to retrieve a handle to the last active popup.
488                 CWnd * mainwnd = GetMainWnd();
489                 if (mainwnd != nullptr)
490                         pParentWnd = mainwnd->GetLastActivePopup();
491         }
492
493         // Use our own message box implementation, which adds the
494         // do not show again checkbox, and implements it on subsequent calls
495         // (if caller set the style)
496
497         if (m_bNonInteractive)
498                 return IDCANCEL;
499
500         // Create the message box dialog.
501         CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
502                 nIDPrompt);
503         
504         if (m_pMainWnd->IsIconic())
505                 m_pMainWnd->ShowWindow(SW_RESTORE);
506
507         // Display the message box dialog and return the result.
508         return static_cast<int>(dlgMessage.DoModal());
509 }
510
511 /** 
512  * @brief Set flag so that application will broadcast notification at next
513  * idle time (via WM_TIMER id=IDLE_TIMER)
514  */
515 void CMergeApp::SetNeedIdleTimer()
516 {
517         m_bNeedIdleTimer = true; 
518 }
519
520 bool CMergeApp::IsReallyIdle() const
521 {
522         bool idle = true;
523         POSITION pos = m_pDirTemplate->GetFirstDocPosition();
524         while (pos != nullptr)
525         {
526                 CDirDoc *pDirDoc = static_cast<CDirDoc *>(m_pDirTemplate->GetNextDoc(pos));
527                 if (const CompareStats *pCompareStats = pDirDoc->GetCompareStats())
528                 {
529                         if (!pCompareStats->IsCompareDone() || pDirDoc->GetGeneratingReport())
530                                 idle = false;
531                 }
532         }
533     return idle;
534 }
535
536 BOOL CMergeApp::OnIdle(LONG lCount) 
537 {
538         if (CWinApp::OnIdle(lCount))
539                 return TRUE;
540
541         // If anyone has requested notification when next idle occurs, send it
542         if (m_bNeedIdleTimer)
543         {
544                 m_bNeedIdleTimer = false;
545                 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
546         }
547
548         if (m_bNonInteractive && IsReallyIdle())
549                 m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
550
551         return FALSE;
552 }
553
554 /**
555  * @brief Load any known file filters.
556  *
557  * This function loads filter files from paths we know contain them.
558  * @note User's filter location may not be set yet.
559  */
560 void CMergeApp::InitializeFileFilters()
561 {
562         CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
563
564         if (!filterPath.IsEmpty())
565         {
566                 m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
567         }
568         m_pGlobalFileFilter->LoadAllFileFilters();
569 }
570
571 void CMergeApp::ApplyCommandLineConfigOptions(MergeCmdLineInfo& cmdInfo)
572 {
573         if (cmdInfo.m_bNoPrefs)
574                 m_pOptions->SetSerializing(false); // Turn off serializing to registry.
575
576         for (const auto& it : cmdInfo.m_Options)
577         {
578                 if (m_pOptions->Set(it.first, it.second) == COption::OPT_NOTFOUND)
579                 {
580                         String longname = m_pOptions->ExpandShortName(it.first);
581                         if (!longname.empty())
582                         {
583                                 m_pOptions->Set(longname, it.second);
584                         }
585                         else
586                         {
587                                 cmdInfo.m_sErrorMessages.push_back(strutils::format_string1(_T("Invalid key '%1' specified in /config option"), it.first));
588                         }
589                 }
590         }
591 }
592
593 /** @brief Read command line arguments and open files for comparison.
594  *
595  * The name of the function is a legacy code from the time that this function
596  * actually parsed the command line. Today the parsing is done using the
597  * MergeCmdLineInfo class.
598  * @param [in] cmdInfo Commandline parameters info.
599  * @param [in] pMainFrame Pointer to application main frame.
600  * @return `true` if we opened the compare, `false` if the compare was canceled.
601  */
602 bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
603 {
604         bool bCompared = false;
605         String strDesc[3];
606         m_bNonInteractive = cmdInfo.m_bNonInteractive;
607
608         // Set the global file filter.
609         if (!cmdInfo.m_sFileFilter.empty())
610         {
611                 m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter);
612         }
613
614         // Set codepage.
615         if (cmdInfo.m_nCodepage)
616         {
617                 UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
618         }
619
620         // Unless the user has requested to see WinMerge's usage open files for
621         // comparison.
622         if (cmdInfo.m_bShowUsage)
623         {
624                 ShowHelp(CommandLineHelpLocation);
625         }
626         else
627         {
628                 // Set the required information we need from the command line:
629
630                 m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
631                 m_bEscShutdown = cmdInfo.m_bEscShutdown;
632
633                 m_strSaveAsPath = cmdInfo.m_sOutputpath;
634
635                 strDesc[0] = cmdInfo.m_sLeftDesc;
636                 if (cmdInfo.m_Files.GetSize() < 3)
637                 {
638                         strDesc[1] = cmdInfo.m_sRightDesc;
639                 }
640                 else
641                 {
642                         strDesc[1] = cmdInfo.m_sMiddleDesc;
643                         strDesc[2] = cmdInfo.m_sRightDesc;
644                 }
645
646                 if (cmdInfo.m_Files.GetSize() > 2)
647                 {
648                         cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
649                         cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
650                         cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
651                         DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
652                         bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
653                                 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
654                                 cmdInfo.m_sPreDiffer);
655                 }
656                 else if (cmdInfo.m_Files.GetSize() > 1)
657                 {
658                         DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
659                         bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
660                                 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
661                                 cmdInfo.m_sPreDiffer);
662                 }
663                 else if (cmdInfo.m_Files.GetSize() == 1)
664                 {
665                         String sFilepath = cmdInfo.m_Files[0];
666                         if (IsProjectFile(sFilepath))
667                         {
668                                 bCompared = LoadAndOpenProjectFile(sFilepath);
669                         }
670                         else if (IsConflictFile(sFilepath))
671                         {
672                                 //For a conflict file, load the descriptions in their respective positions:  (they will be reordered as needed)
673                                 strDesc[0] = cmdInfo.m_sLeftDesc;
674                                 strDesc[1] = cmdInfo.m_sMiddleDesc;
675                                 strDesc[2] = cmdInfo.m_sRightDesc;
676                                 bCompared = pMainFrame->DoOpenConflict(sFilepath, strDesc);
677                         }
678                         else
679                         {
680                                 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
681                                 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
682                                         dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr, 
683                                         cmdInfo.m_sPreDiffer);
684                         }
685                 }
686                 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
687                 {
688                         bool showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
689                         if (showFiles)
690                                 pMainFrame->DoFileOpen();
691                 }
692         }
693         return bCompared;
694 }
695
696 void CMergeApp::UpdateDefaultCodepage(int cpDefaultMode, int cpCustomCodepage)
697 {
698         int wLangId;
699
700         switch (cpDefaultMode)
701         {
702                 case 0:
703                         ucr::setDefaultCodepage(GetACP());
704                         break;
705                 case 1:
706                         TCHAR buff[32];
707                         wLangId = GetLangId();
708                         if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
709                                 ucr::setDefaultCodepage(_ttol(buff));
710                         else
711                                 ucr::setDefaultCodepage(GetACP());
712                         break;
713                 case 2:
714                         ucr::setDefaultCodepage(cpCustomCodepage);
715                         break;
716                 default:
717                         // no other valid option
718                         assert (false);
719                         ucr::setDefaultCodepage(GetACP());
720         }
721 }
722
723 /**
724  * @brief Send current option settings into codepage module
725  */
726 void CMergeApp::UpdateCodepageModule()
727 {
728         // Get current codepage settings from the options module
729         // and push them into the codepage module
730         UpdateDefaultCodepage(GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_MODE), GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_CUSTOM));
731 }
732
733 /** @brief Open help from mainframe when user presses F1*/
734 void CMergeApp::OnHelp()
735 {
736         ShowHelp();
737 }
738
739 /**
740  * @brief Open given file to external editor specified in options.
741  * @param [in] file Full path to file to open.
742  *
743  * Opens file to defined (in Options/system), Notepad by default,
744  * external editor. Path is decorated with quotation marks if needed
745  * (contains spaces). Also '$file' in editor path is replaced by
746  * filename to open.
747  * @param [in] file Full path to file to open.
748  * @param [in] nLineNumber Line number to go to.
749  */
750 void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
751 {
752         String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
753         String sFile(file);
754         strutils::replace(sCmd, _T("$linenum"), strutils::to_str(nLineNumber));
755
756         size_t nIndex = sCmd.find(_T("$file"));
757         if (nIndex != String::npos)
758         {
759                 sFile.insert(0, _T("\""));
760                 strutils::replace(sCmd, _T("$file"), sFile);
761                 nIndex = sCmd.find(' ', nIndex + sFile.length());
762                 if (nIndex != String::npos)
763                         sCmd.insert(nIndex, _T("\""));
764                 else
765                         sCmd += '"';
766         }
767         else
768         {
769                 sCmd += _T(" \"");
770                 sCmd += sFile;
771                 sCmd += _T("\"");
772         }
773
774         bool retVal = false;
775         STARTUPINFO stInfo = { sizeof STARTUPINFO };
776         PROCESS_INFORMATION processInfo;
777
778         retVal = !!CreateProcess(nullptr, (LPTSTR)sCmd.c_str(),
779                 nullptr, nullptr, FALSE, CREATE_DEFAULT_ERROR_MODE, nullptr, nullptr,
780                 &stInfo, &processInfo);
781
782         if (!retVal)
783         {
784                 // Error invoking external editor
785                 String msg = strutils::format_string1(_("Failed to execute external editor: %1"), sCmd);
786                 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
787         }
788         else
789         {
790                 CloseHandle(processInfo.hThread);
791                 CloseHandle(processInfo.hProcess);
792         }
793 }
794
795 /**
796  * @brief Open file, if it exists, else open url
797  */
798 void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
799 {
800         if (paths::DoesPathExist(szFile) == paths::IS_EXISTING_FILE)
801                 ShellExecute(nullptr, _T("open"), _T("notepad.exe"), szFile, nullptr, SW_SHOWNORMAL);
802         else
803                 ShellExecute(nullptr, _T("open"), szUrl, nullptr, nullptr, SW_SHOWNORMAL);
804 }
805
806 /**
807  * @brief Show Help - this is for opening help from outside mainframe.
808  * @param [in] helpLocation Location inside help, if `nullptr` main help is opened.
809  */
810 void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= nullptr*/)
811 {
812         String sPath = env::GetProgPath();
813         LANGID LangId = GetLangId();
814         sPath = paths::ConcatPath(sPath, DocsPath);
815         if (helpLocation == nullptr)
816         {
817                 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
818                         ::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOC, NULL);
819                 else
820                         ShellExecute(nullptr, _T("open"), DocsURL, nullptr, nullptr, SW_SHOWNORMAL);
821         }
822         else
823         {
824                 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
825                 {
826                         sPath += helpLocation;
827                         ::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
828                 }
829         }
830 }
831
832 /**
833  * @brief Creates backup before file is saved or copied over.
834  * This function handles formatting correct path and filename for
835  * backup file. Formatting is done based on several options available
836  * for users in Options/Backups dialog. After path is formatted, file
837  * is simply just copied. Not much error checking, just if copying
838  * succeeded or failed.
839  * @param [in] bFolder Are we creating backup in folder compare?
840  * @param [in] pszPath Full path to file to backup.
841  * @return `true` if backup succeeds, or isn't just done.
842  */
843 bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath)
844 {
845         // If user doesn't want to backups in folder compare, return
846         // success so operations don't abort.
847         if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
848                 return true;
849         // Likewise if user doesn't want backups in file compare
850         else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
851                 return true;
852
853         // create backup copy of file if destination file exists
854         if (paths::DoesPathExist(pszPath) == paths::IS_EXISTING_FILE)
855         {
856                 String bakPath;
857                 String path;
858                 String filename;
859                 String ext;
860         
861                 paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
862
863                 // Determine backup folder
864                 if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
865                         PropBackups::FOLDER_ORIGINAL)
866                 {
867                         // Put backups to same folder than original file
868                         bakPath = path;
869                 }
870                 else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
871                         PropBackups::FOLDER_GLOBAL)
872                 {
873                         // Put backups to global folder defined in options
874                         bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
875                         if (bakPath.empty())
876                                 bakPath = path;
877                         else
878                                 bakPath = paths::GetLongPath(bakPath);
879                 }
880                 else
881                 {
882                         _RPTF0(_CRT_ERROR, "Unknown backup location!");
883                 }
884
885                 bool success = false;
886                 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
887                 {
888                         // Don't add dot if there is no existing extension
889                         if (ext.size() > 0)
890                                 ext += _T(".");
891                         ext += BACKUP_FILE_EXT;
892                 }
893
894                 // Append time to filename if wanted so
895                 // NOTE just adds timestamp at the moment as I couldn't figure out
896                 // nice way to add a real time (invalid chars etc).
897                 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
898                 {
899                         struct tm tm;
900                         time_t curtime = 0;
901                         time(&curtime);
902                         ::localtime_s(&tm, &curtime);
903                         CString timestr;
904                         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);
905                         filename += _T("-");
906                         filename += timestr;
907                 }
908
909                 // Append filename and extension (+ optional .bak) to path
910                 if ((bakPath.length() + filename.length() + ext.length())
911                         < MAX_PATH_FULL)
912                 {
913                         success = true;
914                         bakPath = paths::ConcatPath(bakPath, filename);
915                         bakPath += _T(".");
916                         bakPath += ext;
917                 }
918
919                 if (success)
920                 {
921                         success = !!CopyFileW(TFile(pszPath).wpath().c_str(), TFile(bakPath).wpath().c_str(), FALSE);
922                 }
923                 
924                 if (!success)
925                 {
926                         String msg = strutils::format_string1(
927                                 _("Unable to backup original file:\n%1\n\nContinue anyway?"),
928                                 pszPath);
929                         if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
930                                 return false;
931                 }
932                 return true;
933         }
934
935         // we got here because we're either not backing up of there was nothing to backup
936         return true;
937 }
938
939 /**
940  * @brief Checks if path (file/folder) is read-only and asks overwriting it.
941  *
942  * @param strSavePath [in,out] Path where to save (file or folder)
943  * @param bMultiFile [in] Single file or multiple files/folder
944  * @param bApplyToAll [in,out] Apply last user selection for all items?
945  * @return Users selection:
946  * - IDOK: Item was not readonly, no actions
947  * - IDYES/IDYESTOALL: Overwrite readonly item
948  * - IDNO: User selected new filename (single file) or user wants to skip
949  * - IDCANCEL: Cancel operation
950  * @sa CMainFrame::SyncFileToVCS()
951  * @sa CMergeDoc::DoSave()
952  */
953 int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
954                 bool &bApplyToAll)
955 {
956         CFileStatus status;
957         int nRetVal = IDOK;
958         bool bFileRO = false;
959         bool bFileExists = false;
960         String s;
961         String str;
962         CString title;
963
964         if (!strSavePath.empty())
965         {
966                 try
967                 {
968                         TFile file(strSavePath);
969                         bFileExists = file.exists();
970                         if (bFileExists)
971                                 bFileRO = !file.canWrite();
972                 }
973                 catch (...)
974                 {
975                 }
976         }
977
978         if (bFileExists && bFileRO)
979         {
980                 UINT userChoice = 0;
981                 
982                 // Don't ask again if its already asked
983                 if (bApplyToAll)
984                         userChoice = IDYES;
985                 else
986                 {
987                         // Prompt for user choice
988                         if (bMultiFile)
989                         {
990                                 // Multiple files or folder
991                                 str = strutils::format_string1(_("%1\nis marked read-only. Would you like to override the read-only item?"), strSavePath);
992                                 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
993                                                 MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
994                                                 MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
995                         }
996                         else
997                         {
998                                 // Single file
999                                 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);
1000                                 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1001                                                 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
1002                                                 IDS_SAVEREADONLY_FMT);
1003                         }
1004                 }
1005                 switch (userChoice)
1006                 {
1007                 // Overwrite read-only file
1008                 case IDYESTOALL:
1009                         bApplyToAll = true;  // Don't ask again (no break here)
1010                 case IDYES:
1011                         CFile::GetStatus(strSavePath.c_str(), status);
1012                         status.m_mtime = 0;             // Avoid unwanted changes
1013                         status.m_attribute &= ~CFile::readOnly;
1014                         CFile::SetStatus(strSavePath.c_str(), status);
1015                         nRetVal = IDYES;
1016                         break;
1017                 
1018                 // Save to new filename (single) /skip this item (multiple)
1019                 case IDNO:
1020                         if (!bMultiFile)
1021                         {
1022                                 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, strSavePath.c_str()))
1023                                 {
1024                                         strSavePath = s;
1025                                         nRetVal = IDNO;
1026                                 }
1027                                 else
1028                                         nRetVal = IDCANCEL;
1029                         }
1030                         else
1031                                 nRetVal = IDNO;
1032                         break;
1033
1034                 // Cancel saving
1035                 case IDCANCEL:
1036                         nRetVal = IDCANCEL;
1037                         break;
1038                 }
1039         }
1040         return nRetVal;
1041 }
1042
1043 /**
1044  * @brief Is specified file a project file?
1045  * @param [in] filepath Full path to file to check.
1046  * @return true if file is a projectfile.
1047  */
1048 bool CMergeApp::IsProjectFile(const String& filepath) const
1049 {
1050         String ext;
1051         paths::SplitFilename(filepath, nullptr, nullptr, &ext);
1052         if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1053                 return true;
1054         else
1055                 return false;
1056 }
1057
1058 bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
1059 {
1060         if (sProject.empty())
1061                 return false;
1062
1063         try
1064         {
1065         project.Read(sProject);
1066         }
1067         catch (Poco::Exception& e)
1068         {
1069                 String sErr = _("Unknown error attempting to open project file");
1070                 sErr += ucr::toTString(e.displayText());
1071                 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1072                 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1073                 return false;
1074         }
1075
1076         return true;
1077 }
1078
1079 bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &project)
1080 {
1081         try
1082         {
1083                 project.Save(sProject);
1084         }
1085         catch (Poco::Exception& e)
1086         {
1087                 String sErr = _("Unknown error attempting to save project file");
1088                 sErr += ucr::toTString(e.displayText());
1089                 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1090                 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1091                 return false;
1092         }
1093
1094         return true;
1095 }
1096
1097 /** 
1098  * @brief Read project and perform comparison specified
1099  * @param [in] sProject Full path to project file.
1100  * @return `true` if loading project file and starting compare succeeded.
1101  */
1102 bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sReportFile)
1103 {
1104         ProjectFile project;
1105         if (!LoadProjectFile(sProject, project))
1106                 return false;
1107         
1108         PathContext tFiles;
1109         bool bLeftReadOnly = false;
1110         bool bMiddleReadOnly = false;
1111         bool bRightReadOnly = false;
1112         bool bRecursive = false;
1113         project.GetPaths(tFiles, bRecursive);
1114         bLeftReadOnly = project.GetLeftReadOnly();
1115         bMiddleReadOnly = project.GetMiddleReadOnly();
1116         bRightReadOnly = project.GetRightReadOnly();
1117         if (project.HasFilter())
1118         {
1119                 String filter = project.GetFilter();
1120                 filter = strutils::trim_ws(filter);
1121                 m_pGlobalFileFilter->SetFilter(filter);
1122         }
1123         if (project.HasSubfolders())
1124                 bRecursive = project.GetSubfolders() > 0;
1125
1126         DWORD dwFlags[3] = {
1127                 static_cast<DWORD>(tFiles.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1128                 static_cast<DWORD>(tFiles.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1129                 static_cast<DWORD>(tFiles.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
1130         };
1131         if (bLeftReadOnly)
1132                 dwFlags[0] |= FFILEOPEN_READONLY;
1133         if (tFiles.GetSize() == 2)
1134         {
1135                 if (bRightReadOnly)
1136                         dwFlags[1] |= FFILEOPEN_READONLY;
1137         }
1138         else
1139         {
1140                 if (bMiddleReadOnly)
1141                         dwFlags[1] |= FFILEOPEN_READONLY;
1142                 if (bRightReadOnly)
1143                         dwFlags[2] |= FFILEOPEN_READONLY;
1144         }
1145
1146         GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
1147         
1148         bool rtn = GetMainFrame()->DoFileOpen(&tFiles, dwFlags, nullptr, sReportFile, bRecursive);
1149
1150         AddToRecentProjectsMRU(sProject.c_str());
1151         return rtn;
1152 }
1153
1154 /**
1155  * @brief Return windows language ID of current WinMerge GUI language
1156  */
1157 WORD CMergeApp::GetLangId() const
1158 {
1159         return m_pLangDlg->GetLangId();
1160 }
1161
1162 /**
1163  * @brief Lang aware version of CStatusBar::SetIndicators()
1164  */
1165 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
1166 {
1167         m_pLangDlg->SetIndicators(sb, rgid, n);
1168 }
1169
1170 /**
1171  * @brief Translate menu to current WinMerge GUI language
1172  */
1173 void CMergeApp::TranslateMenu(HMENU h) const
1174 {
1175         m_pLangDlg->TranslateMenu(h);
1176 }
1177
1178 /**
1179  * @brief Translate dialog to current WinMerge GUI language
1180  */
1181 void CMergeApp::TranslateDialog(HWND h) const
1182 {
1183         CWnd *pWnd = CWnd::FromHandle(h);
1184         pWnd->SetFont(const_cast<CFont *>(&m_fontGUI));
1185         pWnd->SendMessageToDescendants(WM_SETFONT, (WPARAM)m_fontGUI.m_hObject, MAKELPARAM(FALSE, 0), TRUE);
1186
1187         m_pLangDlg->TranslateDialog(h);
1188 }
1189
1190 /**
1191  * @brief Load string and translate to current WinMerge GUI language
1192  */
1193 String CMergeApp::LoadString(UINT id) const
1194 {
1195         return m_pLangDlg->LoadString(id);
1196 }
1197
1198 bool CMergeApp::TranslateString(const std::string& str, String& translated_str) const
1199 {
1200         return m_pLangDlg->TranslateString(str, translated_str);
1201 }
1202
1203 /**
1204  * @brief Load dialog caption and translate to current WinMerge GUI language
1205  */
1206 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1207 {
1208         return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
1209 }
1210
1211 /**
1212  * @brief Adds specified file to the recent projects list.
1213  * @param [in] sPathName Path to project file
1214  */
1215 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
1216 {
1217         // sPathName will be added to the top of the MRU list. 
1218         // If sPathName already exists in the MRU list, it will be moved to the top
1219         if (m_pRecentFileList != nullptr)    {
1220                 m_pRecentFileList->Add(sPathName);
1221                 m_pRecentFileList->WriteList();
1222         }
1223 }
1224
1225 void CMergeApp::SetupTempPath()
1226 {
1227         String instTemp = env::GetPerInstanceString(TempFolderPrefix);
1228         if (GetOptionsMgr()->GetBool(OPT_USE_SYSTEM_TEMP_PATH))
1229                 env::SetTemporaryPath(paths::ConcatPath(env::GetSystemTempPath(), instTemp));
1230         else
1231                 env::SetTemporaryPath(paths::ConcatPath(GetOptionsMgr()->GetString(OPT_CUSTOM_TEMP_PATH), instTemp));
1232 }
1233
1234 /**
1235  * @brief Handles menu selection from recent projects list
1236  * @param [in] nID Menu ID of the selected item
1237  */
1238 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
1239 {
1240         return LoadAndOpenProjectFile(static_cast<const TCHAR *>(m_pRecentFileList->m_arrNames[nID-ID_FILE_PROJECT_MRU_FIRST]));
1241 }
1242
1243 /**
1244  * @brief Return if doc is in Merging/Editing mode
1245  */
1246 bool CMergeApp::GetMergingMode() const
1247 {
1248         return m_bMergingMode;
1249 }
1250
1251 /**
1252  * @brief Set doc to Merging/Editing mode
1253  */
1254 void CMergeApp::SetMergingMode(bool bMergingMode)
1255 {
1256         m_bMergingMode = bMergingMode;
1257         GetOptionsMgr()->SaveOption(OPT_MERGE_MODE, m_bMergingMode);
1258 }
1259
1260 /**
1261  * @brief Switch Merging/Editing mode and update
1262  * buffer read-only states accordingly
1263  */
1264 void CMergeApp::OnMergingMode()
1265 {
1266         bool bMergingMode = GetMergingMode();
1267
1268         if (!bMergingMode)
1269                 LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN);
1270         SetMergingMode(!bMergingMode);
1271 }
1272
1273 /**
1274  * @brief Update Menuitem for Merging Mode
1275  */
1276 void CMergeApp::OnUpdateMergingMode(CCmdUI* pCmdUI)
1277 {
1278         pCmdUI->Enable(true);
1279         pCmdUI->SetCheck(GetMergingMode());
1280 }
1281
1282 /**
1283  * @brief Update MergingMode UI in statusbar
1284  */
1285 void CMergeApp::OnUpdateMergingStatus(CCmdUI *pCmdUI)
1286 {
1287         String text = theApp.LoadString(IDS_MERGEMODE_MERGING);
1288         pCmdUI->SetText(text.c_str());
1289         pCmdUI->Enable(GetMergingMode());
1290 }