OSDN Git Service

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