OSDN Git Service

Fix issue #784: Error on try to show differences between two different gif
[winmerge-jp/winmerge-jp.git] / Src / MainFrm.cpp
index 3e64de5..708ddb4 100644 (file)
 #include "HexMergeView.h"
 #include "ImgMergeFrm.h"
 #include "LineFiltersList.h"
-#include "TokenPairList.h"
+#include "SubstitutionFiltersList.h"
 #include "ConflictFileParser.h"
 #include "LineFiltersDlg.h"
-#include "IgnoredSubstitutionsDlg.h"
+#include "SubstitutionFiltersDlg.h"
 #include "paths.h"
 #include "Environment.h"
 #include "PatchTool.h"
@@ -62,7 +62,9 @@
 #include "Bitmap.h"
 #include "CCrystalTextMarkers.h"
 #include "utils/hqbitmap.h"
-
+#include "UniFile.h"
+#include "TFile.h"
+#include "Shell.h"
 #include "WindowsManagerDialog.h"
 
 using std::vector;
@@ -109,8 +111,6 @@ const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
        { ID_TOOLS_CUSTOMIZECOLUMNS,    IDB_TOOLS_COLUMNS,                              CMainFrame::MENU_ALL },
        { ID_TOOLS_GENERATEPATCH,               IDB_TOOLS_GENERATEPATCH,                CMainFrame::MENU_ALL },
        { ID_PLUGINS_LIST,                              IDB_PLUGINS_LIST,                               CMainFrame::MENU_ALL },
-       { ID_COPY_FROM_LEFT,                    IDB_COPY_FROM_LEFT,                             CMainFrame::MENU_ALL },
-       { ID_COPY_FROM_RIGHT,                   IDB_COPY_FROM_RIGHT,                    CMainFrame::MENU_ALL },
        { ID_FILE_PRINT,                                IDB_FILE_PRINT,                                 CMainFrame::MENU_FILECMP },
        { ID_TOOLS_GENERATEREPORT,              IDB_TOOLS_GENERATEREPORT,               CMainFrame::MENU_FILECMP },
        { ID_EDIT_TOGGLE_BOOKMARK,              IDB_EDIT_TOGGLE_BOOKMARK,               CMainFrame::MENU_FILECMP },
@@ -119,6 +119,12 @@ const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
        { ID_EDIT_CLEAR_ALL_BOOKMARKS,  IDB_EDIT_CLEAR_ALL_BOOKMARKS,   CMainFrame::MENU_FILECMP },
        { ID_VIEW_ZOOMIN,                               IDB_VIEW_ZOOMIN,                                CMainFrame::MENU_FILECMP },
        { ID_VIEW_ZOOMOUT,                              IDB_VIEW_ZOOMOUT,                               CMainFrame::MENU_FILECMP },
+       { ID_COPY_FROM_LEFT,                    IDB_COPY_FROM_LEFT,                             CMainFrame::MENU_FILECMP },
+       { ID_COPY_FROM_RIGHT,                   IDB_COPY_FROM_RIGHT,                    CMainFrame::MENU_FILECMP },
+       { ID_LINES_R2L,                                 IDB_COPY_SELECTED_LINES_TO_LEFT,        CMainFrame::MENU_FILECMP },
+       { ID_LINES_L2R,                                 IDB_COPY_SELECTED_LINES_TO_RIGHT,       CMainFrame::MENU_FILECMP },
+       { ID_COPY_LINES_FROM_LEFT,              IDB_COPY_SELECTED_LINES_FROM_LEFT,      CMainFrame::MENU_FILECMP },
+       { ID_COPY_LINES_FROM_RIGHT,             IDB_COPY_SELECTED_LINES_FROM_RIGHT,     CMainFrame::MENU_FILECMP },
        { ID_MERGE_COMPARE,                             IDB_MERGE_COMPARE,                              CMainFrame::MENU_FOLDERCMP },
        { ID_MERGE_COMPARE_LEFT1_LEFT2,         IDB_MERGE_COMPARE_LEFT1_LEFT2,  CMainFrame::MENU_FOLDERCMP },
        { ID_MERGE_COMPARE_RIGHT1_RIGHT2,       IDB_MERGE_COMPARE_RIGHT1_RIGHT2,CMainFrame::MENU_FOLDERCMP },
@@ -236,12 +242,20 @@ BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
        ON_COMMAND_RANGE(ID_MRU_FIRST, ID_MRU_LAST, OnMRUs)
        ON_UPDATE_COMMAND_UI(ID_MRU_FIRST, OnUpdateNoMRUs)
        ON_UPDATE_COMMAND_UI(ID_NO_MRU, OnUpdateNoMRUs)
+       ON_COMMAND(ID_FIRSTFILE, OnFirstFile)
+       ON_UPDATE_COMMAND_UI(ID_FIRSTFILE, OnUpdateFirstFile)
+       ON_COMMAND(ID_PREVFILE, OnPrevFile)
+       ON_UPDATE_COMMAND_UI(ID_PREVFILE, OnUpdatePrevFile)
+       ON_COMMAND(ID_NEXTFILE, OnNextFile)
+       ON_UPDATE_COMMAND_UI(ID_NEXTFILE, OnUpdateNextFile)
+       ON_COMMAND(ID_LASTFILE, OnLastFile)
+       ON_UPDATE_COMMAND_UI(ID_LASTFILE, OnUpdateLastFile)
        ON_COMMAND(ID_ACCEL_QUIT, &CMainFrame::OnAccelQuit)
-       //}}AFX_MSG_MAP
        ON_MESSAGE(WMU_CHILDFRAMEADDED, &CMainFrame::OnChildFrameAdded)
        ON_MESSAGE(WMU_CHILDFRAMEREMOVED, &CMainFrame::OnChildFrameRemoved)
        ON_MESSAGE(WMU_CHILDFRAMEACTIVATE, &CMainFrame::OnChildFrameActivate)
        ON_MESSAGE(WMU_CHILDFRAMEACTIVATED, &CMainFrame::OnChildFrameActivated)
+       //}}AFX_MSG_MAP
 END_MESSAGE_MAP()
 
 /**
@@ -631,7 +645,7 @@ void CMainFrame::OnFileOpen()
 static void
 FileLocationGuessEncodings(FileLocation & fileloc, int iGuessEncoding)
 {
-       fileloc.encoding = GuessCodepageEncoding(fileloc.filepath, iGuessEncoding);
+       fileloc.encoding = codepage_detect::Guess(fileloc.filepath, iGuessEncoding);
 }
 
 bool CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
@@ -639,9 +653,20 @@ bool CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
        const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
        const PackingInfo * infoUnpacker /*= nullptr*/)
 {
+       ASSERT(pDirDoc != nullptr);
+
        if (sReportFile.empty() && pDirDoc->CompareFilesIfFilesAreLarge(nFiles, ifileloc))
                return false;
 
+       String unpackedFileExtension;
+       if (infoUnpacker && GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
+       {
+               std::vector<String> filepaths(nFiles);
+               std::transform(ifileloc, ifileloc + nFiles, filepaths.begin(),
+                       [](auto& file) { return file.filepath; });
+               String filteredFilenames = strutils::join(filepaths.begin(), filepaths.end(), _T("|"));
+               unpackedFileExtension = FileTransform::GetUnpackedFileExtension(filteredFilenames, infoUnpacker);
+       }
        FileFilterHelper filterImg, filterBin;
        filterImg.UseMask(true);
        filterImg.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
@@ -649,12 +674,38 @@ bool CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
        filterBin.SetMask(GetOptionsMgr()->GetString(OPT_CMP_BIN_FILEPATTERNS));
        for (int pane = 0; pane < nFiles; ++pane)
        {
-               if (filterImg.includeFile(ifileloc[pane].filepath) && CImgMergeFrame::IsLoadable())
+               String filepath = ifileloc[pane].filepath + unpackedFileExtension;
+               if (filterImg.includeFile(filepath) && CImgMergeFrame::IsLoadable())
                        return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
-               else if (filterBin.includeFile(ifileloc[pane].filepath) && CHexMergeView::IsLoadable())
+               else if (filterBin.includeFile(filepath) && CHexMergeView::IsLoadable())
                        return ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
        }
-       return ShowMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
+       return ShowTextOrTableMergeDoc({}, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
+}
+
+bool CMainFrame::ShowMergeDoc(UINT nID, CDirDoc* pDirDoc,
+       int nFiles, const FileLocation ifileloc[],
+       const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
+       const PackingInfo* infoUnpacker /*= nullptr*/)
+{
+       switch (nID)
+       {
+       case ID_MERGE_COMPARE_TEXT:
+               return GetMainFrame()->ShowTextMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
+                       strDesc, sReportFile, infoUnpacker);
+       case ID_MERGE_COMPARE_TABLE:
+               return GetMainFrame()->ShowTableMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
+                       strDesc, sReportFile, infoUnpacker);
+       case ID_MERGE_COMPARE_HEX:
+               return GetMainFrame()->ShowHexMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
+                       strDesc, sReportFile, infoUnpacker);
+       case ID_MERGE_COMPARE_IMAGE:
+               return GetMainFrame()->ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
+                       strDesc, sReportFile, infoUnpacker);
+       default:
+               return GetMainFrame()->ShowAutoMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags,
+                       strDesc, sReportFile, infoUnpacker);
+       }
 }
 
 std::array<bool, 3> GetROFromFlags(int nFiles, const DWORD dwFlags[])
@@ -689,7 +740,7 @@ int GetActivePaneFromFlags(int nFiles, const DWORD dwFlags[])
  * @param [in] infoUnpacker Plugin info.
  * @return success/failure
  */
-bool CMainFrame::ShowMergeDoc(CDirDoc * pDirDoc,
+bool CMainFrame::ShowTextOrTableMergeDoc(std::optional<bool> table, CDirDoc * pDirDoc,
        int nFiles, const FileLocation ifileloc[],
        const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
        const PackingInfo * infoUnpacker /*= nullptr*/)
@@ -724,6 +775,8 @@ bool CMainFrame::ShowMergeDoc(CDirDoc * pDirDoc,
                }
        }
 
+       pMergeDoc->SetEnableTableEditing(table);
+
        // Note that OpenDocs() takes care of closing compare window when needed.
        bool bResult = pMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc);
        if (bResult)
@@ -762,6 +815,22 @@ bool CMainFrame::ShowMergeDoc(CDirDoc * pDirDoc,
        return true;
 }
 
+bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc,
+       int nFiles, const FileLocation ifileloc[],
+       const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
+       const PackingInfo* infoUnpacker /*= nullptr*/)
+{
+       return ShowTextOrTableMergeDoc(false, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
+}
+
+bool CMainFrame::ShowTableMergeDoc(CDirDoc* pDirDoc,
+       int nFiles, const FileLocation ifileloc[],
+       const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
+       const PackingInfo* infoUnpacker /*= nullptr*/)
+{
+       return ShowTextOrTableMergeDoc(true, pDirDoc, nFiles, ifileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
+}
+
 bool CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
        const DWORD dwFlags[], const String strDesc[], const String& sReportFile /*= _T("")*/,
        const PackingInfo * infoUnpacker /*= nullptr*/)
@@ -772,6 +841,8 @@ bool CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocati
        if (pHexMergeDoc == nullptr)
                return false;
 
+       pHexMergeDoc->SetUnpacker(infoUnpacker);
+
        if (!pHexMergeDoc->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc))
                return false;
 
@@ -791,12 +862,12 @@ bool CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocati
        if (!CImgMergeFrame::menu.m_hMenu)
                CImgMergeFrame::menu.m_hMenu = NewImgMergeViewMenu();
        pImgMergeFrame->SetSharedMenu(CImgMergeFrame::menu.m_hMenu);
-
+       pImgMergeFrame->SetUnpacker(infoUnpacker);
        pImgMergeFrame->SetDirDoc(pDirDoc);
        pDirDoc->AddMergeDoc(pImgMergeFrame);
                
        if (!pImgMergeFrame->OpenDocs(nFiles, fileloc, GetROFromFlags(nFiles, dwFlags).data(), strDesc, this))
-               return ShowMergeDoc(pDirDoc, nFiles, fileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
+               return ShowTextMergeDoc(pDirDoc, nFiles, fileloc, dwFlags, strDesc, sReportFile, infoUnpacker);
 
        for (int pane = 0; pane < nFiles; pane++)
        {
@@ -812,13 +883,36 @@ bool CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocati
        return true;
 }
 
+bool CMainFrame::ShowTextMergeDoc(CDirDoc* pDirDoc, int nBuffers, const String text[],
+               const String strDesc[], const String& strFileExt)
+{
+       FileLocation fileloc[3];
+       DWORD dwFlags[3] = {};
+       CDirDoc* pDirDoc2 = pDirDoc->GetMainView() ? pDirDoc :
+               static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
+       for (int nBuffer = 0; nBuffer < nBuffers; ++nBuffer)
+       {
+               TempFilePtr wTemp(new TempFile());
+               String workFile = wTemp->Create(_T("text_"), strFileExt);
+               m_tempFiles.push_back(wTemp);
+               wTemp->Create(_T(""), strFileExt);
+               UniStdioFile file;
+               if (file.OpenCreateUtf8(workFile))
+               {
+                       file.WriteString(text[nBuffer]);
+               }
+               fileloc[nBuffer].setPath(workFile);
+       }
+       return ShowTextMergeDoc(pDirDoc2, nBuffers, fileloc, dwFlags, strDesc);
+}
+
 /**
  * @brief Show GNU licence information in notepad (local file) or in Web Browser
  */
 void CMainFrame::OnHelpGnulicense() 
 {
        const String spath = paths::ConcatPath(env::GetProgPath(), LicenseFile);
-       theApp.OpenFileOrUrl(spath.c_str(), LicenceUrl);
+       shell::OpenFileOrUrl(spath.c_str(), LicenceUrl);
 }
 
 /**
@@ -1064,6 +1158,17 @@ bool CMainFrame::DoFileOpen(const PathContext * pFiles /*= nullptr*/,
        return true;
 }
 
+bool CMainFrame::DoFileOpen(UINT nID, const PathContext* pFiles /*= nullptr*/,
+       const DWORD dwFlags[] /*= nullptr*/, const String strDesc[] /*= nullptr*/)
+{
+       CDirDoc* pDirDoc = static_cast<CDirDoc*>(theApp.m_pDirTemplate->CreateNewDocument());
+       FileLocation fileloc[3];
+       for (int pane = 0; pane < pFiles->GetSize(); pane++)
+               fileloc[pane].setPath((*pFiles)[pane]);
+       return ShowMergeDoc(nID, pDirDoc, pFiles->GetSize(), fileloc,
+               dwFlags, strDesc);
+}
+
 void CMainFrame::UpdateFont(FRAMETYPE frame)
 {
        if (frame == FRAME_FOLDER)
@@ -1548,7 +1653,7 @@ void CMainFrame::OnSaveConfigData()
        if (configLog.WriteLogFile(sError))
        {
                String sFileName = configLog.GetFileName();
-               theApp.OpenFileToExternalEditor(sFileName);
+               CMergeApp::OpenFileToExternalEditor(sFileName);
        }
        else
        {
@@ -1593,16 +1698,14 @@ void CMainFrame::FileNew(int nPanes, FRAMETYPE frameType, bool table)
                fileloc[1].encoding.SetCodepage(ucr::getDefaultCodepage());
                fileloc[2].encoding.SetCodepage(ucr::getDefaultCodepage());
        }
-       if (frameType == FRAME_FILE)
+       UINT nID = ID_MERGE_COMPARE_TEXT;
+       switch (frameType)
        {
-               ShowMergeDoc(pDirDoc, nPanes, fileloc, dwFlags, strDesc);
-               if (table)
-                       PostMessage(WM_COMMAND, ID_MERGE_COMPARE_TABLE);
+       case FRAME_FILE: nID = !table ? ID_MERGE_COMPARE_TEXT : ID_MERGE_COMPARE_TABLE; break;
+       case FRAME_HEXFILE: nID = ID_MERGE_COMPARE_HEX; break;
+       case FRAME_IMGFILE: nID = ID_MERGE_COMPARE_IMAGE; break;
        }
-       else if (frameType == FRAME_HEXFILE)
-               ShowHexMergeDoc(pDirDoc, nPanes, fileloc, dwFlags, strDesc);
-       else if (frameType == FRAME_IMGFILE)
-               ShowImgMergeDoc(pDirDoc, nPanes, fileloc, dwFlags, strDesc);
+       ShowMergeDoc(nID, pDirDoc, nPanes, fileloc, dwFlags, strDesc);
 }
 
 /**
@@ -1629,15 +1732,15 @@ void CMainFrame::OnToolsFilters()
        String title = _("Filters");
        CPropertySheet sht(title.c_str());
        LineFiltersDlg lineFiltersDlg;
-       IgnoredSubstitutionsDlg ignoredSubstitutionsFiltersDlg;
+       SubstitutionFiltersDlg substitutionFiltersDlg;
        FileFiltersDlg fileFiltersDlg;
        std::unique_ptr<LineFiltersList> lineFilters(new LineFiltersList());
-       std::unique_ptr<TokenPairList> ignoredSubstitutionsFilters(new TokenPairList());
+       std::unique_ptr<SubstitutionFiltersList> SubstitutionFilters(new SubstitutionFiltersList());
        String selectedFilter;
        const String origFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
        sht.AddPage(&fileFiltersDlg);
        sht.AddPage(&lineFiltersDlg);
-       sht.AddPage(&ignoredSubstitutionsFiltersDlg);
+       sht.AddPage(&substitutionFiltersDlg);
        sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
 
        // Make sure all filters are up-to-date
@@ -1651,18 +1754,10 @@ void CMainFrame::OnToolsFilters()
        lineFilters->CloneFrom(theApp.m_pLineFilters.get());
        lineFiltersDlg.SetList(lineFilters.get());
 
-       const bool ignoredSubstitutionsAreEnabledOrig = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED);
-       const bool ignoredSubstitutionsWorkBothWaysOrig = GetOptionsMgr()->GetBool(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS);
-       const bool completelyBlankOutIgnoredSubstitutionsOrig = GetOptionsMgr()->GetBool(OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS);
-       const bool optUseRegexpsForSubstitutionsOrig = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS);
+       SubstitutionFilters->CloneFrom(theApp.m_pSubstitutionFiltersList.get());
+       substitutionFiltersDlg.SetList(SubstitutionFilters.get());
 
-       ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsAreEnabled = ignoredSubstitutionsAreEnabledOrig;
-       ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsWorkBothWays = ignoredSubstitutionsWorkBothWaysOrig;
-       ignoredSubstitutionsFiltersDlg.m_CompletelyBlankOutIgnoredSubstitutions = completelyBlankOutIgnoredSubstitutionsOrig;
-       ignoredSubstitutionsFiltersDlg.m_UseRegexpsForIgnoredSubstitutions = optUseRegexpsForSubstitutionsOrig;
-       
-       ignoredSubstitutionsFilters->CloneFrom(theApp.m_pTokensForIs.get());
-       ignoredSubstitutionsFiltersDlg.SetList(ignoredSubstitutionsFilters.get());
+       sht.SetActivePage(AfxGetApp()->GetProfileInt(_T("Settings"), _T("FilterStartPage"), 0));
 
        if (sht.DoModal() == IDOK)
        {
@@ -1688,19 +1783,6 @@ void CMainFrame::OnToolsFilters()
                bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp;
                GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled);
 
-               bool ignoredSubstitutionsAreEnabled = ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsAreEnabled;
-               GetOptionsMgr()->SaveOption(OPT_IGNORED_SUBSTITUTIONS_ARE_ENABLED, ignoredSubstitutionsAreEnabled);
-
-               bool ignoredSubstitutionsWorkBothWays = ignoredSubstitutionsFiltersDlg.m_IgnoredSubstitutionsWorkBothWays;
-               GetOptionsMgr()->SaveOption(OPT_IGNORED_SUBSTITUTIONS_WORK_BOTH_WAYS, ignoredSubstitutionsWorkBothWays);
-
-               bool completelyBlankOutIgnoredSubstitutions = ignoredSubstitutionsFiltersDlg.m_CompletelyBlankOutIgnoredSubstitutions;
-               GetOptionsMgr()->SaveOption(OPT_COMPLETELY_BLANK_OUT_IGNORED_SUBSTITUTIONS, completelyBlankOutIgnoredSubstitutions);
-               
-               bool optUseRegexpsForSubstitutions = GetOptionsMgr()->GetBool(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS);
-               GetOptionsMgr()->SaveOption(OPT_USE_REGEXPS_FOR_IGNORED_SUBSTITUTIONS, optUseRegexpsForSubstitutions);
-
-
                // Check if compare documents need rescanning
                bool bFileCompareRescan = false;
                bool bFolderCompareRescan = false;
@@ -1711,12 +1793,8 @@ void CMainFrame::OnToolsFilters()
                        if
                        (
                                   linefiltersEnabled != lineFiltersEnabledOrig
-                               || ignoredSubstitutionsAreEnabled != ignoredSubstitutionsAreEnabledOrig
-                               || ignoredSubstitutionsWorkBothWays != ignoredSubstitutionsWorkBothWaysOrig
-                               || completelyBlankOutIgnoredSubstitutions != completelyBlankOutIgnoredSubstitutionsOrig
-                               || optUseRegexpsForSubstitutions != optUseRegexpsForSubstitutionsOrig
                                || !lineFilters->Compare(theApp.m_pLineFilters.get())
-                               || !ignoredSubstitutionsFilters->Compare(theApp.m_pTokensForIs.get())
+                               || !SubstitutionFilters->Compare(theApp.m_pSubstitutionFiltersList.get())
                        )
                        {
                                bFileCompareRescan = true;
@@ -1738,9 +1816,8 @@ void CMainFrame::OnToolsFilters()
                theApp.m_pLineFilters->CloneFrom(lineFilters.get());
                theApp.m_pLineFilters->SaveFilters();
 
-               theApp.m_pTokensForIs->CloneFrom(ignoredSubstitutionsFilters.get());
-               theApp.m_pTokensForIs->SaveFilters();
-
+               theApp.m_pSubstitutionFiltersList->CloneFrom(SubstitutionFilters.get());
+               theApp.m_pSubstitutionFiltersList->SaveFilters();
 
                if (bFileCompareRescan)
                {
@@ -1905,15 +1982,8 @@ LRESULT CMainFrame::OnCopyData(WPARAM wParam, LPARAM lParam)
 
 LRESULT CMainFrame::OnUser1(WPARAM wParam, LPARAM lParam)
 {
-       CFrameWnd * pFrame = GetActiveFrame();
-       if (pFrame != nullptr)
-       {
-               IMergeDoc *pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
-               if (pMergeDoc == nullptr)
-                       pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
-               if (pMergeDoc != nullptr)
-                       pMergeDoc->CheckFileChanged();
-       }
+       if (IMergeDoc *pMergeDoc = GetActiveIMergeDoc())
+               pMergeDoc->CheckFileChanged();
        return 0;
 }
 
@@ -2034,15 +2104,8 @@ void CMainFrame::OnActivateApp(BOOL bActive, HTASK hTask)
        CMDIFrameWnd::OnActivateApp(bActive, hTask);
 #endif
 
-       CFrameWnd * pFrame = GetActiveFrame();
-       if (pFrame != nullptr)
-       {
-               IMergeDoc *pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
-               if (pMergeDoc == nullptr)
-                       pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
-               if (pMergeDoc != nullptr)
-                       PostMessage(WM_USER+1);
-       }
+       if (IMergeDoc *pMergeDoc = GetActiveIMergeDoc())
+               PostMessage(WM_USER+1);
 }
 
 BOOL CMainFrame::CreateToolbar()
@@ -2071,7 +2134,7 @@ BOOL CMainFrame::CreateToolbar()
        LoadToolbarImages();
 
        UINT nID, nStyle;
-       for (auto cmd : { ID_OPTIONS, ID_FILE_NEW })
+       for (auto cmd : { ID_OPTIONS, ID_FILE_NEW, ID_FILE_OPEN, ID_FILE_SAVE })
        {
                int iImage;
                int index = m_wndToolBar.GetToolBarCtrl().CommandToIndex(cmd);
@@ -2140,7 +2203,7 @@ static void LoadHiColImageList(UINT nIDResource, int nWidth, int nHeight, int nN
  */
 static void LoadToolbarImageList(int orgImageWidth, int newImageWidth, UINT nIDResource, bool bGrayscale, CImageList& ImgList)
 {
-       const int ImageCount = 22;
+       const int ImageCount = 26;
        const int orgImageHeight = orgImageWidth - 1;
        const int newImageHeight = newImageWidth - 1;
        LoadHiColImageList(nIDResource, orgImageWidth, orgImageHeight, newImageWidth, newImageHeight, ImageCount, bGrayscale, ImgList);
@@ -2261,7 +2324,7 @@ bool CMainFrame::AskCloseConfirmation()
 void CMainFrame::OnHelpReleasenotes()
 {
        const String sPath = paths::ConcatPath(env::GetProgPath(), RelNotes);
-       ShellExecute(nullptr, _T("open"), sPath.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+       shell::Open(sPath.c_str());
 }
 
 /**
@@ -2270,7 +2333,7 @@ void CMainFrame::OnHelpReleasenotes()
  */
 void CMainFrame::OnHelpTranslations()
 {
-       ShellExecute(nullptr, _T("open"), TranslationsUrl, nullptr, nullptr, SW_SHOWNORMAL);
+       shell::Open(TranslationsUrl);
 }
 
 /**
@@ -2364,12 +2427,29 @@ bool CMainFrame::DoOpenConflict(const String& conflictFile, const String strDesc
        return conflictCompared;
 }
 
+bool CMainFrame::DoSelfCompare(UINT nID, const String& file, const String strDesc[] /*= nullptr*/)
+{
+       String ext = paths::FindExtension(file);
+       TempFilePtr wTemp(new TempFile());
+       String copiedFile = wTemp->Create(_T("self-compare_"), ext);
+       m_tempFiles.push_back(wTemp);
+
+       TFile(file).copyTo(copiedFile);
+
+       String strDesc2[2] = { 
+               (strDesc && !strDesc[0].empty()) ? strDesc[0] : _("Original File"),
+               (strDesc && !strDesc[1].empty()) ? strDesc[1] : _("") };
+       DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU};
+       PathContext tmpPathContext(copiedFile, file);
+       return DoFileOpen(nID, &tmpPathContext, dwFlags, strDesc2);
+}
+
 /**
  * @brief Get type of frame (File/Folder compare).
  * @param [in] pFrame Pointer to frame to check.
  * @return FRAMETYPE of the given frame.
 */
-CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame) const
+CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame)
 {
        bool bMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CMergeEditFrame));
        bool bHexMergeFrame = !!pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame));
@@ -2402,7 +2482,22 @@ void CMainFrame::OnToolbarButtonDropDown(NMHDR* pNMHDR, LRESULT* pResult)
        LPNMTOOLBAR pToolBar = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
        ClientToScreen(&(pToolBar->rcButton));
        BCMenu menu;
-       int id = (pToolBar->iItem == ID_FILE_NEW) ? IDR_POPUP_NEW : IDR_POPUP_DIFF_OPTIONS;
+       int id;
+       switch (pToolBar->iItem)
+       {
+       case ID_FILE_NEW:
+               id = IDR_POPUP_NEW;
+               break;
+       case ID_FILE_OPEN:
+               id = IDR_POPUP_OPEN;
+               break;
+       case ID_FILE_SAVE:
+               id = IDR_POPUP_SAVE;
+               break;
+       default:
+               id = IDR_POPUP_DIFF_OPTIONS;
+               break;
+       }
        VERIFY(menu.LoadMenu(id));
        theApp.TranslateMenu(menu.m_hMenu);
        CMenu* pPopup = menu.GetSubMenu(0);
@@ -2502,9 +2597,10 @@ void CMainFrame::OnMRUs(UINT nID)
 void CMainFrame::OnUpdateNoMRUs(CCmdUI* pCmdUI)
 {
        // append the MRU submenu
-       HMENU hMenu = GetSubmenu(AfxGetMainWnd()->GetMenu()->GetSubMenu(0/*File menu*/)->m_hMenu, false);
-       if (hMenu == nullptr)
+       CMenu *pMenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu : pCmdUI->m_pMenu;
+       if (pMenu == nullptr)
                return;
+       HMENU hMenu = pMenu->m_hMenu;
        
        // empty the menu
        size_t i = ::GetMenuItemCount(hMenu);
@@ -2540,6 +2636,94 @@ void CMainFrame::OnUpdatePluginName(CCmdUI* pCmdUI)
        pCmdUI->SetText(_T(""));
 }
 
+/**
+ * @brief Move to next file
+ */
+void CMainFrame::OnNextFile()
+{
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       pDirDoc->MoveToNextFile(pMergeDoc);
+}
+
+/**
+ * @brief Called when Move to next file is updated
+ */
+void CMainFrame::OnUpdateNextFile(CCmdUI* pCmdUI)
+{
+       bool enabled = false;
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       enabled = !pDirDoc->IsLastFile();
+       pCmdUI->Enable(enabled);
+}
+
+/**
+ * @brief Move to previous file
+ */
+void CMainFrame::OnPrevFile()
+{
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       pDirDoc->MoveToPrevFile(pMergeDoc);
+}
+
+/**
+ * @brief Called when Move to previous file is updated
+ */
+void CMainFrame::OnUpdatePrevFile(CCmdUI* pCmdUI)
+{
+       bool enabled = false;
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       enabled = !pDirDoc->IsFirstFile();
+       pCmdUI->Enable(enabled);
+}
+
+/**
+ * @brief Move to first file
+ */
+void CMainFrame::OnFirstFile()
+{
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       pDirDoc->MoveToFirstFile(pMergeDoc);
+}
+
+/**
+ * @brief Called when Move to first file is updated
+ */
+void CMainFrame::OnUpdateFirstFile(CCmdUI* pCmdUI)
+{
+       bool enabled = false;
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       enabled = !pDirDoc->IsFirstFile();
+       pCmdUI->Enable(enabled);
+}
+
+/**
+ * @brief Move to last file
+ */
+void CMainFrame::OnLastFile()
+{
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       pDirDoc->MoveToLastFile(pMergeDoc);
+}
+
+/**
+ * @brief Called when Move to last file item is updated
+ */
+void CMainFrame::OnUpdateLastFile(CCmdUI* pCmdUI)
+{
+       bool enabled = false;
+       if (IMergeDoc* pMergeDoc = GetActiveIMergeDoc())
+               if (CDirDoc* pDirDoc = pMergeDoc->GetDirDoc())
+                       enabled = !pDirDoc->IsLastFile();
+       pCmdUI->Enable(enabled);
+}
+
 void CMainFrame::ReloadMenu()
 {
        // set the menu of the main frame window
@@ -2613,6 +2797,17 @@ void CMainFrame::ReloadMenu()
        }
 }
 
+IMergeDoc* CMainFrame::GetActiveIMergeDoc()
+{
+       CFrameWnd* pFrame = GetActiveFrame();
+       if (!pFrame)
+               return nullptr;
+       IMergeDoc* pMergeDoc = dynamic_cast<IMergeDoc*>(pFrame->GetActiveDocument());
+       if (!pMergeDoc)
+               pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
+       return pMergeDoc;
+}
+
 void CMainFrame::UpdateDocTitle()
 {
        CDocManager* pDocManager = AfxGetApp()->m_pDocManager;