OSDN Git Service

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