OSDN Git Service

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