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.
30 #include "Constants.h"
31 #include "UnicodeString.h"
33 #include "Environment.h"
34 #include "OptionsMgr.h"
35 #include "OptionsInit.h"
36 #include "RegOptionsMgr.h"
40 #include "HexMergeDoc.h"
41 #include "HexMergeFrm.h"
42 #include "HexMergeView.h"
50 #include "PropBackups.h"
51 #include "FileOrFolderSelect.h"
53 #include "FileFilterHelper.h"
54 #include "LineFiltersList.h"
55 #include "FilterCommentsManager.h"
56 #include "SyntaxColors.h"
57 #include "CCrystalTextMarkers.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"
67 #include "stringdiffs.h"
69 #include "SourceControl.h"
71 #include "CompareStats.h"
74 // For shutdown cleanup
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 StringCchPrintf(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_HELP, OnHelp)
160 ON_COMMAND_EX_RANGE(ID_FILE_PROJECT_MRU_FIRST, ID_FILE_PROJECT_MRU_LAST, OnOpenRecentFile)
161 ON_UPDATE_COMMAND_UI(ID_FILE_PROJECT_MRU_FIRST, &CWinApp::OnUpdateRecentFileMenu)
162 ON_COMMAND(ID_FILE_MERGINGMODE, OnMergingMode)
163 ON_UPDATE_COMMAND_UI(ID_FILE_MERGINGMODE, OnUpdateMergingMode)
164 ON_UPDATE_COMMAND_UI(ID_STATUS_MERGINGMODE, OnUpdateMergingStatus)
166 // Standard file based document commands
167 //ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
168 //ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
169 // Standard print setup command
170 ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
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())
206 , m_bEscShutdown(FALSE)
207 , m_bClearCaseTool(FALSE)
208 , m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
209 , m_pLineFilters(new LineFiltersList())
210 , m_pFilterCommentsManager(new FilterCommentsManager())
211 , m_pSyntaxColors(new SyntaxColors())
212 , m_pMarkers(new CCrystalTextMarkers())
213 , m_pSourceControl(new SourceControl())
214 , m_bMergingMode(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 // Load registry keys from WinMerge.reg if existing WinMerge.reg
286 env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
288 // Parse command-line arguments.
289 MergeCmdLineInfo cmdInfo(GetCommandLine());
290 if (cmdInfo.m_bNoPrefs)
291 m_pOptions->SetSerializing(false); // Turn off serializing to registry.
293 Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
295 for (const auto& it : cmdInfo.m_Options)
296 m_pOptions->Set(it.first, it.second);
298 // Initialize temp folder
301 // If paths were given to commandline we consider this being an invoke from
302 // commandline (from other application, shellextension etc).
303 BOOL bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
305 // WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
306 // This is the name of the company of the original author (Dean Grimm)
307 SetRegistryKey(_T("Thingamahoochie"));
309 BOOL bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
310 (true == cmdInfo.m_bSingleInstance);
312 // Create exclusion mutex name
313 TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
314 DWORD dwLengthNeeded;
315 GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME,
316 szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
317 TCHAR szMutexName[MAX_PATH + 40];
318 // Combine window class name and desktop name to form a unique mutex name.
319 // As the window class name is decorated to distinguish between ANSI and
320 // UNICODE build, so will be the mutex name.
321 wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
322 HANDLE hMutex = CreateMutex(NULL, FALSE, szMutexName);
324 WaitForSingleObject(hMutex, INFINITE);
325 if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
327 // Activate previous instance and send commandline to it
328 HWND hWnd = FindWindow(CMainFrame::szClassName, NULL);
332 ShowWindow(hWnd, SW_RESTORE);
333 SetForegroundWindow(GetLastActivePopup(hWnd));
334 LPTSTR cmdLine = GetCommandLine();
335 COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
336 if (SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
338 ReleaseMutex(hMutex);
345 LoadStdProfileSettings(GetOptionsMgr()->GetInt(OPT_MRU_MAX)); // Load standard INI file options (including MRU)
347 InitializeFileFilters();
349 // Read last used filter from registry
350 // If filter fails to set, reset to default
351 const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
352 BOOL bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
355 String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
356 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
360 UpdateCodepageModule();
362 if (m_pSourceControl)
363 m_pSourceControl->InitializeSourceControlMembers();
365 FileTransform::g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
366 FileTransform::g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
368 NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
369 if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
371 const int lfHeight = -MulDiv(9, CClientDC(CWnd::GetDesktopWindow()).GetDeviceCaps(LOGPIXELSY), 72);
372 if (abs(ncm.lfMenuFont.lfHeight) > abs(lfHeight))
373 ncm.lfMenuFont.lfHeight = lfHeight;
374 if (wcscmp(ncm.lfMenuFont.lfFaceName, L"Meiryo") == 0 || wcscmp(ncm.lfMenuFont.lfFaceName, L"\U000030e1\U000030a4\U000030ea\U000030aa"/* "Meiryo" in Japanese */) == 0)
375 wcscpy_s(ncm.lfMenuFont.lfFaceName, L"Meiryo UI");
376 m_fontGUI.CreateFontIndirect(&ncm.lfMenuFont);
380 Options::SyntaxColors::Load(GetOptionsMgr(), m_pSyntaxColors.get());
383 m_pMarkers->LoadFromRegistry();
386 m_pLineFilters->Initialize(GetOptionsMgr());
388 // If there are no filters loaded, and there is filter string in previous
389 // option string, import old filters to new place.
390 if (m_pLineFilters->GetCount() == 0)
392 String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
393 if (!oldFilter.empty())
394 m_pLineFilters->Import(oldFilter);
397 // Check if filter folder is set, and create it if not
398 String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
399 if (pathMyFolders.empty())
401 // No filter path, set it to default and make sure it exists.
402 pathMyFolders = GetOptionsMgr()->GetDefault<String>(OPT_FILTER_USERPATH);
403 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathMyFolders);
404 theApp.m_pGlobalFileFilter->SetUserFilterPath(pathMyFolders);
406 if (!paths::CreateIfNeeded(pathMyFolders))
408 // Failed to create a folder, check it didn't already
410 DWORD errCode = GetLastError();
411 if (errCode != ERROR_ALREADY_EXISTS)
413 // Failed to create a folder for filters, fallback to
414 // "My Documents"-folder. It is not worth the trouble to
415 // bother user about this or user more clever solutions.
416 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, env::GetMyDocuments());
420 strdiff::Init(); // String diff init
421 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
423 m_bMergingMode = GetOptionsMgr()->GetBool(OPT_MERGE_MODE);
425 // Initialize i18n (multiple language) support
427 m_pLangDlg->InitializeLanguage((WORD)GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
429 m_mainThreadScripts = new CAssureScriptsForThread;
431 // Register the application's document templates. Document templates
432 // serve as the connection between documents, frame windows and views.
435 m_pOpenTemplate = new CMultiDocTemplate(
437 RUNTIME_CLASS(COpenDoc),
438 RUNTIME_CLASS(COpenFrame), // custom MDI child frame
439 RUNTIME_CLASS(COpenView));
440 AddDocTemplate(m_pOpenTemplate);
443 m_pDiffTemplate = new CMultiDocTemplate(
445 RUNTIME_CLASS(CMergeDoc),
446 RUNTIME_CLASS(CChildFrame), // custom MDI child frame
447 RUNTIME_CLASS(CMergeEditView));
448 AddDocTemplate(m_pDiffTemplate);
451 m_pHexMergeTemplate = new CMultiDocTemplate(
453 RUNTIME_CLASS(CHexMergeDoc),
454 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
455 RUNTIME_CLASS(CHexMergeView));
456 AddDocTemplate(m_pHexMergeTemplate);
459 m_pDirTemplate = new CMultiDocTemplate(
461 RUNTIME_CLASS(CDirDoc),
462 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
463 RUNTIME_CLASS(CDirView));
464 AddDocTemplate(m_pDirTemplate);
466 // create main MDI Frame window
467 CMainFrame* pMainFrame = new CMainFrame;
468 if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
472 ReleaseMutex(hMutex);
477 m_pMainWnd = pMainFrame;
479 // Init menus -- hMenuDefault is for MainFrame
480 pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
483 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
484 CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
485 pMainFrame->MDISetMenu(pNewMenu, NULL);
487 // The main window has been initialized, so activate and update it.
488 pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
489 pMainFrame->UpdateWindow();
491 // Since this function actually opens paths for compare it must be
492 // called after initializing CMainFrame!
493 BOOL bContinue = TRUE;
494 if (ParseArgsAndDoOpen(cmdInfo, pMainFrame) == FALSE && bCommandLineInvoke)
498 ReleaseMutex(hMutex);
500 // If user wants to cancel the compare, close WinMerge
501 if (bContinue == FALSE)
503 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
507 WinMergeTest::TestAll();
513 static void OpenContributersFile(int&)
515 theApp.OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
518 // App command to run the dialog
519 void CMergeApp::OnAppAbout()
522 aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
524 aboutDlg.m_onclick_contributers.clear();
527 /////////////////////////////////////////////////////////////////////////////
528 // CMergeApp commands
531 * @brief Called when application is about to exit.
532 * This functions is called when application is exiting, so this is
533 * good place to do cleanups.
534 * @return Application's exit value (returned from WinMain()).
536 int CMergeApp::ExitInstance()
540 // Save registry keys if existing WinMerge.reg
541 env::SaveRegistryToFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")), RegDir);
544 const String temp = env::GetTemporaryPath();
545 ClearTempfolder(temp);
547 // Cleanup left over tempfiles from previous instances.
548 // Normally this should not neet to do anything - but if for some reason
549 // WinMerge did not delete temp files this makes sure they are removed.
552 delete m_mainThreadScripts;
553 CWinApp::ExitInstance();
557 int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
559 // This is a convenient point for breakpointing !!!
561 // Create a handle to store the parent window of the message box.
562 CWnd* pParentWnd = CWnd::GetActiveWindow();
564 // Check whether an active window was retrieved successfully.
565 if ( pParentWnd == NULL )
567 // Try to retrieve a handle to the last active popup.
568 CWnd * mainwnd = GetMainWnd();
570 pParentWnd = mainwnd->GetLastActivePopup();
573 // Use our own message box implementation, which adds the
574 // do not show again checkbox, and implements it on subsequent calls
575 // (if caller set the style)
577 if (m_bNonInteractive)
580 // Create the message box dialog.
581 CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
584 if (m_pMainWnd->IsIconic())
585 m_pMainWnd->ShowWindow(SW_RESTORE);
587 // Display the message box dialog and return the result.
588 return static_cast<int>(dlgMessage.DoModal());
592 * @brief Set flag so that application will broadcast notification at next
593 * idle time (via WM_TIMER id=IDLE_TIMER)
595 void CMergeApp::SetNeedIdleTimer()
597 m_bNeedIdleTimer = TRUE;
600 bool CMergeApp::IsReallyIdle() const
603 POSITION pos = m_pDirTemplate->GetFirstDocPosition();
606 CDirDoc *pDirDoc = static_cast<CDirDoc *>(m_pDirTemplate->GetNextDoc(pos));
607 if (const CompareStats *pCompareStats = pDirDoc->GetCompareStats())
609 if (!pCompareStats->IsCompareDone() || pDirDoc->GetGeneratingReport())
616 BOOL CMergeApp::OnIdle(LONG lCount)
618 if (CWinApp::OnIdle(lCount))
621 // If anyone has requested notification when next idle occurs, send it
622 if (m_bNeedIdleTimer)
624 m_bNeedIdleTimer = FALSE;
625 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
628 if (m_bNonInteractive && IsReallyIdle())
629 m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
635 * @brief Load any known file filters.
637 * This function loads filter files from paths we know contain them.
638 * @note User's filter location may not be set yet.
640 void CMergeApp::InitializeFileFilters()
642 CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
644 if (!filterPath.IsEmpty())
646 m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
648 m_pGlobalFileFilter->LoadAllFileFilters();
651 /** @brief Read command line arguments and open files for comparison.
653 * The name of the function is a legacy code from the time that this function
654 * actually parsed the command line. Today the parsing is done using the
655 * MergeCmdLineInfo class.
656 * @param [in] cmdInfo Commandline parameters info.
657 * @param [in] pMainFrame Pointer to application main frame.
658 * @return TRUE if we opened the compare, FALSE if the compare was canceled.
660 BOOL CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
662 BOOL bCompared = FALSE;
664 m_bNonInteractive = cmdInfo.m_bNonInteractive;
666 // Set the global file filter.
667 if (!cmdInfo.m_sFileFilter.empty())
669 m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter);
673 if (cmdInfo.m_nCodepage)
675 UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
678 // Unless the user has requested to see WinMerge's usage open files for
680 if (cmdInfo.m_bShowUsage)
682 ShowHelp(CommandLineHelpLocation);
686 // Set the required information we need from the command line:
688 m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
689 m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
690 m_bEscShutdown = cmdInfo.m_bEscShutdown;
692 m_strSaveAsPath = cmdInfo.m_sOutputpath;
694 strDesc[0] = cmdInfo.m_sLeftDesc;
695 if (cmdInfo.m_Files.GetSize() < 3)
697 strDesc[1] = cmdInfo.m_sRightDesc;
701 strDesc[1] = cmdInfo.m_sMiddleDesc;
702 strDesc[2] = cmdInfo.m_sRightDesc;
705 if (cmdInfo.m_Files.GetSize() > 2)
707 cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
708 cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
709 cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
710 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
711 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
712 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
713 cmdInfo.m_sPreDiffer);
715 else if (cmdInfo.m_Files.GetSize() > 1)
717 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
718 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
719 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
720 cmdInfo.m_sPreDiffer);
722 else if (cmdInfo.m_Files.GetSize() == 1)
724 String sFilepath = cmdInfo.m_Files[0];
725 if (IsProjectFile(sFilepath))
727 bCompared = LoadAndOpenProjectFile(sFilepath);
729 else if (IsConflictFile(sFilepath))
731 //For a conflict file, load the descriptions in their respective positions: (they will be reordered as needed)
732 strDesc[0] = cmdInfo.m_sLeftDesc;
733 strDesc[1] = cmdInfo.m_sMiddleDesc;
734 strDesc[2] = cmdInfo.m_sRightDesc;
735 bCompared = pMainFrame->DoOpenConflict(sFilepath, strDesc);
739 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
740 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
741 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
742 cmdInfo.m_sPreDiffer);
745 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
747 BOOL showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
749 pMainFrame->DoFileOpen();
755 void CMergeApp::UpdateDefaultCodepage(int cpDefaultMode, int cpCustomCodepage)
759 switch (cpDefaultMode)
762 ucr::setDefaultCodepage(GetACP());
766 wLangId = GetLangId();
767 if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
768 ucr::setDefaultCodepage(_ttol(buff));
770 ucr::setDefaultCodepage(GetACP());
773 ucr::setDefaultCodepage(cpCustomCodepage);
776 // no other valid option
778 ucr::setDefaultCodepage(GetACP());
783 * @brief Send current option settings into codepage module
785 void CMergeApp::UpdateCodepageModule()
787 // Get current codepage settings from the options module
788 // and push them into the codepage module
789 UpdateDefaultCodepage(GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_MODE), GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_CUSTOM));
792 /** @brief Open help from mainframe when user presses F1*/
793 void CMergeApp::OnHelp()
799 * @brief Open given file to external editor specified in options.
800 * @param [in] file Full path to file to open.
802 * Opens file to defined (in Options/system), Notepad by default,
803 * external editor. Path is decorated with quotation marks if needed
804 * (contains spaces). Also '$file' in editor path is replaced by
806 * @param [in] file Full path to file to open.
807 * @param [in] nLineNumber Line number to go to.
809 void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
811 String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
813 strutils::replace(sCmd, _T("$linenum"), strutils::to_str(nLineNumber));
815 size_t nIndex = sCmd.find(_T("$file"));
816 if (nIndex != String::npos)
818 sFile.insert(0, _T("\""));
819 strutils::replace(sCmd, _T("$file"), sFile);
820 nIndex = sCmd.find(' ', nIndex + sFile.length());
821 if (nIndex != String::npos)
822 sCmd.insert(nIndex, _T("\""));
834 STARTUPINFO stInfo = { sizeof STARTUPINFO };
835 PROCESS_INFORMATION processInfo;
837 retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
838 NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
839 &stInfo, &processInfo);
843 // Error invoking external editor
844 String msg = strutils::format_string1(_("Failed to execute external editor: %1"), sCmd);
845 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
849 CloseHandle(processInfo.hThread);
850 CloseHandle(processInfo.hProcess);
855 * @brief Open file, if it exists, else open url
857 void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
859 if (paths::DoesPathExist(szFile) == paths::IS_EXISTING_FILE)
860 ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
862 ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
866 * @brief Show Help - this is for opening help from outside mainframe.
867 * @param [in] helpLocation Location inside help, if NULL main help is opened.
869 void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
871 String sPath = env::GetProgPath();
872 LANGID LangId = GetLangId();
873 sPath = paths::ConcatPath(sPath, DocsPath);
874 if (helpLocation == NULL)
876 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
877 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
879 ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
883 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
885 sPath += helpLocation;
886 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
892 * @brief Creates backup before file is saved or copied over.
893 * This function handles formatting correct path and filename for
894 * backup file. Formatting is done based on several options available
895 * for users in Options/Backups dialog. After path is formatted, file
896 * is simply just copied. Not much error checking, just if copying
897 * succeeded or failed.
898 * @param [in] bFolder Are we creating backup in folder compare?
899 * @param [in] pszPath Full path to file to backup.
900 * @return TRUE if backup succeeds, or isn't just done.
902 BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
904 // If user doesn't want to backups in folder compare, return
905 // success so operations don't abort.
906 if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
908 // Likewise if user doesn't want backups in file compare
909 else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
912 // create backup copy of file if destination file exists
913 if (paths::DoesPathExist(pszPath) == paths::IS_EXISTING_FILE)
920 paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
922 // Determine backup folder
923 if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
924 PropBackups::FOLDER_ORIGINAL)
926 // Put backups to same folder than original file
929 else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
930 PropBackups::FOLDER_GLOBAL)
932 // Put backups to global folder defined in options
933 bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
937 bakPath = paths::GetLongPath(bakPath);
941 _RPTF0(_CRT_ERROR, "Unknown backup location!");
944 BOOL success = FALSE;
945 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
947 // Don't add dot if there is no existing extension
950 ext += BACKUP_FILE_EXT;
953 // Append time to filename if wanted so
954 // NOTE just adds timestamp at the moment as I couldn't figure out
955 // nice way to add a real time (invalid chars etc).
956 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
961 ::localtime_s(&tm, &curtime);
963 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);
968 // Append filename and extension (+ optional .bak) to path
969 if ((bakPath.length() + filename.length() + ext.length())
973 bakPath = paths::ConcatPath(bakPath, filename);
979 success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
983 String msg = strutils::format_string1(
984 _("Unable to backup original file:\n%1\n\nContinue anyway?"),
986 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
992 // we got here because we're either not backing up of there was nothing to backup
997 * @brief Sync file to Version Control System
998 * @param pszDest [in] Where to copy (incl. filename)
999 * @param bApplyToAll [in,out] Apply user selection to all items
1000 * @param psError [out] Error string that can be shown to user in caller func
1001 * @return User selection or -1 if error happened
1002 * @sa CMainFrame::HandleReadonlySave()
1003 * @sa CDirView::PerformActionList()
1005 int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
1008 String sActionError;
1009 String strSavePath(pszDest);
1012 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
1014 int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
1015 if (nRetVal == IDCANCEL || nRetVal == IDNO)
1018 // If VC project opened from VSS sync and version control used
1019 if ((nVerSys == SourceControl::VCS_VSS4 || nVerSys == SourceControl::VCS_VSS5) && m_pSourceControl->m_bVCProjSync)
1021 if (!m_pSourceControl->m_vssHelper.ReLinkVCProj(strSavePath, sError))
1028 * @brief Checks if path (file/folder) is read-only and asks overwriting it.
1030 * @param strSavePath [in,out] Path where to save (file or folder)
1031 * @param bMultiFile [in] Single file or multiple files/folder
1032 * @param bApplyToAll [in,out] Apply last user selection for all items?
1033 * @return Users selection:
1034 * - IDOK: Item was not readonly, no actions
1035 * - IDYES/IDYESTOALL: Overwrite readonly item
1036 * - IDNO: User selected new filename (single file) or user wants to skip
1037 * - IDCANCEL: Cancel operation
1038 * @sa CMainFrame::SyncFileToVCS()
1039 * @sa CMergeDoc::DoSave()
1041 int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
1046 BOOL bFileRO = FALSE;
1047 BOOL bFileExists = FALSE;
1053 if (!strSavePath.empty())
1057 TFile file(strSavePath);
1058 bFileExists = file.exists();
1060 bFileRO = !file.canWrite();
1067 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
1069 if (bFileExists && bFileRO)
1071 UINT userChoice = 0;
1072 // Version control system used?
1073 // Checkout file from VCS and modify, don't ask about overwriting
1075 if (nVerSys != SourceControl::VCS_NONE)
1077 bool bRetVal = m_pSourceControl->SaveToVersionControl(strSavePath);
1084 // Don't ask again if its already asked
1089 // Prompt for user choice
1092 // Multiple files or folder
1093 str = strutils::format_string1(_("%1\nis marked read-only. Would you like to override the read-only item?"), strSavePath);
1094 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1095 MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
1096 MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
1101 str = strutils::format_string1(_("%1 is marked read-only. Would you like to override the read-only file ? (No to save as new filename.)"), strSavePath);
1102 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1103 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
1104 IDS_SAVEREADONLY_FMT);
1109 // Overwrite read-only file
1111 bApplyToAll = TRUE; // Don't ask again (no break here)
1113 CFile::GetStatus(strSavePath.c_str(), status);
1114 status.m_mtime = 0; // Avoid unwanted changes
1115 status.m_attribute &= ~CFile::readOnly;
1116 CFile::SetStatus(strSavePath.c_str(), status);
1120 // Save to new filename (single) /skip this item (multiple)
1124 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), _("Save As"), _T(""), FALSE))
1146 * @brief Shows VSS error from exception and writes log.
1148 void CMergeApp::ShowVSSError(CException *e, const String& strItem)
1150 TCHAR errStr[1024] = {0};
1151 if (e->GetErrorMessage(errStr, 1024))
1153 String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
1154 String logMsg = errMsg;
1159 if (!strItem.empty())
1161 errMsg += _T("\n\n");
1166 LogErrorString(logMsg);
1167 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
1171 LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
1172 e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
1177 * @brief Is specified file a project file?
1178 * @param [in] filepath Full path to file to check.
1179 * @return true if file is a projectfile.
1181 bool CMergeApp::IsProjectFile(const String& filepath) const
1184 paths::SplitFilename(filepath, NULL, NULL, &ext);
1185 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1191 bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
1193 if (sProject.empty())
1198 project.Read(sProject);
1200 catch (Poco::Exception& e)
1202 String sErr = _("Unknown error attempting to open project file");
1203 sErr += ucr::toTString(e.displayText());
1204 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1205 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1212 bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &project)
1216 project.Save(sProject);
1218 catch (Poco::Exception& e)
1220 String sErr = _("Unknown error attempting to save project file");
1221 sErr += ucr::toTString(e.displayText());
1222 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1223 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1231 * @brief Read project and perform comparison specified
1232 * @param [in] sProject Full path to project file.
1233 * @return TRUE if loading project file and starting compare succeeded.
1235 bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sReportFile)
1237 ProjectFile project;
1238 if (!LoadProjectFile(sProject, project))
1242 BOOL bLeftReadOnly = FALSE;
1243 BOOL bMiddleReadOnly = FALSE;
1244 BOOL bRightReadOnly = FALSE;
1245 bool bRecursive = FALSE;
1246 project.GetPaths(files, bRecursive);
1247 bLeftReadOnly = project.GetLeftReadOnly();
1248 bMiddleReadOnly = project.GetMiddleReadOnly();
1249 bRightReadOnly = project.GetRightReadOnly();
1250 if (project.HasFilter())
1252 String filter = project.GetFilter();
1253 filter = strutils::trim_ws(filter);
1254 m_pGlobalFileFilter->SetFilter(filter);
1256 if (project.HasSubfolders())
1257 bRecursive = project.GetSubfolders() > 0;
1259 DWORD dwFlags[3] = {
1260 static_cast<DWORD>(files.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1261 static_cast<DWORD>(files.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1262 static_cast<DWORD>(files.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
1265 dwFlags[0] |= FFILEOPEN_READONLY;
1266 if (files.GetSize() == 2)
1269 dwFlags[1] |= FFILEOPEN_READONLY;
1273 if (bMiddleReadOnly)
1274 dwFlags[1] |= FFILEOPEN_READONLY;
1276 dwFlags[2] |= FFILEOPEN_READONLY;
1279 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
1281 BOOL rtn = GetMainFrame()->DoFileOpen(&files, dwFlags, NULL, sReportFile, bRecursive);
1283 AddToRecentProjectsMRU(sProject.c_str());
1288 * @brief Return windows language ID of current WinMerge GUI language
1290 WORD CMergeApp::GetLangId() const
1292 return m_pLangDlg->GetLangId();
1296 * @brief Lang aware version of CStatusBar::SetIndicators()
1298 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
1300 m_pLangDlg->SetIndicators(sb, rgid, n);
1304 * @brief Translate menu to current WinMerge GUI language
1306 void CMergeApp::TranslateMenu(HMENU h) const
1308 m_pLangDlg->TranslateMenu(h);
1312 * @brief Translate dialog to current WinMerge GUI language
1314 void CMergeApp::TranslateDialog(HWND h) const
1316 CWnd *pWnd = CWnd::FromHandle(h);
1317 pWnd->SetFont(const_cast<CFont *>(&m_fontGUI));
1318 pWnd->SendMessageToDescendants(WM_SETFONT, (WPARAM)m_fontGUI.m_hObject, MAKELPARAM(FALSE, 0), TRUE);
1320 m_pLangDlg->TranslateDialog(h);
1324 * @brief Load string and translate to current WinMerge GUI language
1326 String CMergeApp::LoadString(UINT id) const
1328 return m_pLangDlg->LoadString(id);
1331 bool CMergeApp::TranslateString(const std::string& str, String& translated_str) const
1333 return m_pLangDlg->TranslateString(str, translated_str);
1337 * @brief Load dialog caption and translate to current WinMerge GUI language
1339 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1341 return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
1345 * @brief Adds specified file to the recent projects list.
1346 * @param [in] sPathName Path to project file
1348 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
1350 // sPathName will be added to the top of the MRU list.
1351 // If sPathName already exists in the MRU list, it will be moved to the top
1352 if (m_pRecentFileList != NULL) {
1353 m_pRecentFileList->Add(sPathName);
1354 m_pRecentFileList->WriteList();
1358 void CMergeApp::SetupTempPath()
1360 String instTemp = env::GetPerInstanceString(TempFolderPrefix);
1361 if (GetOptionsMgr()->GetBool(OPT_USE_SYSTEM_TEMP_PATH))
1362 env::SetTemporaryPath(paths::ConcatPath(env::GetSystemTempPath(), instTemp));
1364 env::SetTemporaryPath(paths::ConcatPath(GetOptionsMgr()->GetString(OPT_CUSTOM_TEMP_PATH), instTemp));
1368 * @brief Handles menu selection from recent projects list
1369 * @param [in] nID Menu ID of the selected item
1371 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
1373 return LoadAndOpenProjectFile(static_cast<const TCHAR *>(m_pRecentFileList->m_arrNames[nID-ID_FILE_PROJECT_MRU_FIRST]));
1377 * @brief Return if doc is in Merging/Editing mode
1379 bool CMergeApp::GetMergingMode() const
1381 return m_bMergingMode;
1385 * @brief Set doc to Merging/Editing mode
1387 void CMergeApp::SetMergingMode(bool bMergingMode)
1389 m_bMergingMode = bMergingMode;
1390 GetOptionsMgr()->SaveOption(OPT_MERGE_MODE, m_bMergingMode);
1394 * @brief Switch Merging/Editing mode and update
1395 * buffer read-only states accordingly
1397 void CMergeApp::OnMergingMode()
1399 bool bMergingMode = GetMergingMode();
1402 LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN);
1403 SetMergingMode(!bMergingMode);
1407 * @brief Update Menuitem for Merging Mode
1409 void CMergeApp::OnUpdateMergingMode(CCmdUI* pCmdUI)
1411 pCmdUI->Enable(true);
1412 pCmdUI->SetCheck(GetMergingMode());
1416 * @brief Update MergingMode UI in statusbar
1418 void CMergeApp::OnUpdateMergingStatus(CCmdUI *pCmdUI)
1420 String text = theApp.LoadString(IDS_MERGEMODE_MERGING);
1421 pCmdUI->SetText(text.c_str());
1422 pCmdUI->Enable(GetMergingMode());