1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
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.
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.
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.
20 /////////////////////////////////////////////////////////////////////////////
24 * @brief Defines the class behaviors for the application.
27 // ID line follows -- this is updated by SVN
28 // $Id: Merge.cpp 6861 2009-06-25 12:11:07Z kimmov $
32 #include "Constants.h"
33 #include "UnicodeString.h"
35 #include "Environment.h"
36 #include "OptionsMgr.h"
37 #include "RegOptionsMgr.h"
41 #include "HexMergeDoc.h"
42 #include "HexMergeFrm.h"
43 #include "HexMergeView.h"
52 #include "PropBackups.h"
53 #include "FileOrFolderSelect.h"
55 #include "FileFilterHelper.h"
56 #include "LineFiltersList.h"
57 #include "SyntaxColors.h"
58 #include "OptionsSyntaxColors.h"
60 #include "ProjectFile.h"
61 #include "MergeEditView.h"
62 #include "LanguageSelect.h"
63 #include "OptionsDef.h"
64 #include "MergeCmdLineInfo.h"
65 #include "ConflictFileParser.h"
68 #include "stringdiffs.h"
70 #include "VSSHelper.h"
72 // For shutdown cleanup
78 static char THIS_FILE[] = __FILE__;
83 /** @brief Location for command line help to open. */
84 static TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
86 // registry dir to WinMerge
87 static String f_RegDir = _T("Software\\Thingamahoochie\\WinMerge");
89 /** @brief Backup file extension. */
90 static const TCHAR BACKUP_FILE_EXT[] = _T("bak");
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.
100 namespace Turn_STL_exceptions_into_MFC_exceptions
102 # ifndef _STATIC_CPPLIB
103 # error This hack only works with _STATIC_CPPLIB defined.
106 class CDisguisedSTLException : public CException
109 std::exception *m_pSTLException;
111 CDisguisedSTLException(std::exception *pSTLException)
112 : m_pSTLException(pSTLException)
115 virtual BOOL GetErrorMessage(LPTSTR lpszError, UINT nMaxError, PUINT)
117 _sntprintf(lpszError, nMaxError, _T("%hs"), m_pSTLException->what());
122 const DWORD CPP_EXCEPTION = 0xE06D7363;
123 const DWORD MS_MAGIC = 0x19930520;
125 extern "C" void __stdcall _CxxThrowException(void *pObject, _s__ThrowInfo const *pObjectInfo)
127 __declspec(thread) static ULONG_PTR args[3] = { MS_MAGIC, 0, 0 };
130 pObject = reinterpret_cast<void *>(args[1]);
131 pObjectInfo = reinterpret_cast<_s__ThrowInfo const *>(args[2]);
135 args[1] = (ULONG_PTR)pObject;
136 args[2] = (ULONG_PTR)pObjectInfo;
139 if (pObjectInfo->pCatchableTypeArray && (i = pObjectInfo->pCatchableTypeArray->nCatchableTypes))
141 const char *name = typeid(std::exception).raw_name();
142 if (pObjectInfo->pCatchableTypeArray->arrayOfCatchableTypes[i - 1]->pType->name == name)
144 throw new CDisguisedSTLException(static_cast<std::exception *>(pObject));
147 RaiseException(CPP_EXCEPTION, EXCEPTION_NONCONTINUABLE, sizeof(args)/sizeof(args[0]), args);
153 /////////////////////////////////////////////////////////////////////////////
156 BEGIN_MESSAGE_MAP(CMergeApp, CWinApp)
157 //{{AFX_MSG_MAP(CMergeApp)
158 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
159 ON_COMMAND(ID_VIEW_LANGUAGE, OnViewLanguage)
160 ON_UPDATE_COMMAND_UI(ID_VIEW_LANGUAGE, OnUpdateViewLanguage)
161 ON_COMMAND(ID_HELP, OnHelp)
162 ON_COMMAND_EX_RANGE(ID_FILE_MRU_FILE1, ID_FILE_MRU_FILE16, OnOpenRecentFile)
164 // Standard file based document commands
165 //ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
166 //ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
167 // Standard print setup command
168 ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
171 static void AddEnglishResourceHook();
174 * @brief Mapping from command line argument name (eg, ignorews) to WinMerge
175 * option name (eg, Settings/IgnoreSpace).
177 * These arguments take an optional colon and number, like so:
179 * "/ignoreblanklines" (makes WinMerge ignore blank lines)
180 * "/ignoreblanklines:1" (makes WinMerge ignore blank lines)
181 * "/ignoreblanklines:0" (makes WinMerge not ignore blank lines)
186 LPCTSTR WinMergeOptionName;
190 /////////////////////////////////////////////////////////////////////////////
191 // CMergeApp construction
193 CMergeApp::CMergeApp() :
194 m_bNeedIdleTimer(FALSE)
197 , m_pHexMergeTemplate(0)
199 , m_mainThreadScripts(NULL)
200 , m_nLastCompareResult(0)
201 , m_bNonInteractive(false)
202 , m_pOptions(new CRegOptionsMgr())
203 , m_pGlobalFileFilter(new FileFilterHelper())
204 , m_nActiveOperations(0)
205 , m_pLangDlg(new CLanguageSelect(IDR_MAINFRAME, IDR_MAINFRAME))
206 , m_bEscShutdown(FALSE)
207 , m_bClearCaseTool(FALSE)
208 , m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
209 , m_pLineFilters(new LineFiltersList())
210 , m_pSyntaxColors(new SyntaxColors())
211 , m_pVssHelper(new VSSHelper())
212 , m_CheckOutMulti(FALSE)
213 , m_bVCProjSync(FALSE)
214 , m_bVssSuppressPathCheck(FALSE)
216 // add construction code here,
217 // Place all significant initialization in InitInstance
220 CMergeApp::~CMergeApp()
224 /////////////////////////////////////////////////////////////////////////////
225 // The one and only CMergeApp object
229 /////////////////////////////////////////////////////////////////////////////
230 // CMergeApp initialization
233 * @brief Initialize WinMerge application instance.
234 * @return TRUE if application initialization succeeds (and we'll run it),
235 * FALSE if something failed and we exit the instance.
236 * @todo We could handle these failure situations more gratefully, i.e. show
237 * at least some error message to the user..
239 BOOL CMergeApp::InitInstance()
241 // Prevents DLL hijacking
242 HMODULE hLibrary = GetModuleHandle(_T("kernel32.dll"));
243 BOOL (WINAPI *pfnSetSearchPathMode)(DWORD) = (BOOL (WINAPI *)(DWORD))GetProcAddress(hLibrary, "SetSearchPathMode");
244 if (pfnSetSearchPathMode)
245 pfnSetSearchPathMode(0x00000001L /*BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE*/ | 0x00008000L /*BASE_SEARCH_PATH_PERMANENT*/);
246 BOOL (WINAPI *pfnSetDllDirectoryA)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))GetProcAddress(hLibrary, "SetDllDirectoryA");
247 if (pfnSetDllDirectoryA)
248 pfnSetDllDirectoryA("");
250 JumpList::SetCurrentProcessExplicitAppUserModelID(L"Thingamahoochie.WinMerge");
252 InitCommonControls(); // initialize common control library
253 CWinApp::InitInstance(); // call parent class method
255 // Runtime switch so programmer may set this in interactive debugger
259 // get current setting
260 int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
261 // Keep freed memory blocks in the heap's linked list and mark them as freed
262 tmpFlag |= _CRTDBG_DELAY_FREE_MEM_DF;
263 // Call _CrtCheckMemory at every allocation and deallocation request.
264 // WARNING: This slows down WinMerge *A LOT*
265 tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
266 // Set the new state for the flag
267 _CrtSetDbgFlag( tmpFlag );
270 // CCrystalEdit Drag and Drop functionality needs AfxOleInit.
273 TRACE(_T("AfxOleInitFailed. OLE functionality disabled"));
276 // Standard initialization
277 // If you are not using these features and wish to reduce the size
278 // of your final executable, you should remove from the following
279 // the specific initialization routines you do not need.
281 // Revoke the standard OLE Message Filter to avoid drawing frame while loading files.
282 COleMessageFilter* pOldFilter = AfxOleGetMessageFilter();
283 pOldFilter->Revoke();
285 // Only needed by VC6
288 Enable3dControls(); // Call this when using MFC in a shared DLL
290 Enable3dControlsStatic(); // Call this when linking to MFC statically
294 // Load registry keys from WinMerge.reg if existing WinMerge.reg
295 env_LoadRegistryFromFile(env_GetProgPath() + _T("\\WinMerge.reg"));
297 OptionsInit(); // Implementation in OptionsInit.cpp
299 // Initialize temp folder
300 String instTemp = env_GetPerInstanceString(TempFolderPrefix);
301 if (GetOptionsMgr()->GetBool(OPT_USE_SYSTEM_TEMP_PATH))
302 env_SetTempPath(paths_ConcatPath(env_GetSystemTempPath(), instTemp));
304 env_SetTempPath(paths_ConcatPath(GetOptionsMgr()->GetString(OPT_CUSTOM_TEMP_PATH), instTemp));
306 // Cleanup left over tempfiles from previous instances.
307 // Normally this should not neet to do anything - but if for some reason
308 // WinMerge did not delete temp files this makes sure they are removed.
311 // Parse command-line arguments.
312 MergeCmdLineInfo cmdInfo = GetCommandLine();
314 // If paths were given to commandline we consider this being an invoke from
315 // commandline (from other application, shellextension etc).
316 BOOL bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
318 // WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
319 // This is the name of the company of the original author (Dean Grimm)
320 SetRegistryKey(_T("Thingamahoochie"));
322 BOOL bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
323 (true == cmdInfo.m_bSingleInstance);
325 // Create exclusion mutex name
326 TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
327 DWORD dwLengthNeeded;
328 GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME,
329 szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
330 TCHAR szMutexName[MAX_PATH + 40];
331 // Combine window class name and desktop name to form a unique mutex name.
332 // As the window class name is decorated to distinguish between ANSI and
333 // UNICODE build, so will be the mutex name.
334 wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
335 HANDLE hMutex = CreateMutex(NULL, FALSE, szMutexName);
337 WaitForSingleObject(hMutex, INFINITE);
338 if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
340 // Activate previous instance and send commandline to it
341 HWND hWnd = FindWindow(CMainFrame::szClassName, NULL);
345 ShowWindow(hWnd, SW_RESTORE);
346 SetForegroundWindow(GetLastActivePopup(hWnd));
347 LPTSTR cmdLine = GetCommandLine();
348 COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
349 if (SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
351 ReleaseMutex(hMutex);
358 LoadStdProfileSettings(8); // Load standard INI file options (including MRU)
359 BOOL bDisableSplash = GetOptionsMgr()->GetBool(OPT_DISABLE_SPLASH);
361 InitializeFileFilters();
363 // Read last used filter from registry
364 // If filter fails to set, reset to default
365 const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
366 BOOL bFilterSet = m_pGlobalFileFilter->SetFilter(filterString.c_str());
369 String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
370 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
373 UpdateCodepageModule();
375 InitializeSourceControlMembers();
376 g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
377 g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
380 Options::SyntaxColors::Load(m_pSyntaxColors.get());
383 m_pLineFilters->Initialize(GetOptionsMgr());
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)
389 String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
390 if (!oldFilter.empty())
391 m_pLineFilters->Import(oldFilter);
394 // Check if filter folder is set, and create it if not
395 String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
396 if (pathMyFolders.empty())
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());
404 sd_Init(); // String diff init
405 sd_SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
407 CSplashWnd::EnableSplashScreen(!bDisableSplash && !bCommandLineInvoke);
409 // Initialize i18n (multiple language) support
411 m_pLangDlg->InitializeLanguage();
413 AddEnglishResourceHook(); // Use English string when l10n (foreign) string missing
415 m_mainThreadScripts = new CAssureScriptsForThread;
417 // Register the application's document templates. Document templates
418 // serve as the connection between documents, frame windows and views.
421 m_pOpenTemplate = new CMultiDocTemplate(
423 RUNTIME_CLASS(COpenDoc),
424 RUNTIME_CLASS(COpenFrame), // custom MDI child frame
425 RUNTIME_CLASS(COpenView));
426 AddDocTemplate(m_pOpenTemplate);
429 m_pDiffTemplate = new CMultiDocTemplate(
431 RUNTIME_CLASS(CMergeDoc),
432 RUNTIME_CLASS(CChildFrame), // custom MDI child frame
433 RUNTIME_CLASS(CMergeEditView));
434 AddDocTemplate(m_pDiffTemplate);
437 m_pHexMergeTemplate = new CMultiDocTemplate(
439 RUNTIME_CLASS(CHexMergeDoc),
440 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
441 RUNTIME_CLASS(CHexMergeView));
442 AddDocTemplate(m_pHexMergeTemplate);
445 m_pDirTemplate = new CMultiDocTemplate(
447 RUNTIME_CLASS(CDirDoc),
448 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
449 RUNTIME_CLASS(CDirView));
450 AddDocTemplate(m_pDirTemplate);
452 // create main MDI Frame window
453 CMainFrame* pMainFrame = new CMainFrame;
454 if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
458 ReleaseMutex(hMutex);
463 m_pMainWnd = pMainFrame;
464 // Enable drag&drop files
465 pMainFrame->ModifyStyleEx(NULL, WS_EX_ACCEPTFILES);
467 // Init menus -- hMenuDefault is for MainFrame
468 pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
471 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
472 CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
473 pMainFrame->MDISetMenu(pNewMenu, NULL);
475 // The main window has been initialized, so activate and update it.
476 pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
477 pMainFrame->UpdateWindow();
479 // Since this function actually opens paths for compare it must be
480 // called after initializing CMainFrame!
481 BOOL bContinue = TRUE;
482 if (ParseArgsAndDoOpen(cmdInfo, pMainFrame) == FALSE && bCommandLineInvoke)
486 ReleaseMutex(hMutex);
488 if (m_bNonInteractive)
493 // If user wants to cancel the compare, close WinMerge
494 if (bContinue == FALSE)
496 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
502 // App command to run the dialog
503 void CMergeApp::OnAppAbout()
509 /////////////////////////////////////////////////////////////////////////////
510 // CMergeApp commands
513 BOOL CMergeApp::PreTranslateMessage(MSG* pMsg)
515 // CG: The following lines were added by the Splash Screen component.
516 if (CSplashWnd::PreTranslateAppMessage(pMsg))
519 return CWinApp::PreTranslateMessage(pMsg);
522 void CMergeApp::OnViewLanguage()
524 if (m_pLangDlg->DoModal()==IDOK)
526 //m_lang.ReloadMenu();
527 //m_LangDlg.UpdateDocTitle();
528 GetMainFrame()->UpdateResources();
533 * @brief Updates Language select menu item.
534 * If there are no languages installed we disable menuitem to
535 * open language selection dialog.
537 void CMergeApp::OnUpdateViewLanguage(CCmdUI* pCmdUI)
539 BOOL bLangsInstalled = m_pLangDlg->AreLangsInstalled();
540 pCmdUI->Enable(bLangsInstalled);
544 * @brief Called when application is about to exit.
545 * This functions is called when application is exiting, so this is
546 * good place to do cleanups.
547 * @return Application's exit value (returned from WinMain()).
549 int CMergeApp::ExitInstance()
553 // Save registry keys if existing WinMerge.reg
554 env_SaveRegistryToFile(env_GetProgPath() + _T("\\WinMerge.reg"), f_RegDir);
557 const String temp = env_GetTempPath();
558 ClearTempfolder(temp);
559 delete m_mainThreadScripts;
560 CWinApp::ExitInstance();
564 static void AddEnglishResourceHook()
567 // After calling AfxSetResourceHandle to point to a language
568 // resource DLL, then the application is no longer on the
569 // resource lookup (defined by AfxFindResourceHandle).
571 // Add a dummy extension DLL record whose resource handle
572 // points to the application resources, just to provide
573 // fallback to English for any resources missing from
574 // the language resource DLL.
576 // (Why didn't Microsoft think of this? Bruno Haible who
577 // made gettext certainly thought of this.)
579 // NB: This does not fix the problem that if a control is
580 // missing from a dialog (because it was added only to the
581 // English version, for example) then the DDX_ function is
582 // going to fail. I see no easy way to intercept all DDX
583 // functions except by macro overriding the call--Perry, 2002-12-07.
585 static AFX_EXTENSION_MODULE FakeEnglishDLL = { NULL, NULL };
586 memset(&FakeEnglishDLL, 0, sizeof(FakeEnglishDLL));
587 FakeEnglishDLL.hModule = AfxGetApp()->m_hInstance;
588 FakeEnglishDLL.hResource = FakeEnglishDLL.hModule;
589 FakeEnglishDLL.bInitialized = TRUE;
590 new CDynLinkLibrary(FakeEnglishDLL); // hook into MFC extension DLL chain
595 int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
597 // This is a convenient point for breakpointing !!!
599 // Create a handle to store the parent window of the message box.
600 CWnd* pParentWnd = CWnd::GetActiveWindow();
602 // Check whether an active window was retrieved successfully.
603 if ( pParentWnd == NULL )
605 // Try to retrieve a handle to the last active popup.
606 CWnd * mainwnd = GetMainWnd();
608 pParentWnd = mainwnd->GetLastActivePopup();
611 // Use our own message box implementation, which adds the
612 // do not show again checkbox, and implements it on subsequent calls
613 // (if caller set the style)
615 if (m_bNonInteractive)
618 // Create the message box dialog.
619 CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType,
622 // Display the message box dialog and return the result.
623 return (int) dlgMessage.DoModal();
627 * @brief Set flag so that application will broadcast notification at next
628 * idle time (via WM_TIMER id=IDLE_TIMER)
630 void CMergeApp::SetNeedIdleTimer()
632 m_bNeedIdleTimer = TRUE;
635 BOOL CMergeApp::OnIdle(LONG lCount)
637 if (CWinApp::OnIdle(lCount))
640 // If anyone has requested notification when next idle occurs, send it
641 if (m_bNeedIdleTimer)
643 m_bNeedIdleTimer = FALSE;
644 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
650 * @brief Load any known file filters.
652 * This function loads filter files from paths we know contain them.
653 * @note User's filter location may not be set yet.
655 void CMergeApp::InitializeFileFilters()
657 CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
659 if (!filterPath.IsEmpty())
661 m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
663 m_pGlobalFileFilter->LoadAllFileFilters();
666 /** @brief Read command line arguments and open files for comparison.
668 * The name of the function is a legacy code from the time that this function
669 * actually parsed the command line. Today the parsing is done using the
670 * MergeCmdLineInfo class.
671 * @param [in] cmdInfo Commandline parameters info.
672 * @param [in] pMainFrame Pointer to application main frame.
673 * @return TRUE if we opened the compare, FALSE if the compare was canceled.
675 BOOL CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
677 BOOL bCompared = FALSE;
678 m_bNonInteractive = cmdInfo.m_bNonInteractive;
680 // Set the global file filter.
681 if (!cmdInfo.m_sFileFilter.empty())
683 m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter.c_str());
687 if (cmdInfo.m_nCodepage)
689 UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
692 // Unless the user has requested to see WinMerge's usage open files for
694 if (cmdInfo.m_bShowUsage)
696 ShowHelp(CommandLineHelpLocation);
700 // Set the required information we need from the command line:
702 m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
703 m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
704 m_bEscShutdown = cmdInfo.m_bEscShutdown;
706 m_strSaveAsPath = cmdInfo.m_sOutputpath.c_str();
708 m_strDescriptions[0] = cmdInfo.m_sLeftDesc;
709 if (cmdInfo.m_Files.GetSize() < 3)
711 m_strDescriptions[1] = cmdInfo.m_sRightDesc;
715 m_strDescriptions[1] = cmdInfo.m_sMiddleDesc;
716 m_strDescriptions[2] = cmdInfo.m_sRightDesc;
719 if (cmdInfo.m_Files.GetSize() > 2)
721 cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
722 cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
723 cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
724 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
725 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
726 dwFlags, cmdInfo.m_bRecurse, NULL,
727 cmdInfo.m_sPreDiffer.c_str());
729 else if (cmdInfo.m_Files.GetSize() > 1)
731 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
732 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
733 dwFlags, cmdInfo.m_bRecurse, NULL,
734 cmdInfo.m_sPreDiffer.c_str());
736 else if (cmdInfo.m_Files.GetSize() == 1)
738 String sFilepath = cmdInfo.m_Files[0];
739 if (IsProjectFile(sFilepath))
741 bCompared = LoadAndOpenProjectFile(sFilepath);
743 else if (IsConflictFile(sFilepath))
745 bCompared = pMainFrame->DoOpenConflict(sFilepath.c_str());
749 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
750 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
751 dwFlags, cmdInfo.m_bRecurse, NULL,
752 cmdInfo.m_sPreDiffer.c_str());
755 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
757 BOOL showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
759 pMainFrame->DoFileOpen();
765 void CMergeApp::UpdateDefaultCodepage(int cpDefaultMode, int cpCustomCodepage)
769 switch (cpDefaultMode)
772 ucr::setDefaultCodepage(GetACP());
776 wLangId = GetLangId();
777 if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
778 ucr::setDefaultCodepage(_ttol(buff));
780 ucr::setDefaultCodepage(GetACP());
783 ucr::setDefaultCodepage(cpCustomCodepage);
786 // no other valid option
788 ucr::setDefaultCodepage(GetACP());
793 * @brief Send current option settings into codepage module
795 void CMergeApp::UpdateCodepageModule()
797 // Get current codepage settings from the options module
798 // and push them into the codepage module
799 UpdateDefaultCodepage(GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_MODE), GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_CUSTOM));
802 /** @brief Open help from mainframe when user presses F1*/
803 void CMergeApp::OnHelp()
809 * @brief Open given file to external editor specified in options.
810 * @param [in] file Full path to file to open.
812 * Opens file to defined (in Options/system), Notepad by default,
813 * external editor. Path is decorated with quotation marks if needed
814 * (contains spaces). Also '$file' in editor path is replaced by
816 * @param [in] file Full path to file to open.
817 * @param [in] nLineNumber Line number to go to.
819 void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
821 String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
823 string_replace(sCmd, _T("$linenum"), string_format(_T("%d"), nLineNumber));
825 int nIndex = sCmd.find(_T("$file"));
828 sFile.insert(0, _T("\""));
829 string_replace(sCmd, _T("$file"), sFile);
830 nIndex = sCmd.find(' ', nIndex + sFile.length());
832 sCmd.insert(nIndex, _T("\""));
844 STARTUPINFO stInfo = {0};
845 stInfo.cb = sizeof(STARTUPINFO);
846 PROCESS_INFORMATION processInfo;
848 retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
849 NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
850 &stInfo, &processInfo);
854 // Error invoking external editor
855 ResMsgBox1(IDS_ERROR_EXECUTE_FILE, sCmd.c_str(), MB_ICONSTOP);
859 CloseHandle(processInfo.hThread);
860 CloseHandle(processInfo.hProcess);
865 * @brief Open file, if it exists, else open url
867 void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
869 if (paths_DoesPathExist(szFile) == IS_EXISTING_FILE)
870 ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
872 ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
876 * @brief Show Help - this is for opening help from outside mainframe.
877 * @param [in] helpLocation Location inside help, if NULL main help is opened.
879 void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
881 String sPath = env_GetProgPath();
882 LANGID LangId = GetLangId();
883 if (PRIMARYLANGID(LangId) == LANG_JAPANESE)
884 sPath += DocsPath_ja;
887 if (helpLocation == NULL)
889 if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
890 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
892 ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
896 if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
898 sPath += helpLocation;
899 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
905 * @brief Creates backup before file is saved or copied over.
906 * This function handles formatting correct path and filename for
907 * backup file. Formatting is done based on several options available
908 * for users in Options/Backups dialog. After path is formatted, file
909 * is simply just copied. Not much error checking, just if copying
910 * succeeded or failed.
911 * @param [in] bFolder Are we creating backup in folder compare?
912 * @param [in] pszPath Full path to file to backup.
913 * @return TRUE if backup succeeds, or isn't just done.
915 BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
917 // If user doesn't want to backups in folder compare, return
918 // success so operations don't abort.
919 if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
921 // Likewise if user doesn't want backups in file compare
922 else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
925 // create backup copy of file if destination file exists
926 if (paths_DoesPathExist(pszPath) == IS_EXISTING_FILE)
933 paths_SplitFilename(pszPath, &path, &filename, &ext);
935 // Determine backup folder
936 if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
937 PropBackups::FOLDER_ORIGINAL)
939 // Put backups to same folder than original file
942 else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
943 PropBackups::FOLDER_GLOBAL)
945 // Put backups to global folder defined in options
946 bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
950 bakPath = paths_GetLongPath(bakPath);
954 _RPTF0(_CRT_ERROR, "Unknown backup location!");
957 BOOL success = FALSE;
958 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
960 // Don't add dot if there is no existing extension
963 ext += BACKUP_FILE_EXT;
966 // Append time to filename if wanted so
967 // NOTE just adds timestamp at the moment as I couldn't figure out
968 // nice way to add a real time (invalid chars etc).
969 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
974 tm = localtime(&curtime);
976 timestr.Format(_T("%04d%02d%02d%02d%02d%02d"), tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
981 // Append filename and extension (+ optional .bak) to path
982 if ((bakPath.length() + filename.length() + ext.length())
986 if (!paths_EndsWithSlash(bakPath))
988 bakPath = paths_ConcatPath(bakPath, filename);
994 success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
998 if (ResMsgBox1(IDS_BACKUP_FAILED_PROMPT, pszPath.c_str(),
999 MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN,
1000 IDS_BACKUP_FAILED_PROMPT) != IDYES)
1006 // we got here because we're either not backing up of there was nothing to backup
1011 * @brief Sync file to Version Control System
1012 * @param pszDest [in] Where to copy (incl. filename)
1013 * @param bApplyToAll [in,out] Apply user selection to all items
1014 * @param psError [out] Error string that can be shown to user in caller func
1015 * @return User selection or -1 if error happened
1016 * @sa CMainFrame::HandleReadonlySave()
1017 * @sa CDirView::PerformActionList()
1019 int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
1022 String sActionError;
1023 String strSavePath(pszDest);
1026 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
1028 int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
1029 if (nRetVal == IDCANCEL || nRetVal == IDNO)
1032 // If VC project opened from VSS sync and version control used
1033 if ((nVerSys == VCS_VSS4 || nVerSys == VCS_VSS5) && m_bVCProjSync)
1035 if (!m_pVssHelper->ReLinkVCProj(strSavePath, sError))
1042 * @brief Checks if path (file/folder) is read-only and asks overwriting it.
1044 * @param strSavePath [in,out] Path where to save (file or folder)
1045 * @param bMultiFile [in] Single file or multiple files/folder
1046 * @param bApplyToAll [in,out] Apply last user selection for all items?
1047 * @return Users selection:
1048 * - IDOK: Item was not readonly, no actions
1049 * - IDYES/IDYESTOALL: Overwrite readonly item
1050 * - IDNO: User selected new filename (single file) or user wants to skip
1051 * - IDCANCEL: Cancel operation
1052 * @sa CMainFrame::SyncFileToVCS()
1053 * @sa CMergeDoc::DoSave()
1055 int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
1059 UINT userChoice = 0;
1061 BOOL bFileRO = FALSE;
1062 BOOL bFileExists = FALSE;
1070 TFile file(strSavePath);
1071 bFileExists = file.exists();
1073 bFileRO = !file.canWrite();
1078 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
1080 if (bFileExists && bFileRO)
1082 // Version control system used?
1083 // Checkout file from VCS and modify, don't ask about overwriting
1085 if (nVerSys != VCS_NONE)
1087 BOOL bRetVal = SaveToVersionControl(strSavePath);
1094 // Don't ask again if its already asked
1099 // Prompt for user choice
1102 // Multiple files or folder
1103 str = LangFormatString1(IDS_SAVEREADONLY_MULTI, strSavePath.c_str());
1104 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1105 MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
1106 MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
1111 str = LangFormatString1(IDS_SAVEREADONLY_FMT, strSavePath.c_str());
1112 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1113 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
1114 IDS_SAVEREADONLY_FMT);
1119 // Overwrite read-only file
1121 bApplyToAll = TRUE; // Don't ask again (no break here)
1123 CFile::GetStatus(strSavePath.c_str(), status);
1124 status.m_mtime = 0; // Avoid unwanted changes
1125 status.m_attribute &= ~CFile::readOnly;
1126 CFile::SetStatus(strSavePath.c_str(), status);
1130 // Save to new filename (single) /skip this item (multiple)
1134 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), IDS_SAVE_AS_TITLE, NULL, FALSE))
1156 * @brief Shows VSS error from exception and writes log.
1158 void CMergeApp::ShowVSSError(CException *e, const String& strItem)
1160 TCHAR errStr[1024] = {0};
1161 if (e->GetErrorMessage(errStr, 1024))
1163 String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
1164 String logMsg = errMsg;
1169 if (!strItem.empty())
1171 errMsg += _T("\n\n");
1176 LogErrorString(logMsg);
1177 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
1181 LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
1182 e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
1187 * @brief Is specified file a project file?
1188 * @param [in] filepath Full path to file to check.
1189 * @return true if file is a projectfile.
1191 bool CMergeApp::IsProjectFile(const String& filepath) const
1194 paths_SplitFilename(filepath, NULL, NULL, &ext);
1195 if (string_compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1201 bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
1203 if (sProject.empty())
1208 project.Read(sProject);
1210 catch (Poco::Exception& e)
1212 String sErr = LoadString(IDS_UNK_ERROR_READING_PROJECT);
1213 sErr += ucr::toTString(e.displayText());
1214 String msg = LangFormatString2(IDS_ERROR_FILEOPEN, sProject.c_str(), sErr.c_str());
1215 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1222 bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &project)
1226 project.Save(sProject);
1228 catch (Poco::Exception& e)
1230 String sErr = LoadString(IDS_UNK_ERROR_SAVING_PROJECT);
1231 sErr += ucr::toTString(e.displayText());
1232 String msg = LangFormatString2(IDS_ERROR_FILEOPEN, sProject.c_str(), sErr.c_str());
1233 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1241 * @brief Read project and perform comparison specified
1242 * @param [in] sProject Full path to project file.
1243 * @return TRUE if loading project file and starting compare succeeded.
1245 bool CMergeApp::LoadAndOpenProjectFile(const String& sProject)
1247 ProjectFile project;
1248 if (!LoadProjectFile(sProject, project))
1252 BOOL bLeftReadOnly = FALSE;
1253 BOOL bMiddleReadOnly = FALSE;
1254 BOOL bRightReadOnly = FALSE;
1255 bool bRecursive = FALSE;
1256 project.GetPaths(files, bRecursive);
1257 bLeftReadOnly = project.GetLeftReadOnly();
1258 bMiddleReadOnly = project.GetMiddleReadOnly();
1259 bRightReadOnly = project.GetRightReadOnly();
1260 if (project.HasFilter())
1262 String filter = project.GetFilter();
1263 filter = string_trim_ws(filter);
1264 m_pGlobalFileFilter->SetFilter(filter);
1266 if (project.HasSubfolders())
1267 bRecursive = project.GetSubfolders() > 0;
1269 DWORD dwFlags[3] = {
1270 files.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT,
1271 files.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT,
1272 files.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT
1275 dwFlags[0] |= FFILEOPEN_READONLY;
1276 if (files.GetSize() == 2)
1279 dwFlags[1] |= FFILEOPEN_READONLY;
1283 if (bMiddleReadOnly)
1284 dwFlags[1] |= FFILEOPEN_READONLY;
1286 dwFlags[2] |= FFILEOPEN_READONLY;
1289 WriteProfileInt(_T("Settings"), _T("Recurse"), bRecursive);
1291 BOOL rtn = GetMainFrame()->DoFileOpen(&files, dwFlags, bRecursive);
1293 AddToRecentProjectsMRU(sProject.c_str());
1298 * @brief Return windows language ID of current WinMerge GUI language
1300 WORD CMergeApp::GetLangId() const
1302 return m_pLangDlg->GetLangId();
1306 * @brief Lang aware version of CStatusBar::SetIndicators()
1308 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
1310 m_pLangDlg->SetIndicators(sb, rgid, n);
1314 * @brief Translate menu to current WinMerge GUI language
1316 void CMergeApp::TranslateMenu(HMENU h) const
1318 m_pLangDlg->TranslateMenu(h);
1322 * @brief Translate dialog to current WinMerge GUI language
1324 void CMergeApp::TranslateDialog(HWND h) const
1326 m_pLangDlg->TranslateDialog(h);
1330 * @brief Load string and translate to current WinMerge GUI language
1332 String CMergeApp::LoadString(UINT id) const
1334 return m_pLangDlg->LoadString(id);
1338 * @brief Load dialog caption and translate to current WinMerge GUI language
1340 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1342 return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
1346 * @brief Reload main menu(s) (for language change)
1348 void CMergeApp::ReloadMenu()
1350 m_pLangDlg->ReloadMenu();
1354 * @brief Get default editor path.
1355 * @return full path to the editor program executable.
1357 String CMergeApp::GetDefaultEditor()
1359 String path = env_GetWindowsDirectory();
1360 path += _T("\\NOTEPAD.EXE");
1365 * @brief Get default user filter folder path.
1366 * This function returns the default filter path for user filters.
1367 * If wanted so (@p bCreate) path can be created if it does not
1368 * exist yet. But you really want to create the patch only when
1369 * there is no user path defined.
1370 * @param [in] bCreate If TRUE filter path is created if it does
1372 * @return Default folder for user filters.
1374 String CMergeApp::GetDefaultFilterUserPath(BOOL bCreate /*=FALSE*/)
1376 String pathMyFolders = env_GetMyDocuments();
1377 String pathFilters(pathMyFolders);
1378 pathFilters = paths_ConcatPath(pathFilters, DefaultRelativeFilterPath);
1380 if (bCreate && !paths_CreateIfNeeded(pathFilters))
1382 // Failed to create a folder, check it didn't already
1384 DWORD errCode = GetLastError();
1385 if (errCode != ERROR_ALREADY_EXISTS)
1387 // Failed to create a folder for filters, fallback to
1388 // "My Documents"-folder. It is not worth the trouble to
1389 // bother user about this or user more clever solutions.
1390 pathFilters = pathMyFolders;
1398 * @brief Adds specified file to the recent projects list.
1399 * @param [in] sPathName Path to project file
1401 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
1403 // sPathName will be added to the top of the MRU list.
1404 // If sPathName already exists in the MRU list, it will be moved to the top
1405 if (m_pRecentFileList != NULL) {
1406 m_pRecentFileList->Add(sPathName);
1407 m_pRecentFileList->WriteList();
1412 * @brief Handles menu selection from recent projects list
1413 * @param [in] nID Menu ID of the selected item
1415 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
1417 return LoadAndOpenProjectFile((const TCHAR *)m_pRecentFileList->m_arrNames[nID-ID_FILE_MRU_FILE1]);