OSDN Git Service

Added 'Split Vertically' menu item
[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 "Constants.h"
32 #include "UnicodeString.h"
33 #include "Environment.h"
34 #include "OptionsMgr.h"
35 #include "Merge.h"
36 #include "HexMergeDoc.h"
37 #include "HexMergeFrm.h"
38 #include "HexMergeView.h"
39 #include "AboutDlg.h"
40 #include "MainFrm.h"
41 #include "ChildFrm.h"
42 #include "DirFrame.h"
43 #include "MergeDoc.h"
44 #include "DirDoc.h"
45 #include "DirView.h"
46 #include "Splash.h"
47 #include "logfile.h"
48 #include "coretools.h"
49 #include "paths.h"
50 #include "FileFilterHelper.h"
51 #include "Plugins.h"
52 #include "DirScan.h" // for DirScan_InitializeDefaultCodepage
53 #include "ProjectFile.h"
54 #include "MergeEditView.h"
55 #include "LanguageSelect.h"
56 #include "OptionsDef.h"
57 #include "MergeCmdLineInfo.h"
58 #include "ConflictFileParser.h"
59 #include "codepage.h"
60
61 // For shutdown cleanup
62 #include "charsets.h"
63
64 #ifdef _DEBUG
65 #define new DEBUG_NEW
66 #undef THIS_FILE
67 static char THIS_FILE[] = __FILE__;
68 #endif
69
70
71
72 /** @brief Location for command line help to open. */
73 static TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
74
75 // registry dir to WinMerge
76 static CString f_RegDir = _T("Software\\Thingamahoochie\\WinMerge");
77
78
79 /**
80  * @brief Turn STL exceptions into MFC exceptions.
81  * Based on the article "Visual C++ Exception-Handling Instrumentation"
82  * by Eugene Gershnik, published at http://www.drdobbs.com/184416600.
83  * Rethrow fix inspired by http://www.spinics.net/lists/wine/msg05996.html.
84  */
85 namespace Turn_STL_exceptions_into_MFC_exceptions
86 {
87 #       ifndef _STATIC_CPPLIB
88 #       error This hack only works with _STATIC_CPPLIB defined.
89 #       endif
90
91         class CDisguisedSTLException : public CException
92         {
93         private:
94                 std::exception *m_pSTLException;
95         public:
96                 CDisguisedSTLException(std::exception *pSTLException)
97                 : m_pSTLException(pSTLException)
98                 {
99                 }
100                 virtual BOOL GetErrorMessage(LPTSTR lpszError, UINT nMaxError, PUINT)
101                 {
102                         _sntprintf(lpszError, nMaxError, _T("%hs"), m_pSTLException->what());
103                         return TRUE;
104                 }
105         };
106
107         const DWORD CPP_EXCEPTION = 0xE06D7363;
108         const DWORD MS_MAGIC = 0x19930520;
109
110         extern "C" void __stdcall _CxxThrowException(void *pObject, _s__ThrowInfo const *pObjectInfo)
111         {
112                 __declspec(thread) static ULONG_PTR args[3] = { MS_MAGIC, 0, 0 };
113                 if (pObject == NULL)
114                 {
115                         pObject = reinterpret_cast<void *>(args[1]);
116                         pObjectInfo = reinterpret_cast<_s__ThrowInfo const *>(args[2]);
117                 }
118                 else
119                 {
120                         args[1] = (ULONG_PTR)pObject;
121                         args[2] = (ULONG_PTR)pObjectInfo;
122                 }
123                 int i;
124                 if (pObjectInfo->pCatchableTypeArray && (i = pObjectInfo->pCatchableTypeArray->nCatchableTypes))
125                 {
126                         const char *name = typeid(std::exception).raw_name();
127                         if (pObjectInfo->pCatchableTypeArray->arrayOfCatchableTypes[i - 1]->pType->name == name)
128                         {
129                                 throw new CDisguisedSTLException(static_cast<std::exception *>(pObject));
130                         }
131                 }
132                 RaiseException(CPP_EXCEPTION, EXCEPTION_NONCONTINUABLE, sizeof(args)/sizeof(args[0]), args);
133         }
134 }
135
136 /////////////////////////////////////////////////////////////////////////////
137 // CMergeApp
138
139 BEGIN_MESSAGE_MAP(CMergeApp, CWinApp)
140         //{{AFX_MSG_MAP(CMergeApp)
141         ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
142         ON_COMMAND(ID_VIEW_LANGUAGE, OnViewLanguage)
143         ON_UPDATE_COMMAND_UI(ID_VIEW_LANGUAGE, OnUpdateViewLanguage)
144         ON_COMMAND(ID_HELP, OnHelp)
145         ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, OnOpenRecentFile)
146         //}}AFX_MSG_MAP
147         // Standard file based document commands
148         //ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
149         //ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
150         // Standard print setup command
151         ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
152 END_MESSAGE_MAP()
153
154 static void AddEnglishResourceHook();
155
156 /**
157 * @brief Mapping from command line argument name (eg, ignorews) to WinMerge
158 * option name (eg, Settings/IgnoreSpace).
159 *
160 * These arguments take an optional colon and number, like so:
161 *
162 *  "/ignoreblanklines"  (makes WinMerge ignore blank lines)
163 *  "/ignoreblanklines:1"  (makes WinMerge ignore blank lines)
164 *  "/ignoreblanklines:0"  (makes WinMerge not ignore blank lines)
165 */
166 struct ArgSetting
167 {
168         LPCTSTR CmdArgName;
169         LPCTSTR WinMergeOptionName;
170 };
171
172 /**
173  * @brief Get Options Manager.
174  * @return Pointer to OptionsMgr.
175  */
176 COptionsMgr * GetOptionsMgr()
177 {
178         CMergeApp *pApp = static_cast<CMergeApp *>(AfxGetApp());
179         return pApp->GetMergeOptionsMgr();
180 }
181
182 /**
183  * @brief Get Log.
184  * @return Pointer to Log.
185  */
186 CLogFile * GetLog()
187 {
188         CMergeApp *pApp = static_cast<CMergeApp *>(AfxGetApp());
189         return pApp->GetMergeLog();
190 }
191
192
193 /////////////////////////////////////////////////////////////////////////////
194 // CMergeApp construction
195
196 CMergeApp::CMergeApp() :
197   m_bNeedIdleTimer(FALSE)
198 , m_pDiffTemplate(0)
199 , m_pHexMergeTemplate(0)
200 , m_pDirTemplate(0)
201 , m_mainThreadScripts(NULL)
202 , m_nLastCompareResult(0)
203 , m_bNonInteractive(false)
204 , m_pOptions(NULL)
205 , m_pLog(NULL)
206 , m_nActiveOperations(0)
207 {
208         // add construction code here,
209         // Place all significant initialization in InitInstance
210         m_pLangDlg = new CLanguageSelect(IDR_MAINFRAME, IDR_MAINFRAME);
211 }
212
213 CMergeApp::~CMergeApp()
214 {
215         delete m_pOptions;
216         delete m_pLangDlg;
217         delete m_pLog;
218 }
219 /////////////////////////////////////////////////////////////////////////////
220 // The one and only CMergeApp object
221
222 CMergeApp theApp;
223
224 /////////////////////////////////////////////////////////////////////////////
225 // CMergeApp initialization
226
227 /**
228  * @brief Initialize WinMerge application instance.
229  * @return TRUE if application initialization succeeds (and we'll run it),
230  *   FALSE if something failed and we exit the instance.
231  * @todo We could handle these failure situations more gratefully, i.e. show
232  *  at least some error message to the user..
233  */
234 BOOL CMergeApp::InitInstance()
235 {
236         // Prevents DLL hijacking
237         HMODULE hLibrary = GetModuleHandle(_T("kernel32.dll"));
238         BOOL (WINAPI *pfnSetSearchPathMode)(DWORD) = (BOOL (WINAPI *)(DWORD))GetProcAddress(hLibrary, "SetSearchPathMode");
239         if (pfnSetSearchPathMode)
240                 pfnSetSearchPathMode(0x00000001L /*BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE*/ | 0x00008000L /*BASE_SEARCH_PATH_PERMANENT*/);
241         BOOL (WINAPI *pfnSetDllDirectoryA)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))GetProcAddress(hLibrary, "SetDllDirectoryA");
242         if (pfnSetDllDirectoryA)
243                 pfnSetDllDirectoryA("");
244
245         InitCommonControls();    // initialize common control library
246         CWinApp::InitInstance(); // call parent class method
247
248         // Runtime switch so programmer may set this in interactive debugger
249         int dbgmem = 0;
250         if (dbgmem)
251         {
252                 // get current setting
253                 int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
254                 // Keep freed memory blocks in the heap's linked list and mark them as freed
255                 tmpFlag |= _CRTDBG_DELAY_FREE_MEM_DF;
256                 // Call _CrtCheckMemory at every allocation and deallocation request.
257                 // WARNING: This slows down WinMerge *A LOT*
258                 tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
259                 // Set the new state for the flag
260                 _CrtSetDbgFlag( tmpFlag );
261         }
262
263         // CCrystalEdit Drag and Drop functionality needs AfxOleInit.
264         if(!AfxOleInit())
265         {
266                 TRACE(_T("AfxOleInitFailed. OLE functionality disabled"));
267         }
268
269         // Standard initialization
270         // If you are not using these features and wish to reduce the size
271         //  of your final executable, you should remove from the following
272         //  the specific initialization routines you do not need.
273
274         // Revoke the standard OLE Message Filter to avoid drawing frame while loading files.
275         COleMessageFilter* pOldFilter = AfxOleGetMessageFilter();
276         pOldFilter->Revoke();
277
278         // Only needed by VC6
279 #if _MSC_VER < 1300
280 #ifdef _AFXDLL
281         Enable3dControls();                     // Call this when using MFC in a shared DLL
282 #else
283         Enable3dControlsStatic();       // Call this when linking to MFC statically
284 #endif
285 #endif
286
287         // Load registry file if existing
288         CString sRegPath = CString(GetModulePath(m_hInstance).c_str()) + _T("\\WinMerge.reg");
289         if (paths_DoesPathExist(sRegPath) == IS_EXISTING_FILE)
290         {
291                 CString sCmdArg;
292                 if (SearchPath(NULL, _T("reg.exe"), NULL, 0, NULL, NULL) == 0)
293                         sCmdArg = _T("regedit /s \"") + sRegPath + _T("\"");
294                 else
295                         sCmdArg = _T("reg import \"") + sRegPath + _T("\"");
296                 HANDLE hProcess = RunIt(NULL, sCmdArg, TRUE, FALSE);
297                 if (hProcess)
298                 {
299                         WaitForSingleObject(hProcess, INFINITE);
300                         CloseHandle(hProcess);
301                 }
302         }
303
304         m_pOptions = new CRegOptionsMgr;
305         OptionsInit(); // Implementation in OptionsInit.cpp
306
307         // Initialize temp folder
308         String instTemp = env_GetPerInstanceString(_T("WM_"));
309         env_SetInstanceFolder(instTemp.c_str());
310
311         // Cleanup left over tempfiles from previous instances.
312         // Normally this should not neet to do anything - but if for some reason
313         // WinMerge did not delete temp files this makes sure they are removed.
314         CleanupWMtemp();
315
316         m_pLog = new CLogFile();
317
318         int logging = GetOptionsMgr()->GetInt(OPT_LOGGING);
319         if (logging > 0)
320         {
321                 m_pLog->EnableLogging(TRUE);
322                 String logfile = env_GetMyDocuments(NULL);
323                 logfile = paths_ConcatPath(logfile, _T("WinMerge\\WinMerge.log"));
324                 m_pLog->SetFile(logfile);
325
326                 if (logging == 1)
327                         m_pLog->SetMaskLevel(CLogFile::LALL);
328                 else if (logging == 2)
329                         m_pLog->SetMaskLevel(CLogFile::LERROR | CLogFile::LWARNING);
330         }
331
332         // Parse command-line arguments.
333         MergeCmdLineInfo cmdInfo = GetCommandLine();
334
335         // If paths were given to commandline we consider this being an invoke from
336         // commandline (from other application, shellextension etc).
337         BOOL bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
338
339         // Set default codepage
340         DirScan_InitializeDefaultCodepage();
341
342         // WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
343         // This is the name of the company of the original author (Dean Grimm)
344         SetRegistryKey(_T("Thingamahoochie"));
345
346         BOOL bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
347                 (true == cmdInfo.m_bSingleInstance);
348
349         // Create exclusion mutex name
350         TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
351         DWORD dwLengthNeeded;
352         GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, 
353                 szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
354         TCHAR szMutexName[MAX_PATH + 40];
355         // Combine window class name and desktop name to form a unique mutex name.
356         // As the window class name is decorated to distinguish between ANSI and
357         // UNICODE build, so will be the mutex name.
358         wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
359         HANDLE hMutex = CreateMutex(NULL, FALSE, szMutexName);
360         if (hMutex)
361                 WaitForSingleObject(hMutex, INFINITE);
362         if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
363         {
364                 // Activate previous instance and send commandline to it
365                 HWND hWnd = FindWindow(CMainFrame::szClassName, NULL);
366                 if (hWnd)
367                 {
368                         if (IsIconic(hWnd))
369                                 ShowWindow(hWnd, SW_RESTORE);
370                         SetForegroundWindow(GetLastActivePopup(hWnd));
371                         LPTSTR cmdLine = GetCommandLine();
372                         COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
373                         if (SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
374                         {
375                                 ReleaseMutex(hMutex);
376                                 CloseHandle(hMutex);
377                                 return FALSE;
378                         }
379                 }
380         }
381
382         LoadStdProfileSettings(8);  // Load standard INI file options (including MRU)
383         BOOL bDisableSplash     = GetOptionsMgr()->GetBool(OPT_DISABLE_SPLASH);
384
385         InitializeFileFilters();
386
387         // Read last used filter from registry
388         // If filter fails to set, reset to default
389         const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
390         BOOL bFilterSet = theApp.m_globalFileFilter.SetFilter(filterString.c_str());
391         if (!bFilterSet)
392         {
393                 String filter = theApp.m_globalFileFilter.GetFilterNameOrMask();
394                 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter.c_str());
395         }
396
397         CSplashWnd::EnableSplashScreen(!bDisableSplash && !bCommandLineInvoke);
398
399         // Initialize i18n (multiple language) support
400
401         m_pLangDlg->SetLogFile(GetLog());
402         m_pLangDlg->InitializeLanguage();
403
404         AddEnglishResourceHook(); // Use English string when l10n (foreign) string missing
405
406         m_mainThreadScripts = new CAssureScriptsForThread;
407
408         // Register the application's document templates.  Document templates
409         //  serve as the connection between documents, frame windows and views.
410
411         // Merge Edit view
412         m_pDiffTemplate = new CMultiDocTemplate(
413                 IDR_MERGEDOCTYPE,
414                 RUNTIME_CLASS(CMergeDoc),
415                 RUNTIME_CLASS(CChildFrame), // custom MDI child frame
416                 RUNTIME_CLASS(CMergeEditView));
417         AddDocTemplate(m_pDiffTemplate);
418
419         // Merge Edit view
420         m_pHexMergeTemplate = new CMultiDocTemplate(
421                 IDR_MERGEDOCTYPE,
422                 RUNTIME_CLASS(CHexMergeDoc),
423                 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
424                 RUNTIME_CLASS(CHexMergeView));
425         AddDocTemplate(m_pHexMergeTemplate);
426
427         // Directory view
428         m_pDirTemplate = new CMultiDocTemplate(
429                 IDR_DIRDOCTYPE,
430                 RUNTIME_CLASS(CDirDoc),
431                 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
432                 RUNTIME_CLASS(CDirView));
433         AddDocTemplate(m_pDirTemplate);
434
435         // create main MDI Frame window
436         CMainFrame* pMainFrame = new CMainFrame;
437         if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
438         {
439                 if (hMutex)
440                 {
441                         ReleaseMutex(hMutex);
442                         CloseHandle(hMutex);
443                 }
444                 return FALSE;
445         }
446         m_pMainWnd = pMainFrame;
447         // Enable drag&drop files
448         pMainFrame->ModifyStyleEx(NULL, WS_EX_ACCEPTFILES);
449
450         // Init menus -- hMenuDefault is for MainFrame, other
451         // two are for dirdoc and mergedoc (commented out for now)
452         m_pDiffTemplate->m_hMenuShared = pMainFrame->NewMergeViewMenu();
453         m_pHexMergeTemplate->m_hMenuShared = pMainFrame->NewHexMergeViewMenu();
454         m_pDirTemplate->m_hMenuShared = pMainFrame->NewDirViewMenu();
455         pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
456
457         // Set the menu
458         // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
459         CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
460         pMainFrame->MDISetMenu(pNewMenu, NULL);
461
462         // The main window has been initialized, so activate and update it.
463         pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
464         pMainFrame->UpdateWindow();
465
466         // Since this function actually opens paths for compare it must be
467         // called after initializing CMainFrame!
468         BOOL bContinue = TRUE;
469         if (ParseArgsAndDoOpen(cmdInfo, pMainFrame) == FALSE && bCommandLineInvoke)
470                 bContinue = FALSE;
471
472         if (hMutex)
473                 ReleaseMutex(hMutex);
474
475         if (m_bNonInteractive)
476         {
477                 bContinue = FALSE;
478         }
479
480         // If user wants to cancel the compare, close WinMerge
481         if (bContinue == FALSE)
482         {
483                 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
484         }
485
486         return bContinue;
487 }
488
489 // App command to run the dialog
490 void CMergeApp::OnAppAbout()
491 {
492         CAboutDlg aboutDlg;
493         aboutDlg.DoModal();
494 }
495
496 /////////////////////////////////////////////////////////////////////////////
497 // CMergeApp commands
498
499
500 BOOL CMergeApp::PreTranslateMessage(MSG* pMsg)
501 {
502         // CG: The following lines were added by the Splash Screen component.
503         if (CSplashWnd::PreTranslateAppMessage(pMsg))
504                 return TRUE;
505
506         return CWinApp::PreTranslateMessage(pMsg);
507 }
508
509 void CMergeApp::OnViewLanguage() 
510 {
511         if (m_pLangDlg->DoModal()==IDOK)
512         {
513                 //m_lang.ReloadMenu();
514                 //m_LangDlg.UpdateDocTitle();
515                 GetMainFrame()->UpdateResources();
516         }
517 }
518
519 /**
520  * @brief Updates Language select menu item.
521  * If there are no languages installed we disable menuitem to
522  * open language selection dialog.
523  */
524 void CMergeApp::OnUpdateViewLanguage(CCmdUI* pCmdUI)
525 {
526         BOOL bLangsInstalled = m_pLangDlg->AreLangsInstalled();
527         pCmdUI->Enable(bLangsInstalled);
528 }
529
530 /**
531  * @brief Called when application is about to exit.
532  * This functions is called when application is exiting, so this is
533  * good place to do cleanups.
534  * @return Application's exit value (returned from WinMain()).
535  */
536 int CMergeApp::ExitInstance() 
537 {
538         charsets_cleanup();
539         CString sRegPath = CString(GetModulePath(m_hInstance).c_str()) + _T("\\WinMerge.reg");
540         if (paths_DoesPathExist(sRegPath) == IS_EXISTING_FILE)
541         {
542                 DeleteFile(sRegPath);
543                 CString sCmdArg;
544                 if (SearchPath(NULL, _T("reg.exe"), NULL, 0, NULL, NULL) == 0)
545                         sCmdArg = _T("regedit /s /e \"") + sRegPath + _T("\" ") + _T("HKEY_CURRENT_USER\\") + f_RegDir;
546                 else
547                         sCmdArg = _T("reg export HKCU\\") + f_RegDir + _T(" \"") + sRegPath + _T("\"");
548                 HANDLE hProcess = RunIt(NULL, sCmdArg, TRUE, FALSE);
549                 if (hProcess)
550                         CloseHandle(hProcess);
551         }
552
553         // Remove tempfolder
554         const String temp = env_GetTempPath(NULL);
555         ClearTempfolder(temp);
556         delete m_mainThreadScripts;
557         CWinApp::ExitInstance();
558         return 0;
559 }
560
561 static void AddEnglishResourceHook()
562 {
563         // After calling AfxSetResourceHandle to point to a language
564         // resource DLL, then the application is no longer on the
565         // resource lookup (defined by AfxFindResourceHandle).
566         
567         // Add a dummy extension DLL record whose resource handle
568         // points to the application resources, just to provide
569         // fallback to English for any resources missing from 
570         // the language resource DLL.
571         
572         // (Why didn't Microsoft think of this? Bruno Haible who
573         // made gettext certainly thought of this.)
574
575         // NB: This does not fix the problem that if a control is
576         // missing from a dialog (because it was added only to the
577         // English version, for example) then the DDX_ function is
578         // going to fail. I see no easy way to intercept all DDX
579         // functions except by macro overriding the call--Perry, 2002-12-07.
580
581         static AFX_EXTENSION_MODULE FakeEnglishDLL = { NULL, NULL };
582         memset(&FakeEnglishDLL, 0, sizeof(FakeEnglishDLL));
583         FakeEnglishDLL.hModule = AfxGetApp()->m_hInstance;
584         FakeEnglishDLL.hResource = FakeEnglishDLL.hModule;
585         FakeEnglishDLL.bInitialized = TRUE;
586         new CDynLinkLibrary(FakeEnglishDLL); // hook into MFC extension DLL chain
587 }
588
589
590 int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
591 {
592         // This is a convenient point for breakpointing !!!
593
594         // Create a handle to store the parent window of the message box.
595         CWnd* pParentWnd = CWnd::GetActiveWindow();
596         
597         // Check whether an active window was retrieved successfully.
598         if ( pParentWnd == NULL )
599         {
600                 // Try to retrieve a handle to the last active popup.
601                 CWnd * mainwnd = GetMainWnd();
602                 if (mainwnd)
603                         pParentWnd = mainwnd->GetLastActivePopup();
604         }
605
606         // Use our own message box implementation, which adds the
607         // do not show again checkbox, and implements it on subsequent calls
608         // (if caller set the style)
609
610         if (m_bNonInteractive)
611                 return IDCANCEL;
612
613         // Create the message box dialog.
614         CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType,
615                 nIDPrompt);
616         
617         // Display the message box dialog and return the result.
618         return (int) dlgMessage.DoModal();
619 }
620
621 /** 
622  * @brief Set flag so that application will broadcast notification at next
623  * idle time (via WM_TIMER id=IDLE_TIMER)
624  */
625 void CMergeApp::SetNeedIdleTimer()
626 {
627         m_bNeedIdleTimer = TRUE; 
628 }
629
630 BOOL CMergeApp::OnIdle(LONG lCount) 
631 {
632         if (CWinApp::OnIdle(lCount))
633                 return TRUE;
634
635         // If anyone has requested notification when next idle occurs, send it
636         if (m_bNeedIdleTimer)
637         {
638                 m_bNeedIdleTimer = FALSE;
639                 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
640         }
641         return FALSE;
642 }
643
644 /**
645  * @brief Load any known file filters.
646  *
647  * This function loads filter files from paths we know contain them.
648  * @note User's filter location may not be set yet.
649  */
650 void CMergeApp::InitializeFileFilters()
651 {
652         CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
653
654         if (!filterPath.IsEmpty())
655         {
656                 m_globalFileFilter.SetUserFilterPath((LPCTSTR)filterPath);
657         }
658         m_globalFileFilter.LoadAllFileFilters();
659 }
660
661 /** @brief Read command line arguments and open files for comparison.
662  *
663  * The name of the function is a legacy code from the time that this function
664  * actually parsed the command line. Today the parsing is done using the
665  * MergeCmdLineInfo class.
666  * @param [in] cmdInfo Commandline parameters info.
667  * @param [in] pMainFrame Pointer to application main frame.
668  * @return TRUE if we opened the compare, FALSE if the compare was canceled.
669  */
670 BOOL CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
671 {
672         BOOL bCompared = FALSE;
673         m_bNonInteractive = cmdInfo.m_bNonInteractive;
674
675         // Set the global file filter.
676         if (!cmdInfo.m_sFileFilter.empty())
677         {
678                 m_globalFileFilter.SetFilter(cmdInfo.m_sFileFilter.c_str());
679         }
680
681         // Set codepage.
682         if (cmdInfo.m_nCodepage)
683         {
684                 updateDefaultCodepage(2,cmdInfo.m_nCodepage);
685         }
686
687         // Unless the user has requested to see WinMerge's usage open files for
688         // comparison.
689         if (cmdInfo.m_bShowUsage)
690         {
691                 pMainFrame->ShowHelp(CommandLineHelpLocation);
692         }
693         else
694         {
695                 // Set the required information we need from the command line:
696
697                 pMainFrame->m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
698                 pMainFrame->m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
699                 pMainFrame->m_bEscShutdown = cmdInfo.m_bEscShutdown;
700
701                 pMainFrame->m_strSaveAsPath = cmdInfo.m_sOutputpath.c_str();
702
703                 pMainFrame->m_strDescriptions[0] = cmdInfo.m_sLeftDesc;
704                 if (cmdInfo.m_Files.GetSize() < 3)
705                 {
706                         pMainFrame->m_strDescriptions[1] = cmdInfo.m_sRightDesc;
707                 }
708                 else
709                 {
710                         pMainFrame->m_strDescriptions[1] = cmdInfo.m_sMiddleDesc;
711                         pMainFrame->m_strDescriptions[2] = cmdInfo.m_sRightDesc;
712                 }
713
714                 if (cmdInfo.m_Files.GetSize() > 2)
715                 {
716                         cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
717                         cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
718                         cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
719                         DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
720                         bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
721                                 dwFlags, cmdInfo.m_bRecurse, NULL,
722                                 cmdInfo.m_sPreDiffer.c_str());
723                 }
724                 else if (cmdInfo.m_Files.GetSize() > 1)
725                 {
726                         DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
727                         bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
728                                 dwFlags, cmdInfo.m_bRecurse, NULL,
729                                 cmdInfo.m_sPreDiffer.c_str());
730                 }
731                 else if (cmdInfo.m_Files.GetSize() == 1)
732                 {
733                         String sFilepath = cmdInfo.m_Files[0];
734                         if (IsProjectFile(sFilepath.c_str()))
735                         {
736                                 bCompared = LoadAndOpenProjectFile(sFilepath.c_str());
737                         }
738                         else if (IsConflictFile(sFilepath.c_str()))
739                         {
740                                 bCompared = pMainFrame->DoOpenConflict(sFilepath.c_str());
741                         }
742                         else
743                         {
744                                 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
745                                 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
746                                         dwFlags, cmdInfo.m_bRecurse, NULL,
747                                         cmdInfo.m_sPreDiffer.c_str());
748                         }
749                 }
750                 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
751                 {
752                         BOOL showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
753                         if (showFiles)
754                                 pMainFrame->DoFileOpen();
755                 }
756         }
757         return bCompared;
758 }
759
760 /** @brief Open help from mainframe when user presses F1*/
761 void CMergeApp::OnHelp()
762 {
763         GetMainFrame()->ShowHelp();
764 }
765
766 /**
767  * @brief Is specified file a project file?
768  * @param [in] filepath Full path to file to check.
769  * @return true if file is a projectfile.
770  */
771 bool CMergeApp::IsProjectFile(LPCTSTR filepath) const
772 {
773         String ext;
774         SplitFilename(filepath, NULL, NULL, &ext);
775         CString sExt(ext.c_str());
776         if (sExt.CompareNoCase(PROJECTFILE_EXT) == 0)
777                 return true;
778         else
779                 return false;
780 }
781
782 /** 
783  * @brief Read project and perform comparison specified
784  * @param [in] sProject Full path to project file.
785  * @return TRUE if loading project file and starting compare succeeded.
786  */
787 bool CMergeApp::LoadAndOpenProjectFile(LPCTSTR sProject)
788 {
789         if (*sProject == '\0')
790                 return false;
791
792         ProjectFile project;
793         String sErr;
794         if (!project.Read(sProject, &sErr))
795         {
796                 if (sErr.empty())
797                         sErr = theApp.LoadString(IDS_UNK_ERROR_READING_PROJECT);
798                 CString msg;
799                 LangFormatString2(msg, IDS_ERROR_FILEOPEN, sProject, sErr.c_str());
800                 AfxMessageBox(msg, MB_ICONSTOP);
801                 return false;
802         }
803         PathContext files;
804         BOOL bLeftReadOnly = FALSE;
805         BOOL bMiddleReadOnly = FALSE;
806         BOOL bRightReadOnly = FALSE;
807         BOOL bRecursive = FALSE;
808         project.GetPaths(files, bRecursive);
809         bLeftReadOnly = project.GetLeftReadOnly();
810         bMiddleReadOnly = project.GetMiddleReadOnly();
811         bRightReadOnly = project.GetRightReadOnly();
812         if (project.HasFilter())
813         {
814                 String filter = project.GetFilter();
815                 filter = string_trim_ws(filter);
816                 m_globalFileFilter.SetFilter(filter);
817         }
818         if (project.HasSubfolders())
819                 bRecursive = project.GetSubfolders() > 0;
820
821         DWORD dwFlags[3] = {
822                 files.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT,
823                 files.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT,
824                 files.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT
825         };
826         if (bLeftReadOnly)
827                 dwFlags[0] |= FFILEOPEN_READONLY;
828         if (files.GetSize() == 2)
829         {
830                 if (bRightReadOnly)
831                         dwFlags[1] |= FFILEOPEN_READONLY;
832         }
833         else
834         {
835                 if (bMiddleReadOnly)
836                         dwFlags[1] |= FFILEOPEN_READONLY;
837                 if (bRightReadOnly)
838                         dwFlags[2] |= FFILEOPEN_READONLY;
839         }
840
841         WriteProfileInt(_T("Settings"), _T("Recurse"), bRecursive);
842         
843         BOOL rtn = GetMainFrame()->DoFileOpen(&files, dwFlags, bRecursive);
844
845         AddToRecentProjectsMRU(sProject);
846         return !!rtn;
847 }
848
849 /**
850  * @brief Return windows language ID of current WinMerge GUI language
851  */
852 WORD CMergeApp::GetLangId() const
853 {
854         return m_pLangDlg->GetLangId();
855 }
856
857 /**
858  * @brief Lang aware version of CStatusBar::SetIndicators()
859  */
860 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
861 {
862         m_pLangDlg->SetIndicators(sb, rgid, n);
863 }
864
865 /**
866  * @brief Translate menu to current WinMerge GUI language
867  */
868 void CMergeApp::TranslateMenu(HMENU h) const
869 {
870         m_pLangDlg->TranslateMenu(h);
871 }
872
873 /**
874  * @brief Translate dialog to current WinMerge GUI language
875  */
876 void CMergeApp::TranslateDialog(HWND h) const
877 {
878         m_pLangDlg->TranslateDialog(h);
879 }
880
881 /**
882  * @brief Load string and translate to current WinMerge GUI language
883  */
884 String CMergeApp::LoadString(UINT id) const
885 {
886         return m_pLangDlg->LoadString(id);
887 }
888
889 /**
890  * @brief Load dialog caption and translate to current WinMerge GUI language
891  */
892 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
893 {
894         return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
895 }
896
897 /**
898  * @brief Reload main menu(s) (for language change)
899  */
900 void CMergeApp::ReloadMenu()
901 {
902         m_pLangDlg->ReloadMenu();
903 }
904
905 /** @brief Wrap one line of cmdline help in appropriate whitespace */
906 static String CmdlineOption(int idres)
907 {
908         String str = theApp.LoadString(idres) + _T(" \n");
909         return str;
910 }
911
912 /**
913  * @brief Get default editor path.
914  * @return full path to the editor program executable.
915  */
916 CString CMergeApp::GetDefaultEditor()
917 {
918         CString path = env_GetWindowsDirectory().c_str();
919         path += _T("\\NOTEPAD.EXE");
920         return path;
921 }
922
923 /**
924  * @brief Get default user filter folder path.
925  * This function returns the default filter path for user filters.
926  * If wanted so (@p bCreate) path can be created if it does not
927  * exist yet. But you really want to create the patch only when
928  * there is no user path defined.
929  * @param [in] bCreate If TRUE filter path is created if it does
930  *  not exist.
931  * @return Default folder for user filters.
932  */
933 CString CMergeApp::GetDefaultFilterUserPath(BOOL bCreate /*=FALSE*/)
934 {
935         String pathMyFolders = env_GetMyDocuments(NULL);
936         String pathFilters(pathMyFolders);
937         pathFilters = paths_ConcatPath(pathFilters, DefaultRelativeFilterPath);
938
939         if (bCreate && !paths_CreateIfNeeded(pathFilters.c_str()))
940         {
941                 // Failed to create a folder, check it didn't already
942                 // exist.
943                 DWORD errCode = GetLastError();
944                 if (errCode != ERROR_ALREADY_EXISTS)
945                 {
946                         // Failed to create a folder for filters, fallback to
947                         // "My Documents"-folder. It is not worth the trouble to
948                         // bother user about this or user more clever solutions.
949                         pathFilters = pathMyFolders;
950                 }
951         }
952         return pathFilters.c_str();
953 }
954
955
956 /**
957  * @brief Adds specified file to the recent projects list.
958  * @param [in] sPathName Path to project file
959  */
960 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
961 {
962         // sPathName will be added to the top of the MRU list. 
963         // If sPathName already exists in the MRU list, it will be moved to the top
964         if (m_pRecentFileList != NULL)    {
965                 m_pRecentFileList->Add(sPathName);
966                 m_pRecentFileList->WriteList();
967         }
968 }
969
970 /**
971  * @brief Handles menu selection from recent projects list
972  * @param [in] nID Menu ID of the selected item
973  */
974 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
975 {
976         return LoadAndOpenProjectFile(m_pRecentFileList->m_arrNames[nID-ID_FILE_MRU_FILE1]);
977 }