OSDN Git Service

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