OSDN Git Service

Leave the class name as CIniOptionsMgr, but rename the filename to IniOptionsMgr.*
[winmerge-jp/winmerge-jp.git] / Src / Merge.cpp
index 559b4c2..bbda58a 100644 (file)
@@ -2,23 +2,9 @@
 //    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.
@@ -34,6 +20,7 @@
 #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"
@@ -67,6 +53,7 @@
 #include "stringdiffs.h"
 #include "TFile.h"
 #include "paths.h"
+#include "Shell.h"
 #include "CompareStats.h"
 #include "TestMain.h"
 #include "charsets.h" // For shutdown cleanup
@@ -113,14 +100,14 @@ CMergeApp::CMergeApp() :
 , m_mainThreadScripts(nullptr)
 , m_nLastCompareResult(0)
 , m_bNonInteractive(false)
-, m_pOptions(new CRegOptionsMgr())
+, m_pOptions(CreateOptionManager())
 , m_pGlobalFileFilter(new FileFilterHelper())
 , 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)
@@ -129,6 +116,23 @@ CMergeApp::CMergeApp() :
        // Place all significant initialization in InitInstance
 }
 
+/**
+ * @brief Chose which options manager should be initialized.
+ * @return IniOptionsMgr if initial config file exists,
+ *   CRegOptionsMgr otherwise.
+ */
+COptionsMgr *CreateOptionManager()
+{
+       if (CIniOptionsMgr::CheckIfIniFileExist())
+       {
+               return new CIniOptionsMgr();
+       }
+       else
+       {
+               return new CRegOptionsMgr();
+       }
+}
+
 CMergeApp::~CMergeApp()
 {
        strdiff::Close();
@@ -164,6 +168,8 @@ BOOL CMergeApp::InitInstance()
        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)
@@ -198,10 +204,15 @@ BOOL CMergeApp::InitInstance()
        env::LoadRegistryFromFile(paths::ConcatPath(env::GetProgPath(), _T("WinMerge.reg")));
 
        // Parse command-line arguments.
+#ifdef TEST_WINMERGE
+       MergeCmdLineInfo cmdInfo(_T(""));
+#else
        MergeCmdLineInfo cmdInfo(GetCommandLine());
+#endif
        if (cmdInfo.m_bNoPrefs)
                m_pOptions->SetSerializing(false); // Turn off serializing to registry.
 
+       Options::CopyHKLMValues();
        Options::Init(m_pOptions.get()); // Implementation in OptionsInit.cpp
        ApplyCommandLineConfigOptions(cmdInfo);
        if (cmdInfo.m_sErrorMessages.size() > 0)
@@ -229,13 +240,13 @@ BOOL CMergeApp::InitInstance()
        // 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, 
+       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.
@@ -245,7 +256,7 @@ BOOL CMergeApp::InitInstance()
        HANDLE hMutex = CreateMutex(nullptr, FALSE, szMutexName);
        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);
@@ -260,6 +271,14 @@ BOOL CMergeApp::InitInstance()
                        {
                                ReleaseMutex(hMutex);
                                CloseHandle(hMutex);
+                               if (nSingleInstance > 1)
+                               {
+                                       DWORD dwProcessId = 0;
+                                       GetWindowThreadProcessId(hWnd, &dwProcessId);
+                                       HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, dwProcessId);
+                                       if (hProcess)
+                                               WaitForSingleObject(hProcess, INFINITE);
+                               }
                                return FALSE;
                        }
                }
@@ -297,7 +316,7 @@ BOOL CMergeApp::InitInstance()
        }
 
        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();
@@ -316,6 +335,9 @@ BOOL CMergeApp::InitInstance()
                        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())
@@ -406,9 +428,8 @@ BOOL CMergeApp::InitInstance()
        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!
@@ -437,13 +458,20 @@ static void OpenContributersFile(int&)
        theApp.OpenFileToExternalEditor(paths::ConcatPath(env::GetProgPath(), ContributorsPath));
 }
 
+static void OpenUrl(int&)
+{
+       shell::Open(WinMergeURL);
+}
+
 // App command to run the dialog
 void CMergeApp::OnAppAbout()
 {
        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();
 }
 
 /////////////////////////////////////////////////////////////////////////////
@@ -455,7 +483,7 @@ void CMergeApp::OnAppAbout()
  * good place to do cleanups.
  * @return Application's exit value (returned from WinMain()).
  */
-int CMergeApp::ExitInstance() 
+int CMergeApp::ExitInstance()
 {
        charsets_cleanup();
 
@@ -467,7 +495,7 @@ int CMergeApp::ExitInstance()
        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();
 
@@ -511,7 +539,7 @@ 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);
 
@@ -535,7 +563,7 @@ bool CMergeApp::IsReallyIdle() const
     return idle;
 }
 
-BOOL CMergeApp::OnIdle(LONG lCount) 
+BOOL CMergeApp::OnIdle(LONG lCount)
 {
        if (CWinApp::OnIdle(lCount))
                return TRUE;
@@ -550,7 +578,10 @@ BOOL CMergeApp::OnIdle(LONG lCount)
        if (m_bNonInteractive && IsReallyIdle())
                m_pMainWnd->PostMessage(WM_CLOSE, 0, 0);
 
-       static_cast<CRegOptionsMgr *>(GetOptionsMgr())->CloseKeys();
+       if (typeid(*GetOptionsMgr()) == typeid(CRegOptionsMgr))
+       {
+               static_cast<CRegOptionsMgr*>(GetOptionsMgr())->CloseKeys();
+       }
 
        return FALSE;
 }
@@ -607,8 +638,16 @@ bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainF
 {
        bool bCompared = false;
        String strDesc[3];
+       std::unique_ptr<PackingInfo> infoUnpacker;
+
        m_bNonInteractive = cmdInfo.m_bNonInteractive;
 
+       if (!cmdInfo.m_sUnpacker.empty())
+       {
+               infoUnpacker.reset(new PackingInfo(PLUGIN_MODE::PLUGIN_MANUAL));
+               infoUnpacker->m_PluginName = cmdInfo.m_sUnpacker;
+       }
+
        // Set the global file filter.
        if (!cmdInfo.m_sFileFilter.empty())
        {
@@ -621,6 +660,10 @@ bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainF
                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)
@@ -655,19 +698,25 @@ bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainF
                        DWORD dwFlags[3] = {cmdInfo.m_dwLeftFlags, cmdInfo.m_dwMiddleFlags, cmdInfo.m_dwRightFlags};
                        bCompared = pMainFrame->DoFileOpen(&cmdInfo.m_Files,
                                dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
-                               cmdInfo.m_sPreDiffer);
+                               cmdInfo.m_sPreDiffer, infoUnpacker.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,
                                dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
-                               cmdInfo.m_sPreDiffer);
+                               cmdInfo.m_sPreDiffer, infoUnpacker.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(IDOK, sFilepath, strDesc);
+                       }
+                       else if (IsProjectFile(sFilepath))
                        {
                                bCompared = LoadAndOpenProjectFile(sFilepath);
                        }
@@ -683,8 +732,8 @@ bool CMergeApp::ParseArgsAndDoOpen(MergeCmdLineInfo& cmdInfo, CMainFrame* pMainF
                        {
                                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);
+                                       dwFlags, strDesc, cmdInfo.m_sReportFile, cmdInfo.m_bRecurse, nullptr,
+                                       cmdInfo.m_sPreDiffer, infoUnpacker.get());
                        }
                }
                else if (cmdInfo.m_Files.GetSize() == 0) // if there are no input args, we can check the display file dialog flag
@@ -797,17 +846,6 @@ void CMergeApp::OpenFileToExternalEditor(const String& file, int nLineNumber/* =
 }
 
 /**
- * @brief Open file, if it exists, else open url
- */
-void CMergeApp::OpenFileOrUrl(LPCTSTR szFile, LPCTSTR szUrl)
-{
-       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);
-}
-
-/**
  * @brief Show Help - this is for opening help from outside mainframe.
  * @param [in] helpLocation Location inside help, if `nullptr` main help is opened.
  */
@@ -824,7 +862,7 @@ void CMergeApp::ShowHelp(LPCTSTR 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
        {
@@ -864,7 +902,7 @@ bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath)
                String path;
                String filename;
                String ext;
-       
+
                paths::SplitFilename(paths::GetLongPath(pszPath), &path, &filename, &ext);
 
                // Determine backup folder
@@ -927,7 +965,7 @@ bool CMergeApp::CreateBackup(bool bFolder, const String& pszPath)
                {
                        success = !!CopyFileW(TFile(pszPath).wpath().c_str(), TFile(bakPath).wpath().c_str(), FALSE);
                }
-               
+
                if (!success)
                {
                        String msg = strutils::format_string1(
@@ -985,7 +1023,7 @@ int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
        if (bFileExists && bFileRO)
        {
                UINT userChoice = 0;
-               
+
                // Don't ask again if its already asked
                if (bApplyToAll)
                        userChoice = IDYES;
@@ -1003,7 +1041,7 @@ int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
                        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);
@@ -1014,6 +1052,7 @@ int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
                // 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
@@ -1021,7 +1060,7 @@ int CMergeApp::HandleReadonlySave(String& strSavePath, bool bMultiFile,
                        CFile::SetStatus(strSavePath.c_str(), status);
                        nRetVal = IDYES;
                        break;
-               
+
                // Save to new filename (single) /skip this item (multiple)
                case IDNO:
                        if (!bMultiFile)
@@ -1073,7 +1112,7 @@ bool CMergeApp::LoadProjectFile(const String& sProject, ProjectFile &project)
        }
        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);
@@ -1091,7 +1130,7 @@ bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &proje
        }
        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);
@@ -1101,7 +1140,7 @@ bool CMergeApp::SaveProjectFile(const String& sProject, const ProjectFile &proje
        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.
@@ -1111,7 +1150,7 @@ bool CMergeApp::LoadAndOpenProjectFile(const String& sProject, const String& sRe
        ProjectFile project;
        if (!LoadProjectFile(sProject, project))
                return false;
-       
+
        bool rtn = true;
        for (auto& projItem : project.Items())
        {
@@ -1236,7 +1275,7 @@ std::wstring CMergeApp::LoadDialogCaption(LPCTSTR lpDialogTemplateID) const
  */
 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);
@@ -1350,12 +1389,8 @@ BOOL CMergeApp::WriteProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTS
        }
        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;
 }