OSDN Git Service

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