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 "OptionsSyntaxColors.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"
68 #include "stringdiffs.h"
70 #include "SourceControl.h"
72 #include "Constants.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 _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_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)
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)
173 * @brief Mapping from command line argument name (eg, ignorews) to WinMerge
174 * option name (eg, Settings/IgnoreSpace).
176 * These arguments take an optional colon and number, like so:
178 * "/ignoreblanklines" (makes WinMerge ignore blank lines)
179 * "/ignoreblanklines:1" (makes WinMerge ignore blank lines)
180 * "/ignoreblanklines:0" (makes WinMerge not ignore blank lines)
185 LPCTSTR WinMergeOptionName;
189 /////////////////////////////////////////////////////////////////////////////
190 // CMergeApp construction
192 CMergeApp::CMergeApp() :
193 m_bNeedIdleTimer(FALSE)
196 , m_pHexMergeTemplate(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)
214 // add construction code here,
215 // Place all significant initialization in InitInstance
218 CMergeApp::~CMergeApp()
222 /////////////////////////////////////////////////////////////////////////////
223 // The one and only CMergeApp object
227 /////////////////////////////////////////////////////////////////////////////
228 // CMergeApp initialization
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..
237 BOOL CMergeApp::InitInstance()
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("");
248 JumpList::SetCurrentProcessExplicitAppUserModelID(L"Thingamahoochie.WinMerge");
250 InitCommonControls(); // initialize common control library
251 CWinApp::InitInstance(); // call parent class method
253 // Runtime switch so programmer may set this in interactive debugger
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 );
268 // CCrystalEdit Drag and Drop functionality needs AfxOleInit.
271 TRACE(_T("AfxOleInitFailed. OLE functionality disabled"));
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.
279 // Revoke the standard OLE Message Filter to avoid drawing frame while loading files.
280 COleMessageFilter* pOldFilter = AfxOleGetMessageFilter();
281 pOldFilter->Revoke();
283 // Load registry keys from WinMerge.reg if existing WinMerge.reg
284 env_LoadRegistryFromFile(paths_ConcatPath(env_GetProgPath(), _T("WinMerge.reg")));
286 Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
288 // Initialize temp folder
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.
296 // Parse command-line arguments.
297 MergeCmdLineInfo cmdInfo(GetCommandLine());
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;
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"));
307 BOOL bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
308 (true == cmdInfo.m_bSingleInstance);
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);
322 WaitForSingleObject(hMutex, INFINITE);
323 if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
325 // Activate previous instance and send commandline to it
326 HWND hWnd = FindWindow(CMainFrame::szClassName, NULL);
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))
336 ReleaseMutex(hMutex);
343 LoadStdProfileSettings(GetOptionsMgr()->GetInt(OPT_MRU_MAX)); // Load standard INI file options (including MRU)
345 InitializeFileFilters();
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());
353 String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
354 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
357 UpdateCodepageModule();
359 if (m_pSourceControl)
360 m_pSourceControl->InitializeSourceControlMembers();
362 g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
363 g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
365 NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
366 if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
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);
379 Options::SyntaxColors::Load(GetOptionsMgr(), m_pSyntaxColors.get());
382 m_pLineFilters->Initialize(GetOptionsMgr());
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)
388 String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
389 if (!oldFilter.empty())
390 m_pLineFilters->Import(oldFilter);
393 // Check if filter folder is set, and create it if not
394 String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
395 if (pathMyFolders.empty())
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());
402 if (!paths_CreateIfNeeded(pathMyFolders))
404 // Failed to create a folder, check it didn't already
406 DWORD errCode = GetLastError();
407 if (errCode != ERROR_ALREADY_EXISTS)
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());
416 sd_Init(); // String diff init
417 sd_SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
419 m_bMergingMode = GetOptionsMgr()->GetBool(OPT_MERGE_MODE);
421 // Initialize i18n (multiple language) support
423 m_pLangDlg->InitializeLanguage((WORD)GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
425 m_mainThreadScripts = new CAssureScriptsForThread;
427 // Register the application's document templates. Document templates
428 // serve as the connection between documents, frame windows and views.
431 m_pOpenTemplate = new CMultiDocTemplate(
433 RUNTIME_CLASS(COpenDoc),
434 RUNTIME_CLASS(COpenFrame), // custom MDI child frame
435 RUNTIME_CLASS(COpenView));
436 AddDocTemplate(m_pOpenTemplate);
439 m_pDiffTemplate = new CMultiDocTemplate(
441 RUNTIME_CLASS(CMergeDoc),
442 RUNTIME_CLASS(CChildFrame), // custom MDI child frame
443 RUNTIME_CLASS(CMergeEditView));
444 AddDocTemplate(m_pDiffTemplate);
447 m_pHexMergeTemplate = new CMultiDocTemplate(
449 RUNTIME_CLASS(CHexMergeDoc),
450 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
451 RUNTIME_CLASS(CHexMergeView));
452 AddDocTemplate(m_pHexMergeTemplate);
455 m_pDirTemplate = new CMultiDocTemplate(
457 RUNTIME_CLASS(CDirDoc),
458 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
459 RUNTIME_CLASS(CDirView));
460 AddDocTemplate(m_pDirTemplate);
462 // create main MDI Frame window
463 CMainFrame* pMainFrame = new CMainFrame;
464 if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
468 ReleaseMutex(hMutex);
473 m_pMainWnd = pMainFrame;
475 // Init menus -- hMenuDefault is for MainFrame
476 pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
479 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
480 CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
481 pMainFrame->MDISetMenu(pNewMenu, NULL);
483 // The main window has been initialized, so activate and update it.
484 pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
485 pMainFrame->UpdateWindow();
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)
494 ReleaseMutex(hMutex);
496 if (m_bNonInteractive)
501 // If user wants to cancel the compare, close WinMerge
502 if (bContinue == FALSE)
504 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
510 static void OpenContributersFile(int&)
512 theApp.OpenFileToExternalEditor(paths_ConcatPath(env_GetProgPath(), ContributorsPath));
515 // App command to run the dialog
516 void CMergeApp::OnAppAbout()
519 aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
521 aboutDlg.m_onclick_contributers.clear();
524 /////////////////////////////////////////////////////////////////////////////
525 // CMergeApp commands
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()).
533 int CMergeApp::ExitInstance()
537 // Save registry keys if existing WinMerge.reg
538 env_SaveRegistryToFile(paths_ConcatPath(env_GetProgPath(), _T("WinMerge.reg")), RegDir);
541 const String temp = env_GetTempPath();
542 ClearTempfolder(temp);
543 delete m_mainThreadScripts;
544 CWinApp::ExitInstance();
548 int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
550 // This is a convenient point for breakpointing !!!
552 // Create a handle to store the parent window of the message box.
553 CWnd* pParentWnd = CWnd::GetActiveWindow();
555 // Check whether an active window was retrieved successfully.
556 if ( pParentWnd == NULL )
558 // Try to retrieve a handle to the last active popup.
559 CWnd * mainwnd = GetMainWnd();
561 pParentWnd = mainwnd->GetLastActivePopup();
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)
568 if (m_bNonInteractive)
571 // Create the message box dialog.
572 CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
575 if (m_pMainWnd->IsIconic())
576 m_pMainWnd->ShowWindow(SW_RESTORE);
578 // Display the message box dialog and return the result.
579 return static_cast<int>(dlgMessage.DoModal());
583 * @brief Set flag so that application will broadcast notification at next
584 * idle time (via WM_TIMER id=IDLE_TIMER)
586 void CMergeApp::SetNeedIdleTimer()
588 m_bNeedIdleTimer = TRUE;
591 BOOL CMergeApp::OnIdle(LONG lCount)
593 if (CWinApp::OnIdle(lCount))
596 // If anyone has requested notification when next idle occurs, send it
597 if (m_bNeedIdleTimer)
599 m_bNeedIdleTimer = FALSE;
600 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
606 * @brief Load any known file filters.
608 * This function loads filter files from paths we know contain them.
609 * @note User's filter location may not be set yet.
611 void CMergeApp::InitializeFileFilters()
613 CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
615 if (!filterPath.IsEmpty())
617 m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
619 m_pGlobalFileFilter->LoadAllFileFilters();
622 /** @brief Read command line arguments and open files for comparison.
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.
631 BOOL CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
633 BOOL bCompared = FALSE;
634 m_bNonInteractive = cmdInfo.m_bNonInteractive;
636 // Set the global file filter.
637 if (!cmdInfo.m_sFileFilter.empty())
639 m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter.c_str());
643 if (cmdInfo.m_nCodepage)
645 UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
648 // Unless the user has requested to see WinMerge's usage open files for
650 if (cmdInfo.m_bShowUsage)
652 ShowHelp(CommandLineHelpLocation);
656 // Set the required information we need from the command line:
658 m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
659 m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
660 m_bEscShutdown = cmdInfo.m_bEscShutdown;
662 m_strSaveAsPath = cmdInfo.m_sOutputpath.c_str();
664 m_strDescriptions[0] = cmdInfo.m_sLeftDesc;
665 if (cmdInfo.m_Files.GetSize() < 3)
667 m_strDescriptions[1] = cmdInfo.m_sRightDesc;
671 m_strDescriptions[1] = cmdInfo.m_sMiddleDesc;
672 m_strDescriptions[2] = cmdInfo.m_sRightDesc;
675 if (cmdInfo.m_Files.GetSize() > 2)
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());
685 else if (cmdInfo.m_Files.GetSize() > 1)
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());
692 else if (cmdInfo.m_Files.GetSize() == 1)
694 String sFilepath = cmdInfo.m_Files[0];
695 if (IsProjectFile(sFilepath))
697 bCompared = LoadAndOpenProjectFile(sFilepath);
699 else if (IsConflictFile(sFilepath))
701 bCompared = pMainFrame->DoOpenConflict(sFilepath.c_str());
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());
711 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
713 BOOL showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
715 pMainFrame->DoFileOpen();
721 void CMergeApp::UpdateDefaultCodepage(int cpDefaultMode, int cpCustomCodepage)
725 switch (cpDefaultMode)
728 ucr::setDefaultCodepage(GetACP());
732 wLangId = GetLangId();
733 if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
734 ucr::setDefaultCodepage(_ttol(buff));
736 ucr::setDefaultCodepage(GetACP());
739 ucr::setDefaultCodepage(cpCustomCodepage);
742 // no other valid option
744 ucr::setDefaultCodepage(GetACP());
749 * @brief Send current option settings into codepage module
751 void CMergeApp::UpdateCodepageModule()
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));
758 /** @brief Open help from mainframe when user presses F1*/
759 void CMergeApp::OnHelp()
765 * @brief Open given file to external editor specified in options.
766 * @param [in] file Full path to file to open.
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
772 * @param [in] file Full path to file to open.
773 * @param [in] nLineNumber Line number to go to.
775 void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
777 String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
779 string_replace(sCmd, _T("$linenum"), string_to_str(nLineNumber));
781 size_t nIndex = sCmd.find(_T("$file"));
782 if (nIndex != String::npos)
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("\""));
800 STARTUPINFO stInfo = {0};
801 stInfo.cb = sizeof(STARTUPINFO);
802 PROCESS_INFORMATION processInfo;
804 retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
805 NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
806 &stInfo, &processInfo);
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);
816 CloseHandle(processInfo.hThread);
817 CloseHandle(processInfo.hProcess);
822 * @brief Open file, if it exists, else open url
824 void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
826 if (paths_DoesPathExist(szFile) == IS_EXISTING_FILE)
827 ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
829 ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
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.
836 void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
838 String sPath = env_GetProgPath();
839 LANGID LangId = GetLangId();
840 if (PRIMARYLANGID(LangId) == LANG_JAPANESE)
841 sPath = paths_ConcatPath(sPath, DocsPath_ja);
843 sPath = paths_ConcatPath(sPath, DocsPath);
844 if (helpLocation == NULL)
846 if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
847 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
849 ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
853 if (paths_DoesPathExist(sPath) == IS_EXISTING_FILE)
855 sPath += helpLocation;
856 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
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.
872 BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
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)))
878 // Likewise if user doesn't want backups in file compare
879 else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
882 // create backup copy of file if destination file exists
883 if (paths_DoesPathExist(pszPath) == IS_EXISTING_FILE)
890 paths_SplitFilename(paths_GetLongPath(pszPath), &path, &filename, &ext);
892 // Determine backup folder
893 if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
894 PropBackups::FOLDER_ORIGINAL)
896 // Put backups to same folder than original file
899 else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
900 PropBackups::FOLDER_GLOBAL)
902 // Put backups to global folder defined in options
903 bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
907 bakPath = paths_GetLongPath(bakPath);
911 _RPTF0(_CRT_ERROR, "Unknown backup location!");
914 BOOL success = FALSE;
915 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
917 // Don't add dot if there is no existing extension
920 ext += BACKUP_FILE_EXT;
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))
931 tm = localtime(&curtime);
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);
938 // Append filename and extension (+ optional .bak) to path
939 if ((bakPath.length() + filename.length() + ext.length())
943 bakPath = paths_ConcatPath(bakPath, filename);
949 success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
953 String msg = string_format_string1(
954 _("Unable to backup original file:\n%1\n\nContinue anyway?"),
956 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
962 // we got here because we're either not backing up of there was nothing to backup
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()
975 int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
979 String strSavePath(pszDest);
982 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
984 int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
985 if (nRetVal == IDCANCEL || nRetVal == IDNO)
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)
991 if (!m_pSourceControl->m_vssHelper.ReLinkVCProj(strSavePath, sError))
998 * @brief Checks if path (file/folder) is read-only and asks overwriting it.
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()
1011 int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
1016 BOOL bFileRO = FALSE;
1017 BOOL bFileExists = FALSE;
1025 TFile file(strSavePath);
1026 bFileExists = file.exists();
1028 bFileRO = !file.canWrite();
1033 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
1035 if (bFileExists && bFileRO)
1037 UINT userChoice = 0;
1038 // Version control system used?
1039 // Checkout file from VCS and modify, don't ask about overwriting
1041 if (nVerSys != SourceControl::VCS_NONE)
1043 bool bRetVal = m_pSourceControl->SaveToVersionControl(strSavePath);
1050 // Don't ask again if its already asked
1055 // Prompt for user choice
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);
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);
1075 // Overwrite read-only file
1077 bApplyToAll = TRUE; // Don't ask again (no break here)
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);
1086 // Save to new filename (single) /skip this item (multiple)
1090 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), _("Save As"), _T(""), FALSE))
1112 * @brief Shows VSS error from exception and writes log.
1114 void CMergeApp::ShowVSSError(CException *e, const String& strItem)
1116 TCHAR errStr[1024] = {0};
1117 if (e->GetErrorMessage(errStr, 1024))
1119 String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
1120 String logMsg = errMsg;
1125 if (!strItem.empty())
1127 errMsg += _T("\n\n");
1132 LogErrorString(logMsg);
1133 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
1137 LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
1138 e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
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.
1147 bool CMergeApp::IsProjectFile(const String& filepath) const
1150 paths_SplitFilename(filepath, NULL, NULL, &ext);
1151 if (string_compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1157 bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
1159 if (sProject.empty())
1164 project.Read(sProject);
1166 catch (Poco::Exception& e)
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);
1178 bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &project)
1182 project.Save(sProject);
1184 catch (Poco::Exception& e)
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);
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.
1201 bool CMergeApp::LoadAndOpenProjectFile(const String& sProject)
1203 ProjectFile project;
1204 if (!LoadProjectFile(sProject, project))
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())
1218 String filter = project.GetFilter();
1219 filter = string_trim_ws(filter);
1220 m_pGlobalFileFilter->SetFilter(filter);
1222 if (project.HasSubfolders())
1223 bRecursive = project.GetSubfolders() > 0;
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
1231 dwFlags[0] |= FFILEOPEN_READONLY;
1232 if (files.GetSize() == 2)
1235 dwFlags[1] |= FFILEOPEN_READONLY;
1239 if (bMiddleReadOnly)
1240 dwFlags[1] |= FFILEOPEN_READONLY;
1242 dwFlags[2] |= FFILEOPEN_READONLY;
1245 WriteProfileInt(_T("Settings"), _T("Recurse"), bRecursive);
1247 BOOL rtn = GetMainFrame()->DoFileOpen(&files, dwFlags, bRecursive);
1249 AddToRecentProjectsMRU(sProject.c_str());
1254 * @brief Return windows language ID of current WinMerge GUI language
1256 WORD CMergeApp::GetLangId() const
1258 return m_pLangDlg->GetLangId();
1262 * @brief Lang aware version of CStatusBar::SetIndicators()
1264 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
1266 m_pLangDlg->SetIndicators(sb, rgid, n);
1270 * @brief Translate menu to current WinMerge GUI language
1272 void CMergeApp::TranslateMenu(HMENU h) const
1274 m_pLangDlg->TranslateMenu(h);
1278 * @brief Translate dialog to current WinMerge GUI language
1280 void CMergeApp::TranslateDialog(HWND h) const
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);
1286 m_pLangDlg->TranslateDialog(h);
1290 * @brief Load string and translate to current WinMerge GUI language
1292 String CMergeApp::LoadString(UINT id) const
1294 return m_pLangDlg->LoadString(id);
1297 bool CMergeApp::TranslateString(const std::string& str, String& translated_str) const
1299 return m_pLangDlg->TranslateString(str, translated_str);
1303 * @brief Load dialog caption and translate to current WinMerge GUI language
1305 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1307 return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
1311 * @brief Adds specified file to the recent projects list.
1312 * @param [in] sPathName Path to project file
1314 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
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();
1324 void CMergeApp::SetupTempPath()
1326 String instTemp = env_GetPerInstanceString(TempFolderPrefix);
1327 if (GetOptionsMgr()->GetBool(OPT_USE_SYSTEM_TEMP_PATH))
1328 env_SetTempPath(paths_ConcatPath(env_GetSystemTempPath(), instTemp));
1330 env_SetTempPath(paths_ConcatPath(GetOptionsMgr()->GetString(OPT_CUSTOM_TEMP_PATH), instTemp));
1334 * @brief Handles menu selection from recent projects list
1335 * @param [in] nID Menu ID of the selected item
1337 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
1339 return LoadAndOpenProjectFile((const TCHAR *)m_pRecentFileList->m_arrNames[nID-ID_FILE_MRU_FILE1]);
1343 * @brief Return if doc is in Merging/Editing mode
1345 bool CMergeApp::GetMergingMode() const
1347 return m_bMergingMode;
1351 * @brief Set doc to Merging/Editing mode
1353 void CMergeApp::SetMergingMode(bool bMergingMode)
1355 m_bMergingMode = bMergingMode;
1356 GetOptionsMgr()->SaveOption(OPT_MERGE_MODE, m_bMergingMode);
1360 * @brief Switch Merging/Editing mode and update
1361 * buffer read-only states accordingly
1363 void CMergeApp::OnMergingMode()
1365 bool bMergingMode = GetMergingMode();
1368 LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN);
1369 SetMergingMode(!bMergingMode);
1373 * @brief Update Menuitem for Merging Mode
1375 void CMergeApp::OnUpdateMergingMode(CCmdUI* pCmdUI)
1377 pCmdUI->Enable(true);
1378 pCmdUI->SetCheck(GetMergingMode());
1382 * @brief Update MergingMode UI in statusbar
1384 void CMergeApp::OnUpdateMergingStatus(CCmdUI *pCmdUI)
1386 String text = theApp.LoadString(IDS_MERGEMODE_MERGING);
1387 pCmdUI->SetText(text.c_str());
1388 pCmdUI->Enable(GetMergingMode());