OSDN Git Service

Use __super
[winmerge-jp/winmerge-jp.git] / Src / OpenView.cpp
index 13d4ded..b2105e8 100644 (file)
@@ -2,21 +2,7 @@
 //    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  OpenView.cpp
 #include "OpenDoc.h"
 #include "ProjectFile.h"
 #include "paths.h"
-#include "SelectUnpackerDlg.h"
+#include "SelectPluginDlg.h"
 #include "OptionsDef.h"
 #include "MainFrm.h"
 #include "OptionsMgr.h"
 #include "FileOrFolderSelect.h"
 #include "7zCommon.h"
 #include "Constants.h"
-#include "Picture.h"
+#include "Bitmap.h"
 #include "DropHandler.h"
 #include "FileFilterHelper.h"
 #include "Plugins.h"
 #include "BCMenu.h"
 #include "LanguageSelect.h"
 #include "Win_VersionHelper.h"
+#include "OptionsProject.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
@@ -58,8 +45,9 @@
 
 // Timer ID and timeout for delaying path validity check
 const UINT IDT_CHECKFILES = 1;
+const UINT IDT_RETRY = 2;
 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
-static const TCHAR EMPTY_EXTENSION[] = _T(".*");
+const int RETRY_MAX = 3;
 
 /** @brief Location for Open-dialog specific help to open. */
 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
@@ -70,19 +58,13 @@ IMPLEMENT_DYNCREATE(COpenView, CFormView)
 
 BEGIN_MESSAGE_MAP(COpenView, CFormView)
        //{{AFX_MSG_MAP(COpenView)
-       ON_BN_CLICKED(IDC_PATH0_BUTTON, OnPathButton<0>)
-       ON_BN_CLICKED(IDC_PATH1_BUTTON, OnPathButton<1>)
-       ON_BN_CLICKED(IDC_PATH2_BUTTON, OnPathButton<2>)
+       ON_CONTROL_RANGE(BN_CLICKED, IDC_PATH0_BUTTON, IDC_PATH2_BUTTON, OnPathButton)
        ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
        ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
        ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
-       ON_CBN_SELCHANGE(IDC_PATH0_COMBO, OnSelchangePathCombo<0>)
-       ON_CBN_SELCHANGE(IDC_PATH1_COMBO, OnSelchangePathCombo<1>)
-       ON_CBN_SELCHANGE(IDC_PATH2_COMBO, OnSelchangePathCombo<2>)
-       ON_CBN_EDITCHANGE(IDC_PATH0_COMBO, OnEditEvent<0>)
-       ON_CBN_EDITCHANGE(IDC_PATH1_COMBO, OnEditEvent<1>)
-       ON_CBN_EDITCHANGE(IDC_PATH2_COMBO, OnEditEvent<2>)
-       ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
+       ON_CONTROL_RANGE(CBN_SELCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSelchangePathCombo)
+       ON_CONTROL_RANGE(CBN_EDITCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnEditEvent)
+       ON_CONTROL_RANGE(BN_CLICKED, IDC_SELECT_UNPACKER, IDC_SELECT_PREDIFFER, OnSelectPlugin)
        ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
        ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
        ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
@@ -91,12 +73,30 @@ BEGIN_MESSAGE_MAP(COpenView, CFormView)
        ON_WM_TIMER()
        ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
        ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
-       ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, OnDropDownOptions)
+       ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, (OnDropDown<IDC_OPTIONS, IDR_POPUP_PROJECT_DIFF_OPTIONS>))
+       ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
+       ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
+       ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
+       ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
+       ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
+       ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
+       ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
+       ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
+       ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnDiffIgnoreNumbers)
+       ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnUpdateDiffIgnoreNumbers)
+       ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
+       ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
+       ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
+       ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
+       ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
+       ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
        ON_WM_ACTIVATE()
        ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
        ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
-       ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, OnDropDownSaveProject)
+       ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
+       ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
        ON_COMMAND(IDOK, OnOK)
+       ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
        ON_COMMAND(IDCANCEL, OnCancel)
        ON_COMMAND(ID_HELP, OnHelp)
        ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
@@ -104,6 +104,10 @@ BEGIN_MESSAGE_MAP(COpenView, CFormView)
        ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
        ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
        ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
+       ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnCompare)
+       ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateCompare)
+       ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
+       ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
        ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
        ON_WM_PAINT()
        ON_WM_LBUTTONUP()
@@ -127,7 +131,19 @@ COpenView::COpenView()
        , m_bReadOnly {false, false, false}
        , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
        , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
+       , m_retryCount(0)
+       , m_nIgnoreWhite(0)
+       , m_bIgnoreBlankLines(false)
+       , m_bIgnoreCase(false)
+       , m_bIgnoreEol(false)
+       , m_bIgnoreNumbers(false)
+       , m_bIgnoreCodepage(false)
+       , m_bFilterCommentsLines(false)
+       , m_nCompareMethod(0)
 {
+       // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
+       // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
+       m_bInsideUpdate = TRUE;
 }
 
 COpenView::~COpenView()
@@ -137,12 +153,14 @@ COpenView::~COpenView()
 
 void COpenView::DoDataExchange(CDataExchange* pDX)
 {
-       CFormView::DoDataExchange(pDX);
+       __super::DoDataExchange(pDX);
        //{{AFX_DATA_MAP(COpenView)
        DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
        DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
        DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
        DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
+       DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
+       DDX_Control(pDX, IDC_PREDIFFER_COMBO, m_ctlPredifferPipeline);
        DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
        DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
        DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
@@ -151,7 +169,8 @@ void COpenView::DoDataExchange(CDataExchange* pDX)
        DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
        DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
        DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
-       DDX_Text(pDX, IDC_UNPACKER_EDIT, m_strUnpacker);
+       DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
+       DDX_CBStringExact(pDX, IDC_PREDIFFER_COMBO, m_strPredifferPipeline);
        //}}AFX_DATA_MAP
 }
 
@@ -161,7 +180,7 @@ BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
        //  the CREATESTRUCT cs
        cs.style &= ~WS_BORDER;
        cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
-       return CFormView::PreCreateWindow(cs);
+       return __super::PreCreateWindow(cs);
 }
 
 void COpenView::OnInitialUpdate()
@@ -171,16 +190,20 @@ void COpenView::OnInitialUpdate()
                // fallback for XP 
                SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
                SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
+               SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
        }
 
        m_sizeOrig = GetTotalSize();
 
        theApp.TranslateDialog(m_hWnd);
 
-       if (!m_picture.Load(IDR_LOGO))
-               return;
+       if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
+       {
+               // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
+               m_image.Create(1, 1, 24, 0);
+       }
 
-       CFormView::OnInitialUpdate();
+       __super::OnInitialUpdate();
 
        // set caption to "swap paths" button
        LOGFONT lf;
@@ -215,15 +238,17 @@ void COpenView::OnInitialUpdate()
        m_files = pDoc->m_files;
        m_bRecurse = pDoc->m_bRecurse;
        m_strExt = pDoc->m_strExt;
-       m_strUnpacker = pDoc->m_strUnpacker;
-       m_infoHandler = pDoc->m_infoHandler;
+       m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
+       m_strPredifferPipeline = pDoc->m_strPredifferPipeline;
        m_dwFlags[0] = pDoc->m_dwFlags[0];
        m_dwFlags[1] = pDoc->m_dwFlags[1];
        m_dwFlags[2] = pDoc->m_dwFlags[2];
 
        m_ctlPath[0].SetFileControlStates();
-       m_ctlPath[1].SetFileControlStates();
+       m_ctlPath[1].SetFileControlStates(true);
        m_ctlPath[2].SetFileControlStates(true);
+       m_ctlUnpackerPipeline.SetFileControlStates(true);
+       m_ctlPredifferPipeline.SetFileControlStates(true);
 
        for (int file = 0; file < m_files.GetSize(); file++)
        {
@@ -237,16 +262,20 @@ void COpenView::OnInitialUpdate()
        m_ctlPath[2].AttachSystemImageList();
        LoadComboboxStates();
 
+       m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
+       m_ctlPredifferPipeline.SetWindowText(m_strPredifferPipeline.c_str());
+
        bool bDoUpdateData = true;
-       for (int index = 0; index < std::size(m_strPath); index++)
+       for (auto& strPath: m_strPath)
        {
-               if (!m_strPath[index].empty())
+               if (!strPath.empty())
                        bDoUpdateData = false;
        }
        UpdateData(bDoUpdateData);
 
-       String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
-       bool bMask = theApp.m_pGlobalFileFilter->IsUsingMask();
+       auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
+       String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
+       bool bMask = pGlobalFileFilter->IsUsingMask();
 
        if (!bMask)
        {
@@ -269,7 +298,7 @@ void COpenView::OnInitialUpdate()
        if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
        {
                EnableDlgItem(IDOK, true);
-               EnableDlgItem(IDC_UNPACKER_EDIT, true);
+               EnableDlgItem(IDC_UNPACKER_COMBO, true);
                EnableDlgItem(IDC_SELECT_UNPACKER, true);
        }
 
@@ -283,10 +312,17 @@ void COpenView::OnInitialUpdate()
        if (!bOverwriteRecursive)
                m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
 
-       m_strUnpacker = m_infoHandler.m_PluginName;
+       m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
+       m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
+       m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
+       m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
+       m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
+       m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
+       m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
+       m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
+
        UpdateData(FALSE);
        SetStatus(IDS_OPEN_FILESDIRS);
-       SetUnpackerStatus(IDS_USERCHOICE_NONE); 
 
        m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
        RegisterDragDrop(m_hWnd, m_pDropHandler);
@@ -295,6 +331,16 @@ void COpenView::OnInitialUpdate()
 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
 {
        m_bRecurse = GetDocument()->m_bRecurse;
+
+       m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
+       m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
+       m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
+       m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
+       m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
+       m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
+       m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
+       m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
+
        UpdateData(FALSE);
 }
 
@@ -308,7 +354,6 @@ COpenDoc* COpenView::GetDocument() const // non-debug version is inline
 }
 #endif //_DEBUG
 
-
 /////////////////////////////////////////////////////////////////////////////
 // COpenView message handlers
 
@@ -319,11 +364,11 @@ void COpenView::OnPaint()
        GetClientRect(&rc);
 
        // Draw the logo image
-       CSize size = m_picture.GetImageSize(&dc);
+       CSize size{ m_image.GetWidth(), m_image.GetHeight() };
        CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
-       m_picture.Render(&dc, rcImage);
+       m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
        // And extend it to the Right boundary
-    dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
+       dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
 
        // Draw the resize gripper in the Lower Right corner.
        CRect rcGrip = rc;
@@ -342,7 +387,7 @@ void COpenView::OnPaint()
        dc.LineTo(rc.right, rcStatus.top - 3);
        dc.SelectObject(oldpen);
 
-       CFormView::OnPaint();
+       __super::OnPaint();
 }
 
 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
@@ -359,7 +404,7 @@ void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
                        case IDC_PATH2_COMBO:
                                int id2 = 0;
                                CWnd *pwndChild = GetFocus();
-                               if (IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
+                               if (pwndChild && IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
                                {
                                        id2 = pwndChild->GetDlgCtrlID();
                                        pwndChild = pwndChild->GetParent();
@@ -401,7 +446,7 @@ void COpenView::OnMouseMove(UINT nFlags, CPoint point)
                                        SetCursor(m_hIconRotate);
                                        break;
                                }
-                               // fall through
+                               [[fallthrough]];
                        default:
                                SetCursor(m_hCursorNo);
                                break;
@@ -456,7 +501,7 @@ void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
                if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
                {
                        m_constraint.Persist(true, false);
-                       WINDOWPLACEMENT wp;
+                       WINDOWPLACEMENT wp = {};
                        wp.length = sizeof wp;
                        pFrameWnd->GetWindowPlacement(&wp);
                        CRect rc;
@@ -467,7 +512,7 @@ void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
                        pFrameWnd->SetWindowPlacement(&wp);
                }
        }
-       CFormView::OnWindowPosChanged(lpwndpos);
+       __super::OnWindowPosChanged(lpwndpos);
 }
 
 void COpenView::OnDestroy()
@@ -475,7 +520,7 @@ void COpenView::OnDestroy()
        if (m_pDropHandler != nullptr)
                RevokeDragDrop(m_hWnd);
 
-       CFormView::OnDestroy();
+       __super::OnDestroy();
 }
 
 LRESULT COpenView::OnNcHitTest(CPoint point)
@@ -489,11 +534,15 @@ LRESULT COpenView::OnNcHitTest(CPoint point)
                if (PtInRect(&rc, point))
                        return HTRIGHT;
        }
-       return CFormView::OnNcHitTest(point);
+       return __super::OnNcHitTest(point);
 }
 
-void COpenView::OnButton(int index)
+/** 
+ * @brief Called when "Browse..." button is selected for N path.
+ */
+void COpenView::OnPathButton(UINT nId)
 {
+       const int index = nId - IDC_PATH0_BUTTON;
        String s;
        String sfolder;
        UpdateData(TRUE); 
@@ -508,7 +557,8 @@ void COpenView::OnButton(int index)
                sfolder = paths::GetPathOnly(m_strPath[index]);
                break;
        case paths::DOES_NOT_EXIST:
-               // Do nothing, empty foldername will be passed to dialog
+               if (!m_strPath[index].empty())
+                       sfolder = paths::GetParentPath(m_strPath[index]);
                break;
        default:
                _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
@@ -518,23 +568,13 @@ void COpenView::OnButton(int index)
        if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
        {
                m_strPath[index] = s;
-               m_strBrowsePath[index] = s;
+               m_strBrowsePath[index] = std::move(s);
                UpdateData(FALSE);
                UpdateButtonStates();
        }       
 }
 
-/** 
- * @brief Called when "Browse..." button is selected for N path.
- */
-template <int N>
-void COpenView::OnPathButton()
-{
-       OnButton(N);
-}
-
-template<int id1, int id2>
-void COpenView::OnSwapButton() 
+void COpenView::OnSwapButton(int id1, int id2)
 {
        String s1, s2;
        GetDlgItemText(id1, s1);
@@ -544,46 +584,62 @@ void COpenView::OnSwapButton()
        SetDlgItemText(id2, s2);
 }
 
-/** 
- * @brief Called when dialog is closed with "OK".
- *
- * Checks that paths are valid and sets filters.
- */
-void COpenView::OnOK() 
+template<int id1, int id2>
+void COpenView::OnSwapButton() 
+{
+       OnSwapButton(id1, id2);
+}
+
+void COpenView::OnCompare(UINT nID)
 {
        int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
        const String filterPrefix = _("[F] ");
+       auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
 
        UpdateData(TRUE);
        TrimPaths();
 
-       int index;
        int nFiles = 0;
-       for (index = 0; index < std::size(m_strPath); index++)
+       for (auto& strPath : m_strPath)
        {
-               if (index == 2 && m_strPath[index].empty())
+               if (nFiles >= 1 && strPath.empty())
                        break;
                m_files.SetSize(nFiles + 1);
-               m_files[nFiles] = m_strPath[index];
+               m_files[nFiles] = strPath;
                m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
-               m_dwFlags[nFiles] |= m_bReadOnly[index] ? FFILEOPEN_READONLY : 0;
+               m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
                nFiles++;
        }
        // If left path is a project-file, load it
        String ext;
        paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
-       if (m_strPath[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
-               LoadProjectFile(m_strPath[0]);
+       if (nFiles == 1)
+       {
+               if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
+               {
+                       theApp.LoadAndOpenProjectFile(m_strPath[0]);
+               }
+               else if (!paths::IsDirectory(m_strPath[0]))
+               {
+                       PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
+                       if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
+                               tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
+                       PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
+                       GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo, &tmpPrediffingInfo);
+               }
+               return;
+       }
 
        pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
 
-       if (pathsType == paths::DOES_NOT_EXIST)
+       if (pathsType == paths::DOES_NOT_EXIST &&
+               !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); }))
        {
                LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
                return;
        }
 
-       for (index = 0; index < nFiles; index++)
+       for (int index = 0; index < nFiles; index++)
        {
                // If user has edited path by hand, expand environment variables
                bool bExpand = false;
@@ -595,7 +651,7 @@ void COpenView::OnOK()
                        m_files[index] = paths::GetLongPath(m_files[index], bExpand);
        
                        // Add trailing '\' for directories if its missing
-                       if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
+                       if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR && !IsArchiveFile(m_files[index]))
                                m_files[index] = paths::AddTrailingSlash(m_files[index]);
                        m_strPath[index] = m_files[index];
                }
@@ -603,6 +659,7 @@ void COpenView::OnOK()
 
        UpdateData(FALSE);
        KillTimer(IDT_CHECKFILES);
+       KillTimer(IDT_RETRY);
 
        String filter(strutils::trim_ws(m_strExt));
 
@@ -611,19 +668,19 @@ void COpenView::OnOK()
        {
                // Remove prefix + space
                filter.erase(0, filterPrefix.length());
-               if (!theApp.m_pGlobalFileFilter->SetFilter(filter))
+               if (!pGlobalFileFilter->SetFilter(filter))
                {
                        // If filtername is not found use default *.* mask
-                       theApp.m_pGlobalFileFilter->SetFilter(_T("*.*"));
+                       pGlobalFileFilter->SetFilter(_T("*.*"));
                        filter = _T("*.*");
                }
                GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
        }
        else
        {
-               bool bFilterSet = theApp.m_pGlobalFileFilter->SetFilter(filter);
+               bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
                if (!bFilterSet)
-                       m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
+                       m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
                GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
        }
 
@@ -631,14 +688,23 @@ void COpenView::OnOK()
        GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
        LoadComboboxStates();
 
+       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
+       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
+       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
+       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
+       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_NUMBERS, m_bIgnoreNumbers);
+       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
+       GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
+       GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
+
        m_constraint.Persist(true, false);
 
        COpenDoc *pDoc = GetDocument();
        pDoc->m_files = m_files;
        pDoc->m_bRecurse = m_bRecurse;
        pDoc->m_strExt = m_strExt;
-       pDoc->m_strUnpacker = m_strUnpacker;
-       pDoc->m_infoHandler = m_infoHandler;
+       pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
+       pDoc->m_strPredifferPipeline = m_strPredifferPipeline;
        pDoc->m_dwFlags[0] = m_dwFlags[0];
        pDoc->m_dwFlags[1] = m_dwFlags[1];
        pDoc->m_dwFlags[2] = m_dwFlags[2];
@@ -646,11 +712,71 @@ void COpenView::OnOK()
        if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
                GetParentFrame()->PostMessage(WM_CLOSE);
 
+       // Copy the values in pDoc as it will be invalid when COpenFrame is closed. 
+       PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
+       PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
        PathContext tmpPathContext(pDoc->m_files);
-       PackingInfo tmpPackingInfo(pDoc->m_infoHandler);
-       GetMainFrame()->DoFileOpen(
-               &tmpPathContext, std::array<DWORD, 3>(pDoc->m_dwFlags).data(), 
-               nullptr, _T(""), pDoc->m_bRecurse, nullptr, _T(""), &tmpPackingInfo);
+       std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
+       bool recurse = pDoc->m_bRecurse;
+       std::unique_ptr<CMainFrame::OpenFolderParams> pOpenFolderParams;
+       if (!pDoc->m_hiddenItems.empty())
+       {
+               pOpenFolderParams = std::make_unique<CMainFrame::OpenFolderParams>();
+               pOpenFolderParams->m_hiddenItems = pDoc->m_hiddenItems;
+       }
+       if (nID == IDOK)
+       {
+               GetMainFrame()->DoFileOrFolderOpen(
+                       &tmpPathContext, dwFlags.data(),
+                       nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
+       }
+       else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
+       {
+               tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
+               GetMainFrame()->DoFileOrFolderOpen(
+                       &tmpPathContext, dwFlags.data(),
+                       nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
+       }
+       else if (nID == ID_OPEN_WITH_UNPACKER)
+       {
+               CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0], 
+                       CSelectPluginDlg::PluginType::Unpacker, false, this);
+               if (dlg.DoModal() == IDOK)
+               {
+                       tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
+                       GetMainFrame()->DoFileOrFolderOpen(
+                               &tmpPathContext, dwFlags.data(),
+                               nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
+               }
+       }
+       else
+       {
+               GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo, &tmpPrediffingInfo, pOpenFolderParams.get());
+       }
+}
+
+void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
+{
+       bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
+       if (!bFile)
+       {
+               UpdateData(true);
+               PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
+               bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
+                               return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
+                       });
+       }
+       pCmdUI->Enable(bFile);
+}
+
+/** 
+ * @brief Called when dialog is closed with "OK".
+ *
+ * Checks that paths are valid and sets filters.
+ */
+void COpenView::OnOK() 
+{
+       OnCompare(IDOK);
 }
 
 /** 
@@ -670,10 +796,12 @@ void COpenView::OnCancel()
 }
 
 /** 
- * @brief Callled when Open-button for project file is selected.
+ * @brief Called when Open-button for project file is selected.
  */
 void COpenView::OnLoadProject()
 {
+       UpdateData(TRUE);
+
        String fileName = AskProjectFileName(true);
        if (fileName.empty())
                return;
@@ -685,13 +813,15 @@ void COpenView::OnLoadProject()
                return;
        PathContext paths;
        ProjectFileItem& projItem = *project.Items().begin();
-       projItem.GetPaths(paths, m_bRecurse);
-       projItem.GetLeftReadOnly();
-       if (paths.size() < 3)
+       bool bRecurse = m_bRecurse;
+       projItem.GetPaths(paths, bRecurse);
+       if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::IncludeSubfolders))
+               m_bRecurse = bRecurse;
+       if (paths.GetSize() < 3)
        {
                m_strPath[0] = paths[0];
                m_strPath[1] = paths[1];
-               m_strPath[2] = _T("");
+               m_strPath[2].clear();
                m_bReadOnly[0] = projItem.GetLeftReadOnly();
                m_bReadOnly[1] = projItem.GetRightReadOnly();
                m_bReadOnly[2] = false;
@@ -705,9 +835,51 @@ void COpenView::OnLoadProject()
                m_bReadOnly[1] = projItem.GetMiddleReadOnly();
                m_bReadOnly[2] = projItem.GetRightReadOnly();
        }
-       m_strExt = projItem.GetFilter();
+       if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::FileFilter) && projItem.HasFilter())
+               m_strExt = projItem.GetFilter();
+       if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::Plugin))
+       {
+               if (projItem.HasUnpacker())
+                       m_strUnpackerPipeline = projItem.GetUnpacker();
+               if (projItem.HasPrediffer())
+                       m_strPredifferPipeline = projItem.GetPrediffer();
+       }
 
+       if (projItem.HasWindowType())
+               GetDocument()->m_nWindowType = projItem.GetWindowType();
+       if (projItem.HasTableDelimiter())
+               GetDocument()->m_cTableDelimiter = projItem.GetTableDelimiter();
+       if (projItem.HasTableQuote())
+               GetDocument()->m_cTableQuote = projItem.GetTableQuote();
+       if (projItem.HasTableAllowNewLinesInQuotes())
+               GetDocument()->m_bTableAllowNewLinesInQuotes = projItem.GetTableAllowNewLinesInQuotes();
+
+       if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::CompareOptions))
+       {
+               if (projItem.HasIgnoreWhite())
+                       m_nIgnoreWhite = projItem.GetIgnoreWhite();
+               if (projItem.HasIgnoreBlankLines())
+                       m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
+               if (projItem.HasIgnoreCase())
+                       m_bIgnoreCase = projItem.GetIgnoreCase();
+               if (projItem.HasIgnoreEol())
+                       m_bIgnoreEol = projItem.GetIgnoreEol();
+               if (projItem.HasIgnoreNumbers())
+                       m_bIgnoreNumbers = projItem.GetIgnoreNumbers();
+               if (projItem.HasIgnoreCodepage())
+                       m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
+               if (projItem.HasFilterCommentsLines())
+                       m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
+               if (projItem.HasCompareMethod())
+                       m_nCompareMethod = projItem.GetCompareMethod();
+       }
+
+       if ((Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::HiddenItems)) && projItem.HasHiddenItems())
+       {
+               GetDocument()->m_hiddenItems = projItem.GetHiddenItems();
+       }
        UpdateData(FALSE);
+       UpdateButtonStates();
        LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
 }
 
@@ -725,12 +897,36 @@ void COpenView::OnSaveProject()
        ProjectFile project;
        ProjectFileItem projItem;
 
+       bool bSaveFileFilter = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::FileFilter);
+       bool bSaveIncludeSubfolders = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::IncludeSubfolders);
+       bool bSavePlugin = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::Plugin);
+       bool bSaveCompareOptions = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::CompareOptions);
+       bool bSaveHiddenItems = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::HiddenItems);
+
+       projItem.SetSaveFilter(bSaveFileFilter);
+       projItem.SetSaveSubfolders(bSaveIncludeSubfolders);
+       projItem.SetSaveUnpacker(bSavePlugin);
+       projItem.SetSavePrediffer(bSavePlugin);
+       projItem.SetSaveIgnoreWhite(bSaveCompareOptions);
+       projItem.SetSaveIgnoreBlankLines(bSaveCompareOptions);
+       projItem.SetSaveIgnoreCase(bSaveCompareOptions);
+       projItem.SetSaveIgnoreEol(bSaveCompareOptions);
+       projItem.SetSaveIgnoreNumbers(bSaveCompareOptions);
+       projItem.SetSaveIgnoreCodepage(bSaveCompareOptions);
+       projItem.SetSaveFilterCommentsLines(bSaveCompareOptions);
+       projItem.SetSaveCompareMethod(bSaveCompareOptions);
+       projItem.SetSaveHiddenItems(bSaveHiddenItems);
+
        if (!m_strPath[0].empty())
                projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
+       if (!GetDocument()->m_strDesc[0].empty())
+               projItem.SetLeftDesc(GetDocument()->m_strDesc[0]);
        if (m_strPath[2].empty())
        {
                if (!m_strPath[1].empty())
                        projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
+               if (!GetDocument()->m_strDesc[1].empty())
+                       projItem.SetRightDesc(GetDocument()->m_strDesc[1]);
        }
        else
        {
@@ -738,8 +934,12 @@ void COpenView::OnSaveProject()
                        projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
                if (!m_strPath[2].empty())
                        projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
+               if (!GetDocument()->m_strDesc[1].empty())
+                       projItem.SetMiddleDesc(GetDocument()->m_strDesc[1]);
+               if (!GetDocument()->m_strDesc[2].empty())
+                       projItem.SetRightDesc(GetDocument()->m_strDesc[2]);
        }
-       if (!m_strExt.empty())
+       if (bSaveFileFilter && !m_strExt.empty())
        {
                // Remove possbile prefix from the filter name
                String prefix = _("[F] ");
@@ -752,7 +952,39 @@ void COpenView::OnSaveProject()
                strExt = strutils::trim_ws_begin(strExt);
                projItem.SetFilter(strExt);
        }
-       projItem.SetSubfolders(m_bRecurse);
+       if (bSaveIncludeSubfolders)
+               projItem.SetSubfolders(m_bRecurse);
+       if (bSavePlugin)
+       {
+               if (!m_strUnpackerPipeline.empty())
+                       projItem.SetUnpacker(m_strUnpackerPipeline);
+               if (!m_strPredifferPipeline.empty())
+                       projItem.SetPrediffer(m_strPredifferPipeline);
+       }
+       if (GetDocument()->m_nWindowType != -1)
+               projItem.SetWindowType(GetDocument()->m_nWindowType);
+       if (GetDocument()->m_nWindowType == 2 /* table */)
+       {
+               projItem.SetTableDelimiter(GetDocument()->m_cTableDelimiter);
+               projItem.SetTableQuote(GetDocument()->m_cTableQuote);
+               projItem.SetTableAllowNewLinesInQuotes(GetDocument()->m_bTableAllowNewLinesInQuotes);
+       }
+
+       if (bSaveCompareOptions)
+       {
+               projItem.SetIgnoreWhite(m_nIgnoreWhite);
+               projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
+               projItem.SetIgnoreCase(m_bIgnoreCase);
+               projItem.SetIgnoreEol(m_bIgnoreEol);
+               projItem.SetIgnoreNumbers(m_bIgnoreNumbers);
+               projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
+               projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
+               projItem.SetCompareMethod(m_nCompareMethod);
+       }
+
+       if (bSaveHiddenItems)
+               projItem.SetHiddenItems(GetDocument()->m_hiddenItems);
+
        project.Items().push_back(projItem);
 
        if (!theApp.SaveProjectFile(fileName, project))
@@ -761,22 +993,37 @@ void COpenView::OnSaveProject()
        LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
 }
 
-void COpenView::OnDropDownSaveProject(NMHDR *pNMHDR, LRESULT *pResult)
+void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
 {
-       CRect rcButton, rcView;
-       GetDlgItem(ID_SAVE_PROJECT)->GetWindowRect(&rcButton);
+       CRect rcButton;
+       GetDlgItem(nID)->GetWindowRect(&rcButton);
        BCMenu menu;
-       VERIFY(menu.LoadMenu(IDR_POPUP_PROJECT));
+       VERIFY(menu.LoadMenu(nPopupID));
        theApp.TranslateMenu(menu.m_hMenu);
        CMenu* pPopup = menu.GetSubMenu(0);
        if (pPopup != nullptr)
        {
-               pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
+               if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
+               {
+                       UpdateData(TRUE);
+                       String tmpPath[3];
+                       for (int i = 0; i < 3; i++)
+                               tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
+                       String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
+                       CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
+               }
+               pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                        rcButton.left, rcButton.bottom, GetMainFrame());
        }
        *pResult = 0;
 }
 
+template<UINT id, UINT popupid>
+void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
+{
+       DropDown(pNMHDR, pResult, id, popupid);
+}
+
 /** 
  * @brief Allow user to select a file to open/save.
  */
@@ -809,6 +1056,8 @@ void COpenView::LoadComboboxStates()
        m_ctlPath[1].LoadState(_T("Files\\Right"));
        m_ctlPath[2].LoadState(_T("Files\\Option"));
        m_ctlExt.LoadState(_T("Files\\Ext"));
+       m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
+       m_ctlPredifferPipeline.LoadState(_T("Files\\Prediffer"));
 }
 
 /** 
@@ -820,6 +1069,8 @@ void COpenView::SaveComboboxStates()
        m_ctlPath[1].SaveState(_T("Files\\Right"));
        m_ctlPath[2].SaveState(_T("Files\\Option"));
        m_ctlExt.SaveState(_T("Files\\Ext"));
+       m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
+       m_ctlPredifferPipeline.SaveState(_T("Files\\Prediffer"));
 }
 
 struct UpdateButtonStatesThreadParams
@@ -846,8 +1097,8 @@ static UINT UpdateButtonStatesThread(LPVOID lpParam)
                bool bIsaFolderCompare = true;
                bool bIsaFileCompare = true;
                bool bInvalid[3] = {false, false, false};
-               int iStatusMsgId = 0;
-               int iUnpackerStatusMsgId = 0;
+               paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
+               int iStatusMsgId = IDS_OPEN_FILESDIRS;
 
                UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
                PathContext paths = pParams->m_paths;
@@ -863,18 +1114,23 @@ static UINT UpdateButtonStatesThread(LPVOID lpParam)
 
                if (!bProject)
                {
-                       if (paths::DoesPathExist(paths[0], IsArchiveFile) == paths::DOES_NOT_EXIST)
-                               bInvalid[0] = true;
-                       if (paths::DoesPathExist(paths[1], IsArchiveFile) == paths::DOES_NOT_EXIST)
-                               bInvalid[1] = true;
-                       if (paths.GetSize() > 2 && paths::DoesPathExist(paths[2], IsArchiveFile) == paths::DOES_NOT_EXIST)
-                               bInvalid[2] = true;
+                       for (int i = 0; i < paths.GetSize(); ++i)
+                       {
+                               pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
+                               if (pathType[i] == paths::DOES_NOT_EXIST)
+                               {
+                                       if (paths::IsURL(paths[i]))
+                                               pathType[i] = paths::IS_EXISTING_FILE;
+                                       else
+                                               bInvalid[i] = true;
+                               }
+                       }
                }
 
                // Enable buttons as appropriate
                if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
                {
-                       paths::PATH_EXISTENCE pathsType = paths::DOES_NOT_EXIST;
+                       paths::PATH_EXISTENCE pathsType = pathType[0];
 
                        if (paths.GetSize() <= 2)
                        {
@@ -883,11 +1139,15 @@ static UINT UpdateButtonStatesThread(LPVOID lpParam)
                                else if (bInvalid[0])
                                        iStatusMsgId = IDS_OPEN_LEFTINVALID;
                                else if (bInvalid[1])
-                                       iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
+                               {
+                                       if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
+                                               iStatusMsgId = IDS_OPEN_FILESDIRS;
+                                       else
+                                               iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
+                               }
                                else if (!bInvalid[0] && !bInvalid[1])
                                {
-                                       pathsType = paths::GetPairComparability(paths, IsArchiveFile);
-                                       if (pathsType == paths::DOES_NOT_EXIST)
+                                       if (pathType[0] != pathType[1])
                                                iStatusMsgId = IDS_OPEN_MISMATCH;
                                        else
                                                iStatusMsgId = IDS_OPEN_FILESDIRS;
@@ -911,13 +1171,14 @@ static UINT UpdateButtonStatesThread(LPVOID lpParam)
                                        iStatusMsgId = IDS_OPEN_LEFTINVALID;
                                else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
                                {
-                                       pathsType = paths::GetPairComparability(paths, IsArchiveFile);
-                                       if (pathsType == paths::DOES_NOT_EXIST)
+                                       if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
                                                iStatusMsgId = IDS_OPEN_MISMATCH;
                                        else
                                                iStatusMsgId = IDS_OPEN_FILESDIRS;
                                }
                        }
+                       if (iStatusMsgId != IDS_OPEN_FILESDIRS)
+                               pathsType = paths::DOES_NOT_EXIST;
                        bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
                        bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
                        // Both will be `false` if incompatibilities or something is missing
@@ -938,8 +1199,6 @@ static UINT UpdateButtonStatesThread(LPVOID lpParam)
 void COpenView::UpdateResources()
 {
        theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
-       if (m_strUnpacker != m_infoHandler.m_PluginName)
-               m_strUnpacker = theApp.LoadString(IDS_OPEN_UNPACKERDISABLED);
 }
 
 /** 
@@ -963,10 +1222,7 @@ void COpenView::UpdateButtonStates()
 
        UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
        pParams->m_hWnd = this->m_hWnd;
-       if (m_strPath[2].empty())
-               pParams->m_paths = PathContext(m_strPath[0], m_strPath[1]);
-       else
-               pParams->m_paths = PathContext(m_strPath[0], m_strPath[1], m_strPath[2]);
+       pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
 
        PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
 }
@@ -990,8 +1246,9 @@ void COpenView::TerminateThreadIfRunning()
 /**
  * @brief Called when user changes selection in left/middle/right path's combo box.
  */
-void COpenView::OnSelchangeCombo(int index
+void COpenView::OnSelchangePathCombo(UINT nId
 {
+       const int index = nId - IDC_PATH0_COMBO;
        int sel = m_ctlPath[index].GetCurSel();
        if (sel != CB_ERR)
        {
@@ -1004,12 +1261,6 @@ void COpenView::OnSelchangeCombo(int index)
        UpdateButtonStates();
 }
 
-template <int N>
-void COpenView::OnSelchangePathCombo() 
-{
-       OnSelchangeCombo(N);
-}
-
 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult) 
 {
        if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
@@ -1032,9 +1283,9 @@ void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
 /**
  * @brief Called every time paths are edited.
  */
-template <int N>
-void COpenView::OnEditEvent()
+void COpenView::OnEditEvent(UINT nID)
 {
+       const int N = nID - IDC_PATH0_COMBO;
        if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
        {
                int const len = edit->GetWindowTextLength();
@@ -1064,70 +1315,93 @@ void COpenView::OnEditEvent()
  */
 void COpenView::OnTimer(UINT_PTR nIDEvent)
 {
-       if (nIDEvent == IDT_CHECKFILES)
+       if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
                UpdateButtonStates();
 
-       CFormView::OnTimer(nIDEvent);
+       __super::OnTimer(nIDEvent);
 }
 
 /**
  * @brief Called when users selects plugin browse button.
  */
-void COpenView::OnSelectUnpacker()
+void COpenView::OnSelectPlugin(UINT nID)
 {
        paths::PATH_EXISTENCE pathsType;
        UpdateData(TRUE);
 
-       int index;
        int nFiles = 0;
-       for (index = 0; index < std::size(m_strPath); index++)
+       for (auto& strPath: m_strPath)
        {
-               if (index == 2 && m_strPath[index].empty())
+               if (nFiles == 2 && strPath.empty())
                        break;
                m_files.SetSize(nFiles + 1);
-               m_files[nFiles] = m_strPath[index];
+               m_files[nFiles] = strPath;
                nFiles++;
        }
-       pathsType = paths::GetPairComparability(m_files);
+       PathContext tmpFiles = m_files;
+       if (tmpFiles.GetSize() == 2 && tmpFiles[1].empty())
+               tmpFiles[1] = tmpFiles[0];
+       pathsType = paths::GetPairComparability(tmpFiles, IsArchiveFile);
 
-       if (pathsType != paths::IS_EXISTING_FILE) 
+       if (pathsType == paths::IS_EXISTING_DIR || (pathsType == paths::DOES_NOT_EXIST &&
+               !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); })))
                return;
 
        // let the user select a handler
-       CSelectUnpackerDlg dlg(m_files[0], this);
-       PackingInfo infoUnpacker(PLUGIN_AUTO);
-       dlg.SetInitialInfoHandler(&infoUnpacker);
-
+       CSelectPluginDlg dlg(nID == IDC_SELECT_UNPACKER ? m_strUnpackerPipeline : m_strPredifferPipeline, m_files[0], 
+               nID == IDC_SELECT_UNPACKER ? CSelectPluginDlg::PluginType::Unpacker : CSelectPluginDlg::PluginType::Prediffer, false, this);
        if (dlg.DoModal() == IDOK)
        {
-               m_infoHandler = dlg.GetInfoHandler();
-
-               m_strUnpacker = m_infoHandler.m_PluginName;
-
+               if (nID == IDC_SELECT_UNPACKER)
+                       m_strUnpackerPipeline = dlg.GetPluginPipeline();
+               else
+                       m_strPredifferPipeline = dlg.GetPluginPipeline();
                UpdateData(FALSE);
        }
 }
 
 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
 {
-       bool bIsaFolderCompare = LOWORD(wParam) != 0;
-       bool bIsaFileCompare = HIWORD(wParam) != 0;
-       bool bProject = HIWORD(lParam) != 0;
+       const bool bIsaFolderCompare = LOWORD(wParam) != 0;
+       const bool bIsaFileCompare = HIWORD(wParam) != 0;
+       const bool bProject = HIWORD(lParam) != 0;
+       const int iStatusMsgId = LOWORD(lParam);
 
        EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
 
-       EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
-       EnableDlgItem(IDC_UNPACKER_EDIT, bIsaFileCompare);
-       EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
+       for (auto nID : { IDC_FILES_DIRS_GROUP4, IDC_UNPACKER_COMBO, IDC_SELECT_UNPACKER })
+       {
+               EnableDlgItem(nID, bIsaFileCompare);
+       }
 
+       if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
+       {
+               for (auto nID : { IDC_FILES_DIRS_GROUP5, IDC_PREDIFFER_COMBO, IDC_SELECT_PREDIFFER })
+               {
+                       GetDlgItem(nID)->ShowWindow(bIsaFileCompare ? SW_SHOW : SW_HIDE);
+                       EnableDlgItem(nID, bIsaFileCompare);
+               }
+
+               for (auto nID : { IDC_FILES_DIRS_GROUP3, IDC_EXT_COMBO, IDC_SELECT_FILTER, IDC_RECURS_CHECK })
+               {
+                       GetDlgItem(nID)->ShowWindow((bIsaFolderCompare || !bIsaFileCompare) ? SW_SHOW : SW_HIDE);
+                       EnableDlgItem(nID, bIsaFolderCompare);
+               }
+       }
        
-       EnableDlgItem(IDC_FILES_DIRS_GROUP3,  bIsaFolderCompare);
-       EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
-       EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
-       EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
-       
-       SetStatus(LOWORD(lParam));
+       SetStatus(iStatusMsgId);
 
+       if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
+       {
+               if (m_retryCount == 0)
+                       SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
+               m_retryCount++;
+       }
+       else
+       {
+               KillTimer(IDT_RETRY);
+               m_retryCount = 0;
+       }
        return 0;
 }
 
@@ -1143,34 +1417,22 @@ void COpenView::SetStatus(UINT msgID)
        SetDlgItemText(IDC_OPEN_STATUS, msg);
 }
 
-/**
- * @brief Set the plugin edit box text.
- * Plugin edit box is at the same time a plugin status view. This function
- * sets the status text.
- * @param [in] msgID Resource ID of status text to set.
- */
-void COpenView::SetUnpackerStatus(UINT msgID)
-{
-       String msg = (msgID == 0 ? m_strUnpacker : theApp.LoadString(msgID));
-       SetDlgItemText(IDC_UNPACKER_EDIT, msg);
-}
-
 /** 
  * @brief Called when "Select..." button for filters is selected.
  */
 void COpenView::OnSelectFilter()
 {
-       String filterPrefix = _("[F] ");
        String curFilter;
+       auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
 
-       const bool bUseMask = theApp.m_pGlobalFileFilter->IsUsingMask();
+       const bool bUseMask = pGlobalFileFilter->IsUsingMask();
        GetDlgItemText(IDC_EXT_COMBO, curFilter);
        curFilter = strutils::trim_ws(curFilter);
 
        GetMainFrame()->SelectFilter();
        
-       String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
-       if (theApp.m_pGlobalFileFilter->IsUsingMask())
+       String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
+       if (pGlobalFileFilter->IsUsingMask())
        {
                // If we had filter chosen and now has mask we can overwrite filter
                if (!bUseMask || curFilter[0] != '*')
@@ -1180,6 +1442,7 @@ void COpenView::OnSelectFilter()
        }
        else
        {
+               String filterPrefix = _("[F] ");
                filterNameOrMask = filterPrefix + filterNameOrMask;
                SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
        }
@@ -1190,61 +1453,146 @@ void COpenView::OnOptions()
        GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
 }
 
-void COpenView::OnDropDownOptions(NMHDR *pNMHDR, LRESULT *pResult)
+/**
+ * @brief Set "Whitespaces" setting.
+ * @param [in] nID Menu ID of the selected item
+ */
+void COpenView::OnDiffWhitespace(UINT nID)
 {
-       NMTOOLBAR dropDown = { 0 };
-       dropDown.hdr.code = TBN_DROPDOWN;
-       dropDown.hdr.hwndFrom = GetMainFrame()->GetDescendantWindow(AFX_IDW_TOOLBAR)->GetSafeHwnd();
-       dropDown.hdr.idFrom = AFX_IDW_TOOLBAR;
-       GetDlgItem(IDC_OPTIONS)->GetWindowRect(&dropDown.rcButton);
-       GetMainFrame()->ScreenToClient(&dropDown.rcButton);
-       GetMainFrame()->SendMessage(WM_NOTIFY, dropDown.hdr.idFrom, reinterpret_cast<LPARAM>(&dropDown));
-       *pResult = 0;
+       assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
+
+       m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
 }
 
-/** 
- * @brief Read paths and filter from project file.
- * Reads the given project file. After the file is read, found paths and
- * filter is updated to dialog GUI. Other possible settings found in the
- * project file are kept in memory and used later when loading paths
- * selected.
- * @param [in] path Path to the project file.
- * @return `true` if the project file was successfully loaded, `false` otherwise.
+/**
+ * @brief Update "Whitespaces" state.
+ * @param [in] pCmdUI UI component to update.
  */
-bool COpenView::LoadProjectFile(const String &path)
-{
-       String filterPrefix = _("[F] ");
-       ProjectFile prj;
-
-       if (!theApp.LoadProjectFile(path, prj))
-               return false;
-       if (prj.Items().size() == 0)
-               return false;
-       bool recurse;
-       ProjectFileItem& projItem = *prj.Items().begin();
-       projItem.GetPaths(m_files, recurse);
-       m_bRecurse = recurse;
-       m_dwFlags[0] &= ~FFILEOPEN_READONLY;
-       m_dwFlags[0] |= projItem.GetLeftReadOnly() ?    FFILEOPEN_READONLY : 0;
-       if (m_files.GetSize() < 3)
-       {
-               m_dwFlags[1] &= ~FFILEOPEN_READONLY;
-               m_dwFlags[1] |= projItem.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
-       }
-       else
-       {
-               m_dwFlags[1] &= ~FFILEOPEN_READONLY;
-               m_dwFlags[1] |= projItem.GetMiddleReadOnly() ? FFILEOPEN_READONLY : 0;
-               m_dwFlags[2] &= ~FFILEOPEN_READONLY;
-               m_dwFlags[2] |= projItem.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
-       }
-       if (projItem.HasFilter())
-       {
-               m_strExt = strutils::trim_ws(projItem.GetFilter());
-               if (m_strExt[0] != '*')
-                       m_strExt.insert(0, filterPrefix);
-       }
-       return true;
+void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
+}
+
+/**
+ * @brief Toggle "Ignore blank lines" setting.
+ */
+void COpenView::OnDiffIgnoreBlankLines()
+{
+       m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
+}
+
+/**
+ * @brief Update "Ignore blank lines" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetCheck(m_bIgnoreBlankLines);
+}
+
+/**
+ * @brief Toggle "Ignore case" setting.
+ */
+void COpenView::OnDiffIgnoreCase()
+{
+       m_bIgnoreCase = !m_bIgnoreCase;
+}
+
+/**
+ * @brief Update "Ignore case" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetCheck(m_bIgnoreCase);
+}
+
+/**
+ * @brief Toggle "Ignore carriage return differences" setting.
+ */
+void COpenView::OnDiffIgnoreEOL()
+{
+       m_bIgnoreEol = !m_bIgnoreEol;
+}
+
+/**
+ * @brief Update "Ignore carriage return differences" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetCheck(m_bIgnoreEol);
+}
+
+/**
+ * @brief Toggle "Ignore numbers" setting.
+ */
+void COpenView::OnDiffIgnoreNumbers()
+{
+       m_bIgnoreNumbers = !m_bIgnoreNumbers;
+}
+
+/**
+ * @brief Update "Ignore numbers" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateDiffIgnoreNumbers(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetCheck(m_bIgnoreNumbers);
+}
+
+/**
+ * @brief Toggle "Ignore codepage differences" setting.
+ */
+void COpenView::OnDiffIgnoreCP()
+{
+       m_bIgnoreCodepage = !m_bIgnoreCodepage;
+}
+
+/**
+ * @brief Update "Ignore codepage differences" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetCheck(m_bIgnoreCodepage);
+}
+
+/**
+ * @brief Toggle "Ignore comment differences" setting.
+ */
+void COpenView::OnDiffIgnoreComments()
+{
+       m_bFilterCommentsLines = !m_bFilterCommentsLines;
+}
+
+/**
+ * @brief Update "Ignore comment differences" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetCheck(m_bFilterCommentsLines);
+}
+
+/**
+ * @brief Set "Compare method" setting.
+ * @param [in] nID Menu ID of the selected item
+ */
+void COpenView::OnCompareMethod(UINT nID)
+{
+       assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
+
+       m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
+}
+
+/**
+ * @brief Update "Compare method" state.
+ * @param [in] pCmdUI UI component to update.
+ */
+void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
+{
+       pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
 }
 
 /** 
@@ -1253,8 +1601,8 @@ bool COpenView::LoadProjectFile(const String &path)
  */
 void COpenView::TrimPaths()
 {
-       for (int index = 0; index < std::size(m_strPath); index++)
-               m_strPath[index] = strutils::trim_ws(m_strPath[index]);
+       for (auto& strPath: m_strPath)
+               strPath = strutils::trim_ws(strPath);
 }
 
 /** 
@@ -1267,18 +1615,23 @@ void COpenView::TrimPaths()
  */
 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
 {
-       CFormView::OnActivate(nState, pWndOther, bMinimized);
+       __super::OnActivate(nState, pWndOther, bMinimized);
 
        if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
                UpdateButtonStates();
 }
 
-template <int MSG, int WPARAM, int LPARAM>
-void COpenView::OnEditAction()
+void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
 {
        CWnd *pCtl = GetFocus();
        if (pCtl != nullptr)
-               pCtl->PostMessage(MSG, WPARAM, LPARAM);
+               pCtl->PostMessage(msg, wParam, lParam);
+}
+
+template <int MSG, int WPARAM, int LPARAM>
+void COpenView::OnEditAction()
+{
+       OnEditAction(MSG, WPARAM, LPARAM);
 }
 
 /**