OSDN Git Service

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