// WinMerge: an interactive diff/merge utility
// Copyright (C) 1997-2000 Thingamahoochie Software
// Author: Dean Grimm
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-//
+// SPDX-License-Identifier: GPL-2.0-or-later
/////////////////////////////////////////////////////////////////////////////
-/**
+/**
* @file Merge.cpp
*
* @brief Defines the class behaviors for the application.
#include "OptionsMgr.h"
#include "OptionsInit.h"
#include "RegOptionsMgr.h"
+#include "IniOptionsMgr.h"
#include "OpenDoc.h"
#include "OpenFrm.h"
#include "OpenView.h"
#include "DirView.h"
#include "PropBackups.h"
#include "FileOrFolderSelect.h"
-#include "paths.h"
#include "FileFilterHelper.h"
#include "LineFiltersList.h"
-#include "FilterCommentsManager.h"
+#include "SubstitutionFiltersList.h"
#include "SyntaxColors.h"
#include "CCrystalTextMarkers.h"
#include "OptionsSyntaxColors.h"
#include "stringdiffs.h"
#include "TFile.h"
#include "paths.h"
+#include "Shell.h"
#include "CompareStats.h"
#include "TestMain.h"
#include "charsets.h" // For shutdown cleanup
+#include "OptionsProject.h"
#ifdef _DEBUG
#define new DEBUG_NEW
ON_COMMAND(ID_FILE_MERGINGMODE, OnMergingMode)
ON_UPDATE_COMMAND_UI(ID_FILE_MERGINGMODE, OnUpdateMergingMode)
ON_UPDATE_COMMAND_UI(ID_STATUS_MERGINGMODE, OnUpdateMergingStatus)
+ ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
//}}AFX_MSG_MAP
// Standard file based document commands
//ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
//ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
// Standard print setup command
- ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
, m_pHexMergeTemplate(nullptr)
, m_pDirTemplate(nullptr)
, m_mainThreadScripts(nullptr)
-, m_nLastCompareResult(0)
+, m_nLastCompareResult(-1)
, m_bNonInteractive(false)
-, m_pOptions(new CRegOptionsMgr())
-, m_pGlobalFileFilter(new FileFilterHelper())
+, m_pOptions(nullptr)
+, m_pGlobalFileFilter(nullptr)
, m_nActiveOperations(0)
, m_pLangDlg(new CLanguageSelect())
, m_bEscShutdown(false)
-, m_bExitIfNoDiff(MergeCmdLineInfo::Disabled)
+, m_bExitIfNoDiff(MergeCmdLineInfo::ExitNoDiff::Disabled)
, m_pLineFilters(new LineFiltersList())
-, m_pFilterCommentsManager(new FilterCommentsManager())
+, m_pSubstitutionFiltersList(new SubstitutionFiltersList())
, m_pSyntaxColors(new SyntaxColors())
, m_pMarkers(new CCrystalTextMarkers())
, m_bMergingMode(false)
+, m_bEnableExitCode(false)
{
// add construction code here,
// Place all significant initialization in InitInstance
}
+/**
+ * @brief Chose which options manager should be initialized.
+ * @return IniOptionsMgr if initial config file exists,
+ * CRegOptionsMgr otherwise.
+ */
+static COptionsMgr *CreateOptionManager(const MergeCmdLineInfo& cmdInfo)
+{
+ String iniFilePath = cmdInfo.m_sIniFilepath;
+ if (!iniFilePath.empty())
+ {
+ iniFilePath = paths::GetLongPath(iniFilePath);
+ if (paths::CreateIfNeeded(paths::GetParentPath(iniFilePath)))
+ return new CIniOptionsMgr(iniFilePath);
+ }
+ iniFilePath = paths::ConcatPath(env::GetProgPath(), _T("winmerge.ini"));
+ if (paths::DoesPathExist(iniFilePath) == paths::IS_EXISTING_FILE)
+ return new CIniOptionsMgr(iniFilePath);
+ return new CRegOptionsMgr();
+}
+
+static HANDLE CreateMutexHandle()
+{
+ // Create exclusion mutex name
+ TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
+ DWORD dwLengthNeeded;
+ GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME,
+ szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
+ TCHAR szMutexName[MAX_PATH + 40];
+ // Combine window class name and desktop name to form a unique mutex name.
+ // As the window class name is decorated to distinguish between ANSI and
+ // UNICODE build, so will be the mutex name.
+ wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
+ return CreateMutex(nullptr, FALSE, szMutexName);
+}
+
+static HWND ActivatePreviousInstanceAndSendCommandline(LPTSTR cmdLine)
+{
+ HWND hWnd = FindWindow(CMainFrame::szClassName, nullptr);
+ if (hWnd == nullptr)
+ return nullptr;
+ if (IsIconic(hWnd))
+ ShowWindow(hWnd, SW_RESTORE);
+ SetForegroundWindow(GetLastActivePopup(hWnd));
+ COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine };
+ if (!SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
+ return nullptr;
+ return hWnd;
+}
+
+static void WaitForExitPreviousInstance(HWND hWnd)
+{
+ DWORD dwProcessId = 0;
+ GetWindowThreadProcessId(hWnd, &dwProcessId);
+ HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, dwProcessId);
+ if (hProcess)
+ WaitForSingleObject(hProcess, INFINITE);
+}
+
+static int ConvertLastCompareResultToExitCode(int nLastCompareResult)
+{
+ if (nLastCompareResult == 0)
+ return 0;
+ else if (nLastCompareResult > 0)
+ return 1;
+ return 2;
+}
+
CMergeApp::~CMergeApp()
{
strdiff::Close();
InitCommonControls(); // initialize common control library
CWinApp::InitInstance(); // call parent class method
+ m_imageForInitializingGdiplus.Load((IStream*)nullptr); // initialize GDI+
+
// Runtime switch so programmer may set this in interactive debugger
int dbgmem = 0;
if (dbgmem)
env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
// Parse command-line arguments.
+#ifdef TEST_WINMERGE
+ MergeCmdLineInfo cmdInfo(_T(""));
+#else
MergeCmdLineInfo cmdInfo(GetCommandLine());
+#endif
+ m_pOptions.reset(CreateOptionManager(cmdInfo));
if (cmdInfo.m_bNoPrefs)
m_pOptions->SetSerializing(false); // Turn off serializing to registry.
+ if (dynamic_cast<CRegOptionsMgr*>(m_pOptions.get()) != nullptr)
+ Options::CopyHKLMValues();
+
Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
ApplyCommandLineConfigOptions(cmdInfo);
if (cmdInfo.m_sErrorMessages.size() > 0)
// This is the name of the company of the original author (Dean Grimm)
SetRegistryKey(_T("Thingamahoochie"));
- bool bSingleInstance = GetOptionsMgr()->GetBool(OPT_SINGLE_INSTANCE) ||
- (true == cmdInfo.m_bSingleInstance);
+ int nSingleInstance = cmdInfo.m_nSingleInstance.has_value() ?
+ *cmdInfo.m_nSingleInstance : GetOptionsMgr()->GetInt(OPT_SINGLE_INSTANCE);
- // Create exclusion mutex name
- TCHAR szDesktopName[MAX_PATH] = _T("Win9xDesktop");
- DWORD dwLengthNeeded;
- GetUserObjectInformation(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME,
- szDesktopName, sizeof(szDesktopName), &dwLengthNeeded);
- TCHAR szMutexName[MAX_PATH + 40];
- // Combine window class name and desktop name to form a unique mutex name.
- // As the window class name is decorated to distinguish between ANSI and
- // UNICODE build, so will be the mutex name.
- wsprintf(szMutexName, _T("%s-%s"), CMainFrame::szClassName, szDesktopName);
- HANDLE hMutex = CreateMutex(nullptr, FALSE, szMutexName);
+ HANDLE hMutex = CreateMutexHandle();
if (hMutex != nullptr)
WaitForSingleObject(hMutex, INFINITE);
- if (bSingleInstance && GetLastError() == ERROR_ALREADY_EXISTS)
+ if (nSingleInstance != 0 && GetLastError() == ERROR_ALREADY_EXISTS)
{
// Activate previous instance and send commandline to it
- HWND hWnd = FindWindow(CMainFrame::szClassName, nullptr);
+ HWND hWnd = ActivatePreviousInstanceAndSendCommandline(GetCommandLine());
if (hWnd != nullptr)
{
- if (IsIconic(hWnd))
- ShowWindow(hWnd, SW_RESTORE);
- SetForegroundWindow(GetLastActivePopup(hWnd));
- LPTSTR cmdLine = GetCommandLine();
- COPYDATASTRUCT data = { 0, (lstrlen(cmdLine) + 1) * sizeof(TCHAR), cmdLine};
- if (::SendMessage(hWnd, WM_COPYDATA, NULL, (LPARAM)&data))
- {
- ReleaseMutex(hMutex);
- CloseHandle(hMutex);
- return FALSE;
- }
+ ReleaseMutex(hMutex);
+ CloseHandle(hMutex);
+ if (nSingleInstance != 1)
+ WaitForExitPreviousInstance(hWnd);
+ return FALSE;
}
}
LoadStdProfileSettings(GetOptionsMgr()->GetInt(OPT_MRU_MAX)); // Load standard INI file options (including MRU)
- InitializeFileFilters();
-
- // Read last used filter from registry
- // If filter fails to set, reset to default
- const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
- bool bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
- if (!bFilterSet)
- {
- String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
- m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
- }
-
charsets_init();
UpdateCodepageModule();
- FileTransform::g_UnpackerMode = static_cast<PLUGIN_MODE>(GetOptionsMgr()->GetInt(OPT_PLUGINS_UNPACKER_MODE));
- FileTransform::g_PredifferMode = static_cast<PLUGIN_MODE>(GetOptionsMgr()->GetInt(OPT_PLUGINS_PREDIFFER_MODE));
+ FileTransform::AutoUnpacking = GetOptionsMgr()->GetBool(OPT_PLUGINS_UNPACKER_MODE);
+ FileTransform::AutoPrediffing = GetOptionsMgr()->GetBool(OPT_PLUGINS_PREDIFFER_MODE);
NONCLIENTMETRICS ncm = { sizeof NONCLIENTMETRICS };
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof NONCLIENTMETRICS, &ncm, 0))
}
if (m_pSyntaxColors != nullptr)
- Options::SyntaxColors::Load(GetOptionsMgr(), m_pSyntaxColors.get());
+ Options::SyntaxColors::Init(GetOptionsMgr(), m_pSyntaxColors.get());
if (m_pMarkers != nullptr)
m_pMarkers->LoadFromRegistry();
+ CCrystalTextView::SetRenderingModeDefault(static_cast<CCrystalTextView::RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE)));
+
if (m_pLineFilters != nullptr)
m_pLineFilters->Initialize(GetOptionsMgr());
m_pLineFilters->Import(oldFilter);
}
+ if (m_pSubstitutionFiltersList != nullptr)
+ m_pSubstitutionFiltersList->Initialize(GetOptionsMgr());
+
// Check if filter folder is set, and create it if not
String pathMyFolders = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
if (pathMyFolders.empty())
// No filter path, set it to default and make sure it exists.
pathMyFolders = GetOptionsMgr()->GetDefault<String>(OPT_FILTER_USERPATH);
GetOptionsMgr()->SaveOption(OPT_FILTER_USERPATH, pathMyFolders);
- theApp.m_pGlobalFileFilter->SetUserFilterPath(pathMyFolders);
+ theApp.GetGlobalFileFilter()->SetUserFilterPath(pathMyFolders);
}
if (!paths::CreateIfNeeded(pathMyFolders))
{
CMenu * pNewMenu = CMenu::FromHandle(pMainFrame->m_hMenuDefault);
pMainFrame->MDISetMenu(pNewMenu, nullptr);
- // The main window has been initialized, so activate and update it.
+ // The main window has been initialized, so activate it.
pMainFrame->ActivateFrame(cmdInfo.m_nCmdShow);
- pMainFrame->UpdateWindow();
// Since this function actually opens paths for compare it must be
// called after initializing CMainFrame!
static void OpenContributersFile(int&)
{
- theApp.OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
+ CMergeApp::OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
+}
+
+static void OpenUrl(int&)
+{
+ shell::Open(WinMergeURL);
}
// App command to run the dialog
{
CAboutDlg aboutDlg;
aboutDlg.m_onclick_contributers += Poco::delegate(OpenContributersFile);
+ aboutDlg.m_onclick_url += Poco::delegate(OpenUrl);
aboutDlg.DoModal();
aboutDlg.m_onclick_contributers.clear();
+ aboutDlg.m_onclick_url.clear();
}
/////////////////////////////////////////////////////////////////////////////
* good place to do cleanups.
* @return Application's exit value (returned from WinMain()).
*/
-int CMergeApp::ExitInstance()
+int CMergeApp::ExitInstance()
{
charsets_cleanup();
ClearTempfolder(temp);
// Cleanup left over tempfiles from previous instances.
- // Normally this should not neet to do anything - but if for some reason
+ // Normally this should not need to do anything - but if for some reason
// WinMerge did not delete temp files this makes sure they are removed.
CleanupWMtemp();
delete m_mainThreadScripts;
CWinApp::ExitInstance();
- return 0;
+
+#ifndef _DEBUG
+ // There is a problem that OleUninitialize() in mfc/oleinit.cpp, which is called just before the process exits,
+ // hangs in rare cases.
+ // To deal with this problem, force the process to exit
+ // if the process does not exit within 2 seconds after the call to CMergeApp::ExitInstance().
+ _beginthreadex(0, 0,
+ [](void*) -> unsigned int {
+ Sleep(2000);
+ ExitProcess(0);
+ }, nullptr, 0, nullptr);
+#endif
+
+ return m_bEnableExitCode ? ConvertLastCompareResultToExitCode(m_nLastCompareResult) : 0;
}
int CMergeApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
// Create the message box dialog.
CMessageBoxDialog dlgMessage(pParentWnd, lpszPrompt, _T(""), nType | MB_RIGHT_ALIGN,
nIDPrompt);
-
+
if (m_pMainWnd->IsIconic())
m_pMainWnd->ShowWindow(SW_RESTORE);
return static_cast<int>(dlgMessage.DoModal());
}
-/**
- * @brief Set flag so that application will broadcast notification at next
- * idle time (via WM_TIMER id=IDLE_TIMER)
- */
-void CMergeApp::SetNeedIdleTimer()
-{
- m_bNeedIdleTimer = true;
-}
-
bool CMergeApp::IsReallyIdle() const
{
bool idle = true;
return idle;
}
-BOOL CMergeApp::OnIdle(LONG lCount)
+BOOL CMergeApp::OnIdle(LONG lCount)
{
if (CWinApp::OnIdle(lCount))
return TRUE;
if (m_bNonInteractive && IsReallyIdle())
m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
- static_cast<CRegOptionsMgr *>(GetOptionsMgr())->CloseHandles();
+ if (typeid(*GetOptionsMgr()) == typeid(CRegOptionsMgr))
+ {
+ static_cast<CRegOptionsMgr*>(GetOptionsMgr())->CloseKeys();
+ }
return FALSE;
}
*/
void CMergeApp::InitializeFileFilters()
{
- String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
+ assert(m_pGlobalFileFilter != nullptr);
+ const String& filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
if (!filterPath.empty())
{
{
bool bCompared = false;
String strDesc[3];
+ std::unique_ptr<PackingInfo> infoUnpacker;
+ std::unique_ptr<PrediffingInfo> infoPrediffer;
+ unsigned nID = cmdInfo.m_nWindowType == MergeCmdLineInfo::AUTOMATIC ?
+ 0 : static_cast<unsigned>(cmdInfo.m_nWindowType) + ID_MERGE_COMPARE_TEXT - 1;
+
m_bNonInteractive = cmdInfo.m_bNonInteractive;
+ m_bEnableExitCode = cmdInfo.m_bEnableExitCode;
+
+ if (!cmdInfo.m_sUnpacker.empty())
+ infoUnpacker.reset(new PackingInfo(cmdInfo.m_sUnpacker));
+
+ if (!cmdInfo.m_sPreDiffer.empty())
+ infoPrediffer.reset(new PrediffingInfo(cmdInfo.m_sPreDiffer));
// Set the global file filter.
if (!cmdInfo.m_sFileFilter.empty())
{
- m_pGlobalFileFilter->SetFilter(cmdInfo.m_sFileFilter);
+ GetGlobalFileFilter()->SetFilter(cmdInfo.m_sFileFilter);
}
// Set codepage.
UpdateDefaultCodepage(2,cmdInfo.m_nCodepage);
}
+ // Set compare method
+ if (cmdInfo.m_nCompMethod.has_value())
+ GetOptionsMgr()->Set(OPT_CMP_METHOD, *cmdInfo.m_nCompMethod);
+
// Unless the user has requested to see WinMerge's usage open files for
// comparison.
if (cmdInfo.m_bShowUsage)
strDesc[2] = cmdInfo.m_sRightDesc;
}
+ std::unique_ptr<CMainFrame::OpenFileParams> pOpenParams;
+ if (cmdInfo.m_nWindowType == MergeCmdLineInfo::TEXT)
+ pOpenParams.reset(new CMainFrame::OpenTextFileParams());
+ else if (cmdInfo.m_nWindowType == MergeCmdLineInfo::TABLE)
+ pOpenParams.reset(new CMainFrame::OpenTableFileParams());
+ else
+ pOpenParams.reset(static_cast<CMainFrame::OpenTableFileParams *>(new CMainFrame::OpenAutoFileParams()));
+ if (auto* pOpenTextFileParams = dynamic_cast<CMainFrame::OpenTextFileParams*>(pOpenParams.get()))
+ {
+ pOpenTextFileParams->m_line = cmdInfo.m_nLineIndex;
+ pOpenTextFileParams->m_char = cmdInfo.m_nCharIndex;
+ pOpenTextFileParams->m_fileExt = cmdInfo.m_sFileExt;
+ }
+ if (auto* pOpenTableFileParams = dynamic_cast<CMainFrame::OpenTableFileParams*>(pOpenParams.get()))
+ {
+ pOpenTableFileParams->m_tableDelimiter = cmdInfo.m_cTableDelimiter;
+ pOpenTableFileParams->m_tableQuote = cmdInfo.m_cTableQuote;
+ pOpenTableFileParams->m_tableAllowNewlinesInQuotes = cmdInfo.m_bTableAllowNewlinesInQuotes;
+ }
if (cmdInfo.m_Files.GetSize() > 2)
{
cmdInfo.m_dwLeftFlags |= FFILEOPEN_CMDLINE;
cmdInfo.m_dwMiddleFlags |= FFILEOPEN_CMDLINE;
cmdInfo.m_dwRightFlags |= FFILEOPEN_CMDLINE;
DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
- bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
+ bCompared = pMainFrame->DoFileOrFolderOpen(&cmdInfo.m_Files,
dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
- cmdInfo.m_sPreDiffer);
+ infoUnpacker.get(), infoPrediffer.get(), nID, pOpenParams.get());
}
else if (cmdInfo.m_Files.GetSize() > 1)
{
DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
- bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
+ bCompared = pMainFrame->DoFileOrFolderOpen(&cmdInfo.m_Files,
dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
- cmdInfo.m_sPreDiffer);
+ infoUnpacker.get(), infoPrediffer.get(), nID, pOpenParams.get());
}
else if (cmdInfo.m_Files.GetSize() == 1)
{
String sFilepath = cmdInfo.m_Files[0];
- if (IsProjectFile(sFilepath))
+ if (cmdInfo.m_bSelfCompare)
+ {
+ strDesc[0] = cmdInfo.m_sLeftDesc;
+ strDesc[1] = cmdInfo.m_sRightDesc;
+ bCompared = pMainFrame->DoSelfCompare(nID, sFilepath, strDesc,
+ infoUnpacker.get(), infoPrediffer.get(), pOpenParams.get());
+ }
+ else if (IsProjectFile(sFilepath))
{
bCompared = LoadAndOpenProjectFile(sFilepath);
}
- else if (IsConflictFile(sFilepath))
+ else if (ConflictFileParser::IsConflictFile(sFilepath))
{
//For a conflict file, load the descriptions in their respective positions: (they will be reordered as needed)
strDesc[0] = cmdInfo.m_sLeftDesc;
else
{
DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
- bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
- dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
- cmdInfo.m_sPreDiffer);
+ bCompared = pMainFrame->DoFileOrFolderOpen(&cmdInfo.m_Files,
+ dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
+ infoUnpacker.get(), infoPrediffer.get(), nID, pOpenParams.get());
}
}
else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
{
- bool showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
- if (showFiles)
- pMainFrame->DoFileOpen();
+ if (cmdInfo.m_bNewCompare)
+ {
+ bCompared = pMainFrame->DoFileNew(nID, 2, strDesc, infoPrediffer.get(), pOpenParams.get());
+ }
+ else if (cmdInfo.m_bClipboardCompare)
+ {
+ DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwRightFlags, FFILEOPEN_NONE};
+ bCompared = pMainFrame->DoOpenClipboard(nID, 2, dwFlags, strDesc, infoUnpacker.get(), infoPrediffer.get(), pOpenParams.get());
+ }
+ else
+ {
+ bool showFiles = m_pOptions->GetBool(OPT_SHOW_SELECT_FILES_AT_STARTUP);
+ if (showFiles)
+ pMainFrame->DoFileOrFolderOpen();
+ }
}
}
return bCompared;
*/
void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* = 1*/)
{
- String sCmd = GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD);
+ String sCmd = env::ExpandEnvironmentVariables(GetOptionsMgr()->GetString(OPT_EXT_EDITOR_CMD));
String sFile(file);
strutils::replace(sCmd, _T("$linenum"), strutils::to_str(nLineNumber));
}
}
-/**
- * @brief Open file, if it exists, else open url
- */
-void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
+/** @brief Returns pointer to global file filter */
+FileFilterHelper* CMergeApp::GetGlobalFileFilter()
{
- if (paths::DoesPathExist(szFile) == paths::IS_EXISTING_FILE)
- ShellExecute(nullptr, _T("open"), _T("notepad.exe"), szFile, nullptr, SW_SHOWNORMAL);
- else
- ShellExecute(nullptr, _T("open"), szUrl, nullptr, nullptr, SW_SHOWNORMAL);
+ if (!m_pGlobalFileFilter)
+ {
+ m_pGlobalFileFilter.reset(new FileFilterHelper());
+
+ InitializeFileFilters();
+
+ // Read last used filter from registry
+ // If filter fails to set, reset to default
+ const String filterString = m_pOptions->GetString(OPT_FILEFILTER_CURRENT);
+ bool bFilterSet = m_pGlobalFileFilter->SetFilter(filterString);
+ if (!bFilterSet)
+ {
+ String filter = m_pGlobalFileFilter->GetFilterNameOrMask();
+ m_pOptions->SaveOption(OPT_FILEFILTER_CURRENT, filter);
+ }
+ }
+
+ return m_pGlobalFileFilter.get();
}
/**
*/
void CMergeApp::ShowHelp(LPCTSTR helpLocation /*= nullptr*/)
{
- String name, ext;
- LANGID LangId = GetLangId();
- paths::SplitFilename(m_pLangDlg->GetFileName(LangId), nullptr, &name, &ext);
- String sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, name.c_str()));
+ String sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, GetLangName()));
if (paths::DoesPathExist(sPath) != paths::IS_EXISTING_FILE)
sPath = paths::ConcatPath(env::GetProgPath(), strutils::format(DocsPath, _T("")));
if (helpLocation == nullptr)
if (paths::DoesPathExist(sPath) == paths::IS_EXISTING_FILE)
::HtmlHelp(nullptr, sPath.c_str(), HH_DISPLAY_TOC, NULL);
else
- ShellExecute(nullptr, _T("open"), DocsURL, nullptr, nullptr, SW_SHOWNORMAL);
+ shell::Open(DocsURL);
}
else
{
String path;
String filename;
String ext;
-
+
paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
// Determine backup folder
PropBackups::FOLDER_ORIGINAL)
{
// Put backups to same folder than original file
- bakPath = path;
+ bakPath = std::move(path);
}
else if (GetOptionsMgr()->GetInt(OPT_BACKUP_LOCATION) ==
PropBackups::FOLDER_GLOBAL)
// Put backups to global folder defined in options
bakPath = GetOptionsMgr()->GetString(OPT_BACKUP_GLOBALFOLDER);
if (bakPath.empty())
- bakPath = path;
+ bakPath = std::move(path);
else
bakPath = paths::GetLongPath(bakPath);
}
{
success = !!CopyFileW(TFile(pszPath).wpath().c_str(), TFile(bakPath).wpath().c_str(), FALSE);
}
-
+
if (!success)
{
String msg = strutils::format_string1(
_("Unable to backup original file:\n%1\n\nContinue anyway?"),
- pszPath);
- if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN) != IDYES)
+ pszPath + _T("\n(\u2192 ") + bakPath + _T(")"));
+ if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_BACKUP_FAILED_PROMPT) != IDYES)
return false;
}
return true;
bool bFileExists = false;
String s;
String str;
- CString title;
if (!strSavePath.empty())
{
if (bFileExists && bFileRO)
{
UINT userChoice = 0;
-
+
// Don't ask again if its already asked
if (bApplyToAll)
userChoice = IDYES;
else
{
// Single file
- 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);
+ 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);
userChoice = AfxMessageBox(str.c_str(), MB_YESNOCANCEL |
MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN,
IDS_SAVEREADONLY_FMT);
// Overwrite read-only file
case IDYESTOALL:
bApplyToAll = true; // Don't ask again (no break here)
+ [[fallthrough]];
case IDYES:
CFile::GetStatus(strSavePath.c_str(), status);
status.m_mtime = 0; // Avoid unwanted changes
CFile::SetStatus(strSavePath.c_str(), status);
nRetVal = IDYES;
break;
-
+
// Save to new filename (single) /skip this item (multiple)
case IDNO:
if (!bMultiFile)
return nRetVal;
}
+String CMergeApp::GetPackingErrorMessage(int pane, int paneCount, const String& path, const PackingInfo& plugin)
+{
+ String pluginName = plugin.GetPluginPipeline();
+ return strutils::format_string2(
+ pane == 0 ?
+ _("Plugin '%2' cannot pack your changes to the left file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?")
+ : (pane == paneCount - 1) ?
+ _("Plugin '%2' cannot pack your changes to the right file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?")
+ : _("Plugin '%2' cannot pack your changes to the middle file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?"),
+ path, pluginName);
+}
+
/**
* @brief Is specified file a project file?
* @param [in] filepath Full path to file to check.
}
catch (Poco::Exception& e)
{
- String sErr = _("Unknown error attempting to open project file");
+ String sErr = _("Unknown error attempting to open project file.");
sErr += ucr::toTString(e.displayText());
String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
AfxMessageBox(msg.c_str(), MB_ICONSTOP);
}
catch (Poco::Exception& e)
{
- String sErr = _("Unknown error attempting to save project file");
+ String sErr = _("Unknown error attempting to save project file.");
sErr += ucr::toTString(e.displayText());
String msg = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), sProject, sErr);
AfxMessageBox(msg.c_str(), MB_ICONSTOP);
return true;
}
-/**
+/**
* @brief Read project and perform comparison specified
* @param [in] sProject Full path to project file.
* @return `true` if loading project file and starting compare succeeded.
ProjectFile project;
if (!LoadProjectFile(sProject, project))
return false;
-
- PathContext tFiles;
- bool bLeftReadOnly = false;
- bool bMiddleReadOnly = false;
- bool bRightReadOnly = false;
- bool bRecursive = false;
- project.GetPaths(tFiles, bRecursive);
- bLeftReadOnly = project.GetLeftReadOnly();
- bMiddleReadOnly = project.GetMiddleReadOnly();
- bRightReadOnly = project.GetRightReadOnly();
- if (project.HasFilter())
- {
- String filter = project.GetFilter();
- filter = strutils::trim_ws(filter);
- m_pGlobalFileFilter->SetFilter(filter);
- }
- if (project.HasSubfolders())
- bRecursive = project.GetSubfolders() > 0;
-
- DWORD dwFlags[3] = {
- static_cast<DWORD>(tFiles.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
- static_cast<DWORD>(tFiles.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
- static_cast<DWORD>(tFiles.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
- };
- if (bLeftReadOnly)
- dwFlags[0] |= FFILEOPEN_READONLY;
- if (tFiles.GetSize() == 2)
- {
- if (bRightReadOnly)
- dwFlags[1] |= FFILEOPEN_READONLY;
- }
- else
+
+ bool rtn = true;
+ for (auto& projItem : project.Items())
{
- if (bMiddleReadOnly)
- dwFlags[1] |= FFILEOPEN_READONLY;
- if (bRightReadOnly)
- dwFlags[2] |= FFILEOPEN_READONLY;
- }
+ std::unique_ptr<PrediffingInfo> pInfoPrediffer;
+ std::unique_ptr<PackingInfo> pInfoUnpacker;
+ PathContext tFiles;
+ bool bDummy = false;
+ projItem.GetPaths(tFiles, bDummy);
+ for (int i = 0; i < tFiles.GetSize(); ++i)
+ {
+ if (!paths::IsPathAbsolute(tFiles[i]))
+ {
+ String sProjectDir = paths::GetParentPath(sProject);
+ if (tFiles[i].substr(0, 1) == _T("\\"))
+ {
+ if (sProjectDir.length() > 1 && sProjectDir[1] == ':')
+ tFiles[i] = paths::ConcatPath(sProjectDir.substr(0, 2), tFiles[i]);
+ }
+ else
+ tFiles[i] = paths::ConcatPath(sProjectDir, tFiles[i]);
+ }
+ }
+ bool bLeftReadOnly = projItem.GetLeftReadOnly();
+ bool bMiddleReadOnly = projItem.GetMiddleReadOnly();
+ bool bRightReadOnly = projItem.GetRightReadOnly();
+ if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Open, Options::Project::Item::FileFilter) && projItem.HasFilter())
+ {
+ String filter = projItem.GetFilter();
+ filter = strutils::trim_ws(filter);
+ GetGlobalFileFilter()->SetFilter(filter);
+ }
+ bool bRecursive = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
+ if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Open, Options::Project::Item::IncludeSubfolders) && projItem.HasSubfolders())
+ bRecursive = projItem.GetSubfolders() > 0;
+ if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Open, Options::Project::Item::UnpackerPlugin) && projItem.HasUnpacker())
+ pInfoUnpacker.reset(new PackingInfo(projItem.GetUnpacker()));
+ if (projItem.HasPrediffer())
+ pInfoPrediffer.reset(new PrediffingInfo(projItem.GetPrediffer()));
+
+ DWORD dwFlags[3] = {
+ static_cast<DWORD>(tFiles.GetPath(0).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
+ static_cast<DWORD>(tFiles.GetPath(1).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT),
+ static_cast<DWORD>(tFiles.GetPath(2).empty() ? FFILEOPEN_NONE : FFILEOPEN_PROJECT)
+ };
+ if (bLeftReadOnly)
+ dwFlags[0] |= FFILEOPEN_READONLY;
+ if (tFiles.GetSize() == 2)
+ {
+ if (bRightReadOnly)
+ dwFlags[1] |= FFILEOPEN_READONLY;
+ }
+ else
+ {
+ if (bMiddleReadOnly)
+ dwFlags[1] |= FFILEOPEN_READONLY;
+ if (bRightReadOnly)
+ dwFlags[2] |= FFILEOPEN_READONLY;
+ }
- GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
-
- bool rtn = GetMainFrame()->DoFileOpen(&tFiles, dwFlags, nullptr, sReportFile, bRecursive);
+ GetOptionsMgr()->Set(OPT_CMP_INCLUDE_SUBDIRS, bRecursive);
+
+ if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Open, Options::Project::Item::CompareOptions))
+ {
+ if (projItem.HasIgnoreWhite())
+ GetOptionsMgr()->Set(OPT_CMP_IGNORE_WHITESPACE, projItem.GetIgnoreWhite());
+ if (projItem.HasIgnoreBlankLines())
+ GetOptionsMgr()->Set(OPT_CMP_IGNORE_BLANKLINES, projItem.GetIgnoreBlankLines());
+ if (projItem.HasIgnoreCase())
+ GetOptionsMgr()->Set(OPT_CMP_IGNORE_CASE, projItem.GetIgnoreCase());
+ if (projItem.HasIgnoreEol())
+ GetOptionsMgr()->Set(OPT_CMP_IGNORE_EOL, projItem.GetIgnoreEol());
+ if (projItem.HasIgnoreNumbers())
+ GetOptionsMgr()->Set(OPT_CMP_IGNORE_NUMBERS, projItem.GetIgnoreNumbers());
+ if (projItem.HasIgnoreCodepage())
+ GetOptionsMgr()->Set(OPT_CMP_IGNORE_CODEPAGE, projItem.GetIgnoreCodepage());
+ if (projItem.HasFilterCommentsLines())
+ GetOptionsMgr()->Set(OPT_CMP_FILTER_COMMENTLINES, projItem.GetFilterCommentsLines());
+ if (projItem.HasCompareMethod())
+ GetOptionsMgr()->Set(OPT_CMP_METHOD, projItem.GetCompareMethod());
+ }
+
+ rtn &= GetMainFrame()->DoFileOrFolderOpen(&tFiles, dwFlags, nullptr, sReportFile, bRecursive,
+ nullptr, pInfoUnpacker.get(), pInfoPrediffer.get());
+ }
AddToRecentProjectsMRU(sProject.c_str());
return rtn;
return m_pLangDlg->GetLangId();
}
+String CMergeApp::GetLangName() const
+{
+ String name, ext;
+ paths::SplitFilename(theApp.m_pLangDlg->GetFileName(theApp.GetLangId()), nullptr, &name, &ext);
+ return name;
+}
+
/**
* @brief Lang aware version of CStatusBar::SetIndicators()
*/
*/
void CMergeApp::AddToRecentProjectsMRU(LPCTSTR sPathName)
{
- // sPathName will be added to the top of the MRU list.
+ // sPathName will be added to the top of the MRU list.
// If sPathName already exists in the MRU list, it will be moved to the top
if (m_pRecentFileList != nullptr) {
m_pRecentFileList->Add(sPathName);
bool bMergingMode = GetMergingMode();
if (!bMergingMode)
- LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN);
+ LangMessageBox(IDS_MERGE_MODE, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN, IDS_MERGE_MODE);
SetMergingMode(!bMergingMode);
}
}
else
{
- for (auto& name : pOptions->GetNameList())
- {
- if (name.find(lpszSection) == 0)
- pOptions->RemoveOption(name);
- }
-
+ String name = strutils::format(_T("%s/"), lpszSection);
+ pOptions->RemoveOption(name);
}
return TRUE;
}