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"
73 #include "charsets.h" // For shutdown cleanup
79 /** @brief Location for command line help to open. */
80 static const TCHAR CommandLineHelpLocation[] = _T("::/htmlhelp/Command_line.html");
82 /** @brief Backup file extension. */
83 static const TCHAR BACKUP_FILE_EXT[] = _T("bak");
85 /////////////////////////////////////////////////////////////////////////////
88 BEGIN_MESSAGE_MAP(CMergeApp, CWinApp)
89 //{{AFX_MSG_MAP(CMergeApp)
90 ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
91 ON_COMMAND(ID_HELP, OnHelp)
92 ON_COMMAND_EX_RANGE(ID_FILE_PROJECT_MRU_FIRST, ID_FILE_PROJECT_MRU_LAST, OnOpenRecentFile)
93 ON_UPDATE_COMMAND_UI(ID_FILE_PROJECT_MRU_FIRST, CWinApp::OnUpdateRecentFileMenu)
94 ON_COMMAND(ID_FILE_MERGINGMODE, OnMergingMode)
95 ON_UPDATE_COMMAND_UI(ID_FILE_MERGINGMODE, OnUpdateMergingMode)
96 ON_UPDATE_COMMAND_UI(ID_STATUS_MERGINGMODE, OnUpdateMergingStatus)
98 // Standard file based document commands
99 //ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
100 //ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
101 // Standard print setup command
102 ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
105 /////////////////////////////////////////////////////////////////////////////
106 // CMergeApp construction
108 CMergeApp::CMergeApp() :
109 m_bNeedIdleTimer(FALSE)
112 , m_pHexMergeTemplate(0)
114 , m_mainThreadScripts(NULL)
115 , m_nLastCompareResult(0)
116 , m_bNonInteractive(false)
117 , m_pOptions(new CRegOptionsMgr())
118 , m_pGlobalFileFilter(new FileFilterHelper())
119 , m_nActiveOperations(0)
120 , m_pLangDlg(new CLanguageSelect())
121 , m_bEscShutdown(FALSE)
122 , m_bClearCaseTool(FALSE)
123 , m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
124 , m_pLineFilters(new LineFiltersList())
125 , m_pFilterCommentsManager(new FilterCommentsManager())
126 , m_pSyntaxColors(new SyntaxColors())
127 , m_pMarkers(new CCrystalTextMarkers())
128 , m_pSourceControl(new SourceControl())
129 , m_bMergingMode(FALSE)
131 // add construction code here,
132 // Place all significant initialization in InitInstance
135 CMergeApp::~CMergeApp()
139 /////////////////////////////////////////////////////////////////////////////
140 // The one and only CMergeApp object
144 /////////////////////////////////////////////////////////////////////////////
145 // CMergeApp initialization
148 * @brief Initialize WinMerge application instance.
149 * @return TRUE if application initialization succeeds (and we'll run it),
150 * FALSE if something failed and we exit the instance.
151 * @todo We could handle these failure situations more gratefully, i.e. show
152 * at least some error message to the user..
154 BOOL CMergeApp::InitInstance()
156 // Prevents DLL hijacking
157 HMODULE hLibrary = GetModuleHandle(_T("kernel32.dll"));
158 BOOL (WINAPI *pfnSetSearchPathMode)(DWORD) = (BOOL (WINAPI *)(DWORD))GetProcAddress(hLibrary, "SetSearchPathMode");
159 if (pfnSetSearchPathMode)
160 pfnSetSearchPathMode(0x00000001L /*BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE*/ | 0x00008000L /*BASE_SEARCH_PATH_PERMANENT*/);
161 BOOL (WINAPI *pfnSetDllDirectoryA)(LPCSTR) = (BOOL (WINAPI *)(LPCSTR))GetProcAddress(hLibrary, "SetDllDirectoryA");
162 if (pfnSetDllDirectoryA)
163 pfnSetDllDirectoryA("");
165 JumpList::SetCurrentProcessExplicitAppUserModelID(L"Thingamahoochie.WinMerge");
167 InitCommonControls(); // initialize common control library
168 CWinApp::InitInstance(); // call parent class method
170 // Runtime switch so programmer may set this in interactive debugger
174 // get current setting
175 int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
176 // Keep freed memory blocks in the heap's linked list and mark them as freed
177 tmpFlag |= _CRTDBG_DELAY_FREE_MEM_DF;
178 // Call _CrtCheckMemory at every allocation and deallocation request.
179 // WARNING: This slows down WinMerge *A LOT*
180 tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;
181 // Set the new state for the flag
182 _CrtSetDbgFlag( tmpFlag );
185 // CCrystalEdit Drag and Drop functionality needs AfxOleInit.
188 TRACE(_T("AfxOleInitFailed. OLE functionality disabled"));
191 // Standard initialization
192 // If you are not using these features and wish to reduce the size
193 // of your final executable, you should remove from the following
194 // the specific initialization routines you do not need.
196 // Revoke the standard OLE Message Filter to avoid drawing frame while loading files.
197 COleMessageFilter* pOldFilter = AfxOleGetMessageFilter();
198 pOldFilter->Revoke();
200 // Load registry keys from WinMerge.reg if existing WinMerge.reg
201 env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
203 // Parse command-line arguments.
204 MergeCmdLineInfo cmdInfo(GetCommandLine());
205 if (cmdInfo.m_bNoPrefs)
206 m_pOptions->SetSerializing(false); // Turn off serializing to registry.
208 Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
210 for (const auto& it : cmdInfo.m_Options)
211 m_pOptions->Set(it.first, it.second);
213 // Initialize temp folder
216 // If paths were given to commandline we consider this being an invoke from
217 // commandline (from other application, shellextension etc).
218 BOOL bCommandLineInvoke = cmdInfo.m_Files.GetSize() > 0;
220 // WinMerge registry settings are stored under HKEY_CURRENT_USER/Software/Thingamahoochie
221 // This is the name of the company of the original author (Dean Grimm)
222 SetRegistryKey(_T("Thingamahoochie"));
224 BOOL bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
225 (true == cmdInfo.m_bSingleInstance);
227 // Create exclusion mutex name
228 TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
229 DWORD dwLengthNeeded;
230 GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME,
231 szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
232 TCHAR szMutexName[MAX_PATH + 40];
233 // Combine window class name and desktop name to form a unique mutex name.
234 // As the window class name is decorated to distinguish between ANSI and
235 // UNICODE build, so will be the mutex name.
236 wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
237 HANDLE hMutex = CreateMutex(NULL, FALSE, szMutexName);
239 WaitForSingleObject(hMutex, INFINITE);
240 if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
242 // Activate previous instance and send commandline to it
243 HWND hWnd = FindWindow(CMainFrame::szClassName, NULL);
247 ShowWindow(hWnd, SW_RESTORE);
248 SetForegroundWindow(GetLastActivePopup(hWnd));
249 LPTSTR cmdLine = GetCommandLine();
250 COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
251 if (SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
253 ReleaseMutex(hMutex);
260 LoadStdProfileSettings(GetOptionsMgr()->GetInt(OPT_MRU_MAX)); // Load standard INI file options (including MRU)
262 InitializeFileFilters();
264 // Read last used filter from registry
265 // If filter fails to set, reset to default
266 const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
267 BOOL bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
270 String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
271 m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
275 UpdateCodepageModule();
277 if (m_pSourceControl)
278 m_pSourceControl->InitializeSourceControlMembers();
280 FileTransform::g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
281 FileTransform::g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
283 NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
284 if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
286 const int lfHeight = -MulDiv(9, CClientDC(CWnd::GetDesktopWindow()).GetDeviceCaps(LOGPIXELSY), 72);
287 if (abs(ncm.lfMenuFont.lfHeight) > abs(lfHeight))
288 ncm.lfMenuFont.lfHeight = lfHeight;
289 if (wcscmp(ncm.lfMenuFont.lfFaceName, L"Meiryo") == 0 || wcscmp(ncm.lfMenuFont.lfFaceName, L"\U000030e1\U000030a4\U000030ea\U000030aa"/* "Meiryo" in Japanese */) == 0)
290 wcscpy_s(ncm.lfMenuFont.lfFaceName, L"Meiryo UI");
291 m_fontGUI.CreateFontIndirect(&ncm.lfMenuFont);
295 Options::SyntaxColors::Load(GetOptionsMgr(), m_pSyntaxColors.get());
298 m_pMarkers->LoadFromRegistry();
301 m_pLineFilters->Initialize(GetOptionsMgr());
303 // If there are no filters loaded, and there is filter string in previous
304 // option string, import old filters to new place.
305 if (m_pLineFilters->GetCount() == 0)
307 String oldFilter = theApp.GetProfileString(_T("Settings"), _T("RegExps"));
308 if (!oldFilter.empty())
309 m_pLineFilters->Import(oldFilter);
312 // Check if filter folder is set, and create it if not
313 String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
314 if (pathMyFolders.empty())
316 // No filter path, set it to default and make sure it exists.
317 pathMyFolders = GetOptionsMgr()->GetDefault<String>(OPT_FILTER_USERPATH);
318 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathMyFolders);
319 theApp.m_pGlobalFileFilter->SetUserFilterPath(pathMyFolders);
321 if (!paths::CreateIfNeeded(pathMyFolders))
323 // Failed to create a folder, check it didn't already
325 DWORD errCode = GetLastError();
326 if (errCode != ERROR_ALREADY_EXISTS)
328 // Failed to create a folder for filters, fallback to
329 // "My Documents"-folder. It is not worth the trouble to
330 // bother user about this or user more clever solutions.
331 GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, env::GetMyDocuments());
335 strdiff::Init(); // String diff init
336 strdiff::SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
338 m_bMergingMode = GetOptionsMgr()->GetBool(OPT_MERGE_MODE);
340 // Initialize i18n (multiple language) support
342 m_pLangDlg->InitializeLanguage((WORD)GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE));
344 m_mainThreadScripts = new CAssureScriptsForThread;
346 // Register the application's document templates. Document templates
347 // serve as the connection between documents, frame windows and views.
350 m_pOpenTemplate = new CMultiDocTemplate(
352 RUNTIME_CLASS(COpenDoc),
353 RUNTIME_CLASS(COpenFrame), // custom MDI child frame
354 RUNTIME_CLASS(COpenView));
355 AddDocTemplate(m_pOpenTemplate);
358 m_pDiffTemplate = new CMultiDocTemplate(
360 RUNTIME_CLASS(CMergeDoc),
361 RUNTIME_CLASS(CChildFrame), // custom MDI child frame
362 RUNTIME_CLASS(CMergeEditView));
363 AddDocTemplate(m_pDiffTemplate);
366 m_pHexMergeTemplate = new CMultiDocTemplate(
368 RUNTIME_CLASS(CHexMergeDoc),
369 RUNTIME_CLASS(CHexMergeFrame), // custom MDI child frame
370 RUNTIME_CLASS(CHexMergeView));
371 AddDocTemplate(m_pHexMergeTemplate);
374 m_pDirTemplate = new CMultiDocTemplate(
376 RUNTIME_CLASS(CDirDoc),
377 RUNTIME_CLASS(CDirFrame), // custom MDI child frame
378 RUNTIME_CLASS(CDirView));
379 AddDocTemplate(m_pDirTemplate);
381 // create main MDI Frame window
382 CMainFrame* pMainFrame = new CMainFrame;
383 if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
387 ReleaseMutex(hMutex);
392 m_pMainWnd = pMainFrame;
394 // Init menus -- hMenuDefault is for MainFrame
395 pMainFrame->m_hMenuDefault = pMainFrame->NewDefaultMenu();
398 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
399 CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
400 pMainFrame->MDISetMenu(pNewMenu, NULL);
402 // The main window has been initialized, so activate and update it.
403 pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
404 pMainFrame->UpdateWindow();
406 // Since this function actually opens paths for compare it must be
407 // called after initializing CMainFrame!
408 BOOL bContinue = TRUE;
409 if (ParseArgsAndDoOpen(cmdInfo, pMainFrame) == FALSE && bCommandLineInvoke)
413 ReleaseMutex(hMutex);
415 // If user wants to cancel the compare, close WinMerge
416 if (bContinue == FALSE)
418 pMainFrame->PostMessage(WM_CLOSE, 0, 0);
422 WinMergeTest::TestAll();
428 static void OpenContributersFile(int&)
430 theApp.OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
433 // App command to run the dialog
434 void CMergeApp::OnAppAbout()
437 aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
439 aboutDlg.m_onclick_contributers.clear();
442 /////////////////////////////////////////////////////////////////////////////
443 // CMergeApp commands
446 * @brief Called when application is about to exit.
447 * This functions is called when application is exiting, so this is
448 * good place to do cleanups.
449 * @return Application's exit value (returned from WinMain()).
451 int CMergeApp::ExitInstance()
455 // Save registry keys if existing WinMerge.reg
456 env::SaveRegistryToFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")), RegDir);
459 const String temp = env::GetTemporaryPath();
460 ClearTempfolder(temp);
462 // Cleanup left over tempfiles from previous instances.
463 // Normally this should not neet to do anything - but if for some reason
464 // WinMerge did not delete temp files this makes sure they are removed.
467 delete m_mainThreadScripts;
468 CWinApp::ExitInstance();
472 int CMergeApp::DoMessageBox( LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt )
474 // This is a convenient point for breakpointing !!!
476 // Create a handle to store the parent window of the message box.
477 CWnd* pParentWnd = CWnd::GetActiveWindow();
479 // Check whether an active window was retrieved successfully.
480 if ( pParentWnd == NULL )
482 // Try to retrieve a handle to the last active popup.
483 CWnd * mainwnd = GetMainWnd();
485 pParentWnd = mainwnd->GetLastActivePopup();
488 // Use our own message box implementation, which adds the
489 // do not show again checkbox, and implements it on subsequent calls
490 // (if caller set the style)
492 if (m_bNonInteractive)
495 // Create the message box dialog.
496 CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
499 if (m_pMainWnd->IsIconic())
500 m_pMainWnd->ShowWindow(SW_RESTORE);
502 // Display the message box dialog and return the result.
503 return static_cast<int>(dlgMessage.DoModal());
507 * @brief Set flag so that application will broadcast notification at next
508 * idle time (via WM_TIMER id=IDLE_TIMER)
510 void CMergeApp::SetNeedIdleTimer()
512 m_bNeedIdleTimer = TRUE;
515 bool CMergeApp::IsReallyIdle() const
518 POSITION pos = m_pDirTemplate->GetFirstDocPosition();
521 CDirDoc *pDirDoc = static_cast<CDirDoc *>(m_pDirTemplate->GetNextDoc(pos));
522 if (const CompareStats *pCompareStats = pDirDoc->GetCompareStats())
524 if (!pCompareStats->IsCompareDone() || pDirDoc->GetGeneratingReport())
531 BOOL CMergeApp::OnIdle(LONG lCount)
533 if (CWinApp::OnIdle(lCount))
536 // If anyone has requested notification when next idle occurs, send it
537 if (m_bNeedIdleTimer)
539 m_bNeedIdleTimer = FALSE;
540 m_pMainWnd->SendMessageToDescendants(WM_TIMER, IDLE_TIMER, lCount, TRUE, FALSE);
543 if (m_bNonInteractive && IsReallyIdle())
544 m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
550 * @brief Load any known file filters.
552 * This function loads filter files from paths we know contain them.
553 * @note User's filter location may not be set yet.
555 void CMergeApp::InitializeFileFilters()
557 CString filterPath = GetProfileString(_T("Settings"), _T("UserFilterPath"), _T(""));
559 if (!filterPath.IsEmpty())
561 m_pGlobalFileFilter->SetUserFilterPath((LPCTSTR)filterPath);
563 m_pGlobalFileFilter->LoadAllFileFilters();
566 /** @brief Read command line arguments and open files for comparison.
568 * The name of the function is a legacy code from the time that this function
569 * actually parsed the command line. Today the parsing is done using the
570 * MergeCmdLineInfo class.
571 * @param [in] cmdInfo Commandline parameters info.
572 * @param [in] pMainFrame Pointer to application main frame.
573 * @return TRUE if we opened the compare, FALSE if the compare was canceled.
575 BOOL CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainFrame)
577 BOOL bCompared = FALSE;
579 m_bNonInteractive = cmdInfo.m_bNonInteractive;
581 // Set the global file filter.
582 if (!cmdInfo.m_sFileFilter.empty())
584 m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter);
588 if (cmdInfo.m_nCodepage)
590 UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
593 // Unless the user has requested to see WinMerge's usage open files for
595 if (cmdInfo.m_bShowUsage)
597 ShowHelp(CommandLineHelpLocation);
601 // Set the required information we need from the command line:
603 m_bClearCaseTool = cmdInfo.m_bClearCaseTool;
604 m_bExitIfNoDiff = cmdInfo.m_bExitIfNoDiff;
605 m_bEscShutdown = cmdInfo.m_bEscShutdown;
607 m_strSaveAsPath = cmdInfo.m_sOutputpath;
609 strDesc[0] = cmdInfo.m_sLeftDesc;
610 if (cmdInfo.m_Files.GetSize() < 3)
612 strDesc[1] = cmdInfo.m_sRightDesc;
616 strDesc[1] = cmdInfo.m_sMiddleDesc;
617 strDesc[2] = cmdInfo.m_sRightDesc;
620 if (cmdInfo.m_Files.GetSize() > 2)
622 cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
623 cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
624 cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
625 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
626 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
627 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
628 cmdInfo.m_sPreDiffer);
630 else if (cmdInfo.m_Files.GetSize() > 1)
632 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
633 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
634 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
635 cmdInfo.m_sPreDiffer);
637 else if (cmdInfo.m_Files.GetSize() == 1)
639 String sFilepath = cmdInfo.m_Files[0];
640 if (IsProjectFile(sFilepath))
642 bCompared = LoadAndOpenProjectFile(sFilepath);
644 else if (IsConflictFile(sFilepath))
646 //For a conflict file, load the descriptions in their respective positions: (they will be reordered as needed)
647 strDesc[0] = cmdInfo.m_sLeftDesc;
648 strDesc[1] = cmdInfo.m_sMiddleDesc;
649 strDesc[2] = cmdInfo.m_sRightDesc;
650 bCompared = pMainFrame->DoOpenConflict(sFilepath, strDesc);
654 DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
655 bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
656 dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, NULL,
657 cmdInfo.m_sPreDiffer);
660 else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
662 BOOL showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
664 pMainFrame->DoFileOpen();
670 void CMergeApp::UpdateDefaultCodepage(int cpDefaultMode, int cpCustomCodepage)
674 switch (cpDefaultMode)
677 ucr::setDefaultCodepage(GetACP());
681 wLangId = GetLangId();
682 if (GetLocaleInfo(wLangId, LOCALE_IDEFAULTANSICODEPAGE, buff, sizeof(buff)/sizeof(buff[0])))
683 ucr::setDefaultCodepage(_ttol(buff));
685 ucr::setDefaultCodepage(GetACP());
688 ucr::setDefaultCodepage(cpCustomCodepage);
691 // no other valid option
693 ucr::setDefaultCodepage(GetACP());
698 * @brief Send current option settings into codepage module
700 void CMergeApp::UpdateCodepageModule()
702 // Get current codepage settings from the options module
703 // and push them into the codepage module
704 UpdateDefaultCodepage(GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_MODE), GetOptionsMgr()->GetInt(OPT_CP_DEFAULT_CUSTOM));
707 /** @brief Open help from mainframe when user presses F1*/
708 void CMergeApp::OnHelp()
714 * @brief Open given file to external editor specified in options.
715 * @param [in] file Full path to file to open.
717 * Opens file to defined (in Options/system), Notepad by default,
718 * external editor. Path is decorated with quotation marks if needed
719 * (contains spaces). Also '$file' in editor path is replaced by
721 * @param [in] file Full path to file to open.
722 * @param [in] nLineNumber Line number to go to.
724 void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
726 String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
728 strutils::replace(sCmd, _T("$linenum"), strutils::to_str(nLineNumber));
730 size_t nIndex = sCmd.find(_T("$file"));
731 if (nIndex != String::npos)
733 sFile.insert(0, _T("\""));
734 strutils::replace(sCmd, _T("$file"), sFile);
735 nIndex = sCmd.find(' ', nIndex + sFile.length());
736 if (nIndex != String::npos)
737 sCmd.insert(nIndex, _T("\""));
749 STARTUPINFO stInfo = { sizeof STARTUPINFO };
750 PROCESS_INFORMATION processInfo;
752 retVal = CreateProcess(NULL, (LPTSTR)sCmd.c_str(),
753 NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, NULL,
754 &stInfo, &processInfo);
758 // Error invoking external editor
759 String msg = strutils::format_string1(_("Failed to execute external editor: %1"), sCmd);
760 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
764 CloseHandle(processInfo.hThread);
765 CloseHandle(processInfo.hProcess);
770 * @brief Open file, if it exists, else open url
772 void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
774 if (paths::DoesPathExist(szFile) == paths::IS_EXISTING_FILE)
775 ShellExecute(NULL, _T("open"), _T("notepad.exe"), szFile, NULL, SW_SHOWNORMAL);
777 ShellExecute(NULL, _T("open"), szUrl, NULL, NULL, SW_SHOWNORMAL);
781 * @brief Show Help - this is for opening help from outside mainframe.
782 * @param [in] helpLocation Location inside help, if NULL main help is opened.
784 void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= NULL*/)
786 String sPath = env::GetProgPath();
787 LANGID LangId = GetLangId();
788 sPath = paths::ConcatPath(sPath, DocsPath);
789 if (helpLocation == NULL)
791 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
792 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOC, NULL);
794 ShellExecute(NULL, _T("open"), DocsURL, NULL, NULL, SW_SHOWNORMAL);
798 if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
800 sPath += helpLocation;
801 ::HtmlHelp(NULL, sPath.c_str(), HH_DISPLAY_TOPIC, NULL);
807 * @brief Creates backup before file is saved or copied over.
808 * This function handles formatting correct path and filename for
809 * backup file. Formatting is done based on several options available
810 * for users in Options/Backups dialog. After path is formatted, file
811 * is simply just copied. Not much error checking, just if copying
812 * succeeded or failed.
813 * @param [in] bFolder Are we creating backup in folder compare?
814 * @param [in] pszPath Full path to file to backup.
815 * @return TRUE if backup succeeds, or isn't just done.
817 BOOL CMergeApp::CreateBackup(BOOL bFolder, const String& pszPath)
819 // If user doesn't want to backups in folder compare, return
820 // success so operations don't abort.
821 if (bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FOLDERCMP)))
823 // Likewise if user doesn't want backups in file compare
824 else if (!bFolder && !(GetOptionsMgr()->GetBool(OPT_BACKUP_FILECMP)))
827 // create backup copy of file if destination file exists
828 if (paths::DoesPathExist(pszPath) == paths::IS_EXISTING_FILE)
835 paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
837 // Determine backup folder
838 if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
839 PropBackups::FOLDER_ORIGINAL)
841 // Put backups to same folder than original file
844 else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
845 PropBackups::FOLDER_GLOBAL)
847 // Put backups to global folder defined in options
848 bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
852 bakPath = paths::GetLongPath(bakPath);
856 _RPTF0(_CRT_ERROR, "Unknown backup location!");
859 BOOL success = FALSE;
860 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_BAK))
862 // Don't add dot if there is no existing extension
865 ext += BACKUP_FILE_EXT;
868 // Append time to filename if wanted so
869 // NOTE just adds timestamp at the moment as I couldn't figure out
870 // nice way to add a real time (invalid chars etc).
871 if (GetOptionsMgr()->GetBool(OPT_BACKUP_ADD_TIME))
876 ::localtime_s(&tm, &curtime);
878 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);
883 // Append filename and extension (+ optional .bak) to path
884 if ((bakPath.length() + filename.length() + ext.length())
888 bakPath = paths::ConcatPath(bakPath, filename);
894 success = CopyFile(pszPath.c_str(), bakPath.c_str(), FALSE);
898 String msg = strutils::format_string1(
899 _("Unable to backup original file:\n%1\n\nContinue anyway?"),
901 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
907 // we got here because we're either not backing up of there was nothing to backup
912 * @brief Sync file to Version Control System
913 * @param pszDest [in] Where to copy (incl. filename)
914 * @param bApplyToAll [in,out] Apply user selection to all items
915 * @param psError [out] Error string that can be shown to user in caller func
916 * @return User selection or -1 if error happened
917 * @sa CMainFrame::HandleReadonlySave()
918 * @sa CDirView::PerformActionList()
920 int CMergeApp::SyncFileToVCS(const String& pszDest, BOOL &bApplyToAll,
924 String strSavePath(pszDest);
927 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
929 int nRetVal = HandleReadonlySave(strSavePath, TRUE, bApplyToAll);
930 if (nRetVal == IDCANCEL || nRetVal == IDNO)
933 // If VC project opened from VSS sync and version control used
934 if ((nVerSys == SourceControl::VCS_VSS4 || nVerSys == SourceControl::VCS_VSS5) && m_pSourceControl->m_bVCProjSync)
936 if (!m_pSourceControl->m_vssHelper.ReLinkVCProj(strSavePath, sError))
943 * @brief Checks if path (file/folder) is read-only and asks overwriting it.
945 * @param strSavePath [in,out] Path where to save (file or folder)
946 * @param bMultiFile [in] Single file or multiple files/folder
947 * @param bApplyToAll [in,out] Apply last user selection for all items?
948 * @return Users selection:
949 * - IDOK: Item was not readonly, no actions
950 * - IDYES/IDYESTOALL: Overwrite readonly item
951 * - IDNO: User selected new filename (single file) or user wants to skip
952 * - IDCANCEL: Cancel operation
953 * @sa CMainFrame::SyncFileToVCS()
954 * @sa CMergeDoc::DoSave()
956 int CMergeApp::HandleReadonlySave(String& strSavePath, BOOL bMultiFile,
961 BOOL bFileRO = FALSE;
962 BOOL bFileExists = FALSE;
968 if (!strSavePath.empty())
972 TFile file(strSavePath);
973 bFileExists = file.exists();
975 bFileRO = !file.canWrite();
982 nVerSys = GetOptionsMgr()->GetInt(OPT_VCS_SYSTEM);
984 if (bFileExists && bFileRO)
987 // Version control system used?
988 // Checkout file from VCS and modify, don't ask about overwriting
990 if (nVerSys != SourceControl::VCS_NONE)
992 bool bRetVal = m_pSourceControl->SaveToVersionControl(strSavePath);
999 // Don't ask again if its already asked
1004 // Prompt for user choice
1007 // Multiple files or folder
1008 str = strutils::format_string1(_("%1\nis marked read-only. Would you like to override the read-only item?"), strSavePath);
1009 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1010 MB_ICONWARNING | MB_DEFBUTTON3 | MB_DONT_ASK_AGAIN |
1011 MB_YES_TO_ALL, IDS_SAVEREADONLY_MULTI);
1016 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);
1017 userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
1018 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
1019 IDS_SAVEREADONLY_FMT);
1024 // Overwrite read-only file
1026 bApplyToAll = TRUE; // Don't ask again (no break here)
1028 CFile::GetStatus(strSavePath.c_str(), status);
1029 status.m_mtime = 0; // Avoid unwanted changes
1030 status.m_attribute &= ~CFile::readOnly;
1031 CFile::SetStatus(strSavePath.c_str(), status);
1035 // Save to new filename (single) /skip this item (multiple)
1039 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, strSavePath.c_str(), _("Save As"), _T(""), FALSE))
1061 * @brief Shows VSS error from exception and writes log.
1063 void CMergeApp::ShowVSSError(CException *e, const String& strItem)
1065 TCHAR errStr[1024] = {0};
1066 if (e->GetErrorMessage(errStr, 1024))
1068 String errMsg = theApp.LoadString(IDS_VSS_ERRORFROM);
1069 String logMsg = errMsg;
1074 if (!strItem.empty())
1076 errMsg += _T("\n\n");
1081 LogErrorString(logMsg);
1082 AfxMessageBox(errMsg.c_str(), MB_ICONSTOP);
1086 LogErrorString(_T("VSSError (unable to GetErrorMessage)"));
1087 e->ReportError(MB_ICONSTOP, IDS_VSS_RUN_ERROR);
1092 * @brief Is specified file a project file?
1093 * @param [in] filepath Full path to file to check.
1094 * @return true if file is a projectfile.
1096 bool CMergeApp::IsProjectFile(const String& filepath) const
1099 paths::SplitFilename(filepath, NULL, NULL, &ext);
1100 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1106 bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
1108 if (sProject.empty())
1113 project.Read(sProject);
1115 catch (Poco::Exception& e)
1117 String sErr = _("Unknown error attempting to open project file");
1118 sErr += ucr::toTString(e.displayText());
1119 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1120 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1127 bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &project)
1131 project.Save(sProject);
1133 catch (Poco::Exception& e)
1135 String sErr = _("Unknown error attempting to save project file");
1136 sErr += ucr::toTString(e.displayText());
1137 String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
1138 AfxMessageBox(msg.c_str(), MB_ICONSTOP);
1146 * @brief Read project and perform comparison specified
1147 * @param [in] sProject Full path to project file.
1148 * @return TRUE if loading project file and starting compare succeeded.
1150 bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sReportFile)
1152 ProjectFile project;
1153 if (!LoadProjectFile(sProject, project))
1157 BOOL bLeftReadOnly = FALSE;
1158 BOOL bMiddleReadOnly = FALSE;
1159 BOOL bRightReadOnly = FALSE;
1160 bool bRecursive = FALSE;
1161 project.GetPaths(files, bRecursive);
1162 bLeftReadOnly = project.GetLeftReadOnly();
1163 bMiddleReadOnly = project.GetMiddleReadOnly();
1164 bRightReadOnly = project.GetRightReadOnly();
1165 if (project.HasFilter())
1167 String filter = project.GetFilter();
1168 filter = strutils::trim_ws(filter);
1169 m_pGlobalFileFilter->SetFilter(filter);
1171 if (project.HasSubfolders())
1172 bRecursive = project.GetSubfolders() > 0;
1174 DWORD dwFlags[3] = {
1175 static_cast<DWORD>(files.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1176 static_cast<DWORD>(files.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
1177 static_cast<DWORD>(files.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
1180 dwFlags[0] |= FFILEOPEN_READONLY;
1181 if (files.GetSize() == 2)
1184 dwFlags[1] |= FFILEOPEN_READONLY;
1188 if (bMiddleReadOnly)
1189 dwFlags[1] |= FFILEOPEN_READONLY;
1191 dwFlags[2] |= FFILEOPEN_READONLY;
1194 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
1196 BOOL rtn = GetMainFrame()->DoFileOpen(&files, dwFlags, NULL, sReportFile, bRecursive);
1198 AddToRecentProjectsMRU(sProject.c_str());
1203 * @brief Return windows language ID of current WinMerge GUI language
1205 WORD CMergeApp::GetLangId() const
1207 return m_pLangDlg->GetLangId();
1211 * @brief Lang aware version of CStatusBar::SetIndicators()
1213 void CMergeApp::SetIndicators(CStatusBar &sb, const UINT *rgid, int n) const
1215 m_pLangDlg->SetIndicators(sb, rgid, n);
1219 * @brief Translate menu to current WinMerge GUI language
1221 void CMergeApp::TranslateMenu(HMENU h) const
1223 m_pLangDlg->TranslateMenu(h);
1227 * @brief Translate dialog to current WinMerge GUI language
1229 void CMergeApp::TranslateDialog(HWND h) const
1231 CWnd *pWnd = CWnd::FromHandle(h);
1232 pWnd->SetFont(const_cast<CFont *>(&m_fontGUI));
1233 pWnd->SendMessageToDescendants(WM_SETFONT, (WPARAM)m_fontGUI.m_hObject, MAKELPARAM(FALSE, 0), TRUE);
1235 m_pLangDlg->TranslateDialog(h);
1239 * @brief Load string and translate to current WinMerge GUI language
1241 String CMergeApp::LoadString(UINT id) const
1243 return m_pLangDlg->LoadString(id);
1246 bool CMergeApp::TranslateString(const std::string& str, String& translated_str) const
1248 return m_pLangDlg->TranslateString(str, translated_str);
1252 * @brief Load dialog caption and translate to current WinMerge GUI language
1254 std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
1256 return m_pLangDlg->LoadDialogCaption(lpDialogTemplateID);
1260 * @brief Adds specified file to the recent projects list.
1261 * @param [in] sPathName Path to project file
1263 void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
1265 // sPathName will be added to the top of the MRU list.
1266 // If sPathName already exists in the MRU list, it will be moved to the top
1267 if (m_pRecentFileList != NULL) {
1268 m_pRecentFileList->Add(sPathName);
1269 m_pRecentFileList->WriteList();
1273 void CMergeApp::SetupTempPath()
1275 String instTemp = env::GetPerInstanceString(TempFolderPrefix);
1276 if (GetOptionsMgr()->GetBool(OPT_USE_SYSTEM_TEMP_PATH))
1277 env::SetTemporaryPath(paths::ConcatPath(env::GetSystemTempPath(), instTemp));
1279 env::SetTemporaryPath(paths::ConcatPath(GetOptionsMgr()->GetString(OPT_CUSTOM_TEMP_PATH), instTemp));
1283 * @brief Handles menu selection from recent projects list
1284 * @param [in] nID Menu ID of the selected item
1286 BOOL CMergeApp::OnOpenRecentFile(UINT nID)
1288 return LoadAndOpenProjectFile(static_cast<const TCHAR *>(m_pRecentFileList->m_arrNames[nID-ID_FILE_PROJECT_MRU_FIRST]));
1292 * @brief Return if doc is in Merging/Editing mode
1294 bool CMergeApp::GetMergingMode() const
1296 return m_bMergingMode;
1300 * @brief Set doc to Merging/Editing mode
1302 void CMergeApp::SetMergingMode(bool bMergingMode)
1304 m_bMergingMode = bMergingMode;
1305 GetOptionsMgr()->SaveOption(OPT_MERGE_MODE, m_bMergingMode);
1309 * @brief Switch Merging/Editing mode and update
1310 * buffer read-only states accordingly
1312 void CMergeApp::OnMergingMode()
1314 bool bMergingMode = GetMergingMode();
1317 LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN);
1318 SetMergingMode(!bMergingMode);
1322 * @brief Update Menuitem for Merging Mode
1324 void CMergeApp::OnUpdateMergingMode(CCmdUI* pCmdUI)
1326 pCmdUI->Enable(true);
1327 pCmdUI->SetCheck(GetMergingMode());
1331 * @brief Update MergingMode UI in statusbar
1333 void CMergeApp::OnUpdateMergingStatus(CCmdUI *pCmdUI)
1335 String text = theApp.LoadString(IDS_MERGEMODE_MERGING);
1336 pCmdUI->SetText(text.c_str());
1337 pCmdUI->Enable(GetMergingMode());