OSDN Git Service

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