OSDN Git Service

Use __super
[winmerge-jp/winmerge-jp.git] / Src / OpenView.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /** 
8  * @file  OpenView.cpp
9  *
10  * @brief Implementation of the COpenView class
11  */
12
13 #include "stdafx.h"
14 #include "OpenView.h"
15 #include <vector>
16 #include <sys/stat.h>
17 #include "UnicodeString.h"
18 #include "Merge.h"
19 #include "OpenDoc.h"
20 #include "ProjectFile.h"
21 #include "paths.h"
22 #include "SelectPluginDlg.h"
23 #include "OptionsDef.h"
24 #include "MainFrm.h"
25 #include "OptionsMgr.h"
26 #include "FileOrFolderSelect.h"
27 #include "7zCommon.h"
28 #include "Constants.h"
29 #include "Bitmap.h"
30 #include "DropHandler.h"
31 #include "FileFilterHelper.h"
32 #include "Plugins.h"
33 #include "BCMenu.h"
34 #include "LanguageSelect.h"
35 #include "Win_VersionHelper.h"
36 #include "OptionsProject.h"
37
38 #ifdef _DEBUG
39 #define new DEBUG_NEW
40 #endif
41
42 #ifndef BCN_DROPDOWN
43 #define BCN_DROPDOWN            (BCN_FIRST + 0x0002)
44 #endif
45
46 // Timer ID and timeout for delaying path validity check
47 const UINT IDT_CHECKFILES = 1;
48 const UINT IDT_RETRY = 2;
49 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
50 const int RETRY_MAX = 3;
51
52 /** @brief Location for Open-dialog specific help to open. */
53 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
54
55 // COpenView
56
57 IMPLEMENT_DYNCREATE(COpenView, CFormView)
58
59 BEGIN_MESSAGE_MAP(COpenView, CFormView)
60         //{{AFX_MSG_MAP(COpenView)
61         ON_CONTROL_RANGE(BN_CLICKED, IDC_PATH0_BUTTON, IDC_PATH2_BUTTON, OnPathButton)
62         ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
63         ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
64         ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
65         ON_CONTROL_RANGE(CBN_SELCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSelchangePathCombo)
66         ON_CONTROL_RANGE(CBN_EDITCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnEditEvent)
67         ON_CONTROL_RANGE(BN_CLICKED, IDC_SELECT_UNPACKER, IDC_SELECT_PREDIFFER, OnSelectPlugin)
68         ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
69         ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
70         ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
71         ON_NOTIFY_RANGE(CBEN_BEGINEDIT, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSetfocusPathCombo)
72         ON_NOTIFY_RANGE(CBEN_DRAGBEGIN, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnDragBeginPathCombo)
73         ON_WM_TIMER()
74         ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
75         ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
76         ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, (OnDropDown<IDC_OPTIONS, IDR_POPUP_PROJECT_DIFF_OPTIONS>))
77         ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
78         ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
79         ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
80         ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
81         ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
82         ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
83         ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
84         ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
85         ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnDiffIgnoreNumbers)
86         ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnUpdateDiffIgnoreNumbers)
87         ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
88         ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
89         ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
90         ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
91         ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
92         ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
93         ON_WM_ACTIVATE()
94         ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
95         ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
96         ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
97         ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
98         ON_COMMAND(IDOK, OnOK)
99         ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
100         ON_COMMAND(IDCANCEL, OnCancel)
101         ON_COMMAND(ID_HELP, OnHelp)
102         ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
103         ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
104         ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
105         ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
106         ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
107         ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnCompare)
108         ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateCompare)
109         ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
110         ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
111         ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
112         ON_WM_PAINT()
113         ON_WM_LBUTTONUP()
114         ON_WM_MOUSEMOVE()
115         ON_WM_WINDOWPOSCHANGING()
116         ON_WM_WINDOWPOSCHANGED()
117         ON_WM_NCHITTEST()
118         ON_WM_DESTROY()
119         //}}AFX_MSG_MAP
120 END_MESSAGE_MAP()
121
122 // COpenView construction/destruction
123
124 COpenView::COpenView()
125         : CFormView(COpenView::IDD)
126         , m_pUpdateButtonStatusThread(nullptr)
127         , m_bRecurse(false)
128         , m_pDropHandler(nullptr)
129         , m_dwFlags()
130         , m_bAutoCompleteReady()
131         , m_bReadOnly {false, false, false}
132         , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
133         , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
134         , m_retryCount(0)
135         , m_nIgnoreWhite(0)
136         , m_bIgnoreBlankLines(false)
137         , m_bIgnoreCase(false)
138         , m_bIgnoreEol(false)
139         , m_bIgnoreNumbers(false)
140         , m_bIgnoreCodepage(false)
141         , m_bFilterCommentsLines(false)
142         , m_nCompareMethod(0)
143 {
144         // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
145         // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
146         m_bInsideUpdate = TRUE;
147 }
148
149 COpenView::~COpenView()
150 {
151         TerminateThreadIfRunning();
152 }
153
154 void COpenView::DoDataExchange(CDataExchange* pDX)
155 {
156         __super::DoDataExchange(pDX);
157         //{{AFX_DATA_MAP(COpenView)
158         DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
159         DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
160         DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
161         DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
162         DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
163         DDX_Control(pDX, IDC_PREDIFFER_COMBO, m_ctlPredifferPipeline);
164         DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
165         DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
166         DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
167         DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
168         DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
169         DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
170         DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
171         DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
172         DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
173         DDX_CBStringExact(pDX, IDC_PREDIFFER_COMBO, m_strPredifferPipeline);
174         //}}AFX_DATA_MAP
175 }
176
177 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
178 {
179         // TODO: Modify the Window class or styles here by modifying
180         //  the CREATESTRUCT cs
181         cs.style &= ~WS_BORDER;
182         cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
183         return __super::PreCreateWindow(cs);
184 }
185
186 void COpenView::OnInitialUpdate()
187 {
188         if (!IsVista_OrGreater())
189         {
190                 // fallback for XP 
191                 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
192                 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
193                 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
194         }
195
196         m_sizeOrig = GetTotalSize();
197
198         theApp.TranslateDialog(m_hWnd);
199
200         if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
201         {
202                 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
203                 m_image.Create(1, 1, 24, 0);
204         }
205
206         __super::OnInitialUpdate();
207
208         // set caption to "swap paths" button
209         LOGFONT lf;
210         GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
211         lf.lfCharSet = SYMBOL_CHARSET;
212         lstrcpy(lf.lfFaceName, _T("Wingdings"));
213         m_fontSwapButton.CreateFontIndirect(&lf);
214         const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
215         for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
216         {
217                 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
218                 SetDlgItemText(ids[i], _T("\xf4"));
219         }
220
221         m_constraint.InitializeCurrentSize(this);
222         m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
223         m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
224         m_constraint.SetScrollScale(this, 1.0, 1.0);
225         m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
226         m_constraint.DisallowHeightGrowth();
227         //m_constraint.SubclassWnd(); // install subclassing
228
229         m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
230         m_constraint.UpdateSizes();
231
232         COpenDoc *pDoc = GetDocument();
233
234         CString strTitle;
235         GetWindowText(strTitle);
236         pDoc->SetTitle(strTitle);
237
238         m_files = pDoc->m_files;
239         m_bRecurse = pDoc->m_bRecurse;
240         m_strExt = pDoc->m_strExt;
241         m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
242         m_strPredifferPipeline = pDoc->m_strPredifferPipeline;
243         m_dwFlags[0] = pDoc->m_dwFlags[0];
244         m_dwFlags[1] = pDoc->m_dwFlags[1];
245         m_dwFlags[2] = pDoc->m_dwFlags[2];
246
247         m_ctlPath[0].SetFileControlStates();
248         m_ctlPath[1].SetFileControlStates(true);
249         m_ctlPath[2].SetFileControlStates(true);
250         m_ctlUnpackerPipeline.SetFileControlStates(true);
251         m_ctlPredifferPipeline.SetFileControlStates(true);
252
253         for (int file = 0; file < m_files.GetSize(); file++)
254         {
255                 m_strPath[file] = m_files[file];
256                 m_ctlPath[file].SetWindowText(m_files[file].c_str());
257                 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
258         }
259
260         m_ctlPath[0].AttachSystemImageList();
261         m_ctlPath[1].AttachSystemImageList();
262         m_ctlPath[2].AttachSystemImageList();
263         LoadComboboxStates();
264
265         m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
266         m_ctlPredifferPipeline.SetWindowText(m_strPredifferPipeline.c_str());
267
268         bool bDoUpdateData = true;
269         for (auto& strPath: m_strPath)
270         {
271                 if (!strPath.empty())
272                         bDoUpdateData = false;
273         }
274         UpdateData(bDoUpdateData);
275
276         auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
277         String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
278         bool bMask = pGlobalFileFilter->IsUsingMask();
279
280         if (!bMask)
281         {
282                 String filterPrefix = _("[F] ");
283                 filterNameOrMask = filterPrefix + filterNameOrMask;
284         }
285
286         int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
287         if (ind != CB_ERR)
288                 m_ctlExt.SetCurSel(ind);
289         else
290         {
291                 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
292                 if (ind != CB_ERR)
293                         m_ctlExt.SetCurSel(ind);
294                 else
295                         LogErrorString(_T("Failed to add string to filters combo list!"));
296         }
297
298         if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
299         {
300                 EnableDlgItem(IDOK, true);
301                 EnableDlgItem(IDC_UNPACKER_COMBO, true);
302                 EnableDlgItem(IDC_SELECT_UNPACKER, true);
303         }
304
305         UpdateButtonStates();
306
307         bool bOverwriteRecursive = false;
308         if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
309                 bOverwriteRecursive = true;
310         if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
311                 bOverwriteRecursive = true;
312         if (!bOverwriteRecursive)
313                 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
314
315         m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
316         m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
317         m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
318         m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
319         m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
320         m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
321         m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
322         m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
323
324         UpdateData(FALSE);
325         SetStatus(IDS_OPEN_FILESDIRS);
326
327         m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
328         RegisterDragDrop(m_hWnd, m_pDropHandler);
329 }
330
331 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
332 {
333         m_bRecurse = GetDocument()->m_bRecurse;
334
335         m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
336         m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
337         m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
338         m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
339         m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
340         m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
341         m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
342         m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
343
344         UpdateData(FALSE);
345 }
346
347 // COpenView diagnostics
348
349 #ifdef _DEBUG
350 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
351 {
352         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
353         return (COpenDoc*)m_pDocument;
354 }
355 #endif //_DEBUG
356
357 /////////////////////////////////////////////////////////////////////////////
358 // COpenView message handlers
359
360 void COpenView::OnPaint()
361 {
362         CPaintDC dc(this);
363         CRect rc;
364         GetClientRect(&rc);
365
366         // Draw the logo image
367         CSize size{ m_image.GetWidth(), m_image.GetHeight() };
368         CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
369         m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
370         // And extend it to the Right boundary
371         dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
372
373         // Draw the resize gripper in the Lower Right corner.
374         CRect rcGrip = rc;
375         rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
376         rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
377         dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
378
379         // Draw a line to separate the Status Line
380         CPen newPen(PS_SOLID, 1, RGB(208, 208, 208));   // a very light gray
381         CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
382
383         CRect rcStatus;
384         GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
385         ScreenToClient(&rcStatus);
386         dc.MoveTo(0, rcStatus.top - 3);
387         dc.LineTo(rc.right, rcStatus.top - 3);
388         dc.SelectObject(oldpen);
389
390         __super::OnPaint();
391 }
392
393 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
394 {
395         if (::GetCapture() == m_hWnd)
396         {
397                 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
398                         CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
399                 {
400                         switch (int const id1 = pwndHit->GetDlgCtrlID())
401                         {
402                         case IDC_PATH0_COMBO:
403                         case IDC_PATH1_COMBO:
404                         case IDC_PATH2_COMBO:
405                                 int id2 = 0;
406                                 CWnd *pwndChild = GetFocus();
407                                 if (pwndChild && IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
408                                 {
409                                         id2 = pwndChild->GetDlgCtrlID();
410                                         pwndChild = pwndChild->GetParent();
411                                 } while (pwndChild != this);
412                                 switch (id2)
413                                 {
414                                 case IDC_PATH0_COMBO:
415                                 case IDC_PATH1_COMBO:
416                                 case IDC_PATH2_COMBO:
417                                         String s1, s2;
418                                         GetDlgItemText(id1, s1);
419                                         GetDlgItemText(id2, s2);
420                                         SetDlgItemText(id1, s2);
421                                         SetDlgItemText(id2, s1);
422                                         pwndHit->SetFocus();
423                                         break;
424                                 }
425                                 break;
426                         }
427                 }
428                 ReleaseCapture();
429         }
430 }
431
432 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
433 {
434         if (::GetCapture() == m_hWnd)
435         {
436                 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
437                         CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
438                 {
439                         switch (pwndHit->GetDlgCtrlID())
440                         {
441                         case IDC_PATH0_COMBO:
442                         case IDC_PATH1_COMBO:
443                         case IDC_PATH2_COMBO:
444                                 if (!pwndHit->IsChild(GetFocus()))
445                                 {
446                                         SetCursor(m_hIconRotate);
447                                         break;
448                                 }
449                                 [[fallthrough]];
450                         default:
451                                 SetCursor(m_hCursorNo);
452                                 break;
453                         }
454                 }
455         }
456 }
457
458 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
459 {
460         if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
461         {
462                 CFrameWnd *const pFrameWnd = GetParentFrame();
463                 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
464                 {
465                         CRect rc;
466                         pFrameWnd->GetClientRect(&rc);
467                         lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
468                         lpwndpos->cy = m_sizeOrig.cy;
469                         if (lpwndpos->flags & SWP_NOOWNERZORDER)
470                         {
471                                 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
472                                 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
473                                 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
474                                 if (lpwndpos->y < 0)
475                                         lpwndpos->y = 0;
476                         }
477                         else if (pFrameWnd->IsZoomed())
478                         {
479                                 lpwndpos->cx = m_totalLog.cx;
480                                 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
481                                 if (lpwndpos->y < 0)
482                                         lpwndpos->y = 0;
483                         }
484                         if (lpwndpos->cx > rc.Width())
485                                 lpwndpos->cx = rc.Width();
486                         if (lpwndpos->cx < m_sizeOrig.cx)
487                                 lpwndpos->cx = m_sizeOrig.cx;
488                         lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
489                         if (lpwndpos->x < 0)
490                                 lpwndpos->x = 0;
491                 }
492         }
493 }
494
495 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
496 {
497         if (lpwndpos->flags & SWP_FRAMECHANGED)
498         {
499                 m_constraint.UpdateSizes();
500                 CFrameWnd *const pFrameWnd = GetParentFrame();
501                 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
502                 {
503                         m_constraint.Persist(true, false);
504                         WINDOWPLACEMENT wp = {};
505                         wp.length = sizeof wp;
506                         pFrameWnd->GetWindowPlacement(&wp);
507                         CRect rc;
508                         GetWindowRect(&rc);
509                         pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
510                         wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
511                         wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
512                         pFrameWnd->SetWindowPlacement(&wp);
513                 }
514         }
515         __super::OnWindowPosChanged(lpwndpos);
516 }
517
518 void COpenView::OnDestroy()
519 {
520         if (m_pDropHandler != nullptr)
521                 RevokeDragDrop(m_hWnd);
522
523         __super::OnDestroy();
524 }
525
526 LRESULT COpenView::OnNcHitTest(CPoint point)
527 {
528         if (GetParentFrame()->IsZoomed())
529         {
530                 CRect rc;
531                 GetWindowRect(&rc);
532                 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
533                 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
534                 if (PtInRect(&rc, point))
535                         return HTRIGHT;
536         }
537         return __super::OnNcHitTest(point);
538 }
539
540 /** 
541  * @brief Called when "Browse..." button is selected for N path.
542  */
543 void COpenView::OnPathButton(UINT nId)
544 {
545         const int index = nId - IDC_PATH0_BUTTON;
546         String s;
547         String sfolder;
548         UpdateData(TRUE); 
549
550         paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
551         switch (existence)
552         {
553         case paths::IS_EXISTING_DIR:
554                 sfolder = m_strPath[index];
555                 break;
556         case paths::IS_EXISTING_FILE:
557                 sfolder = paths::GetPathOnly(m_strPath[index]);
558                 break;
559         case paths::DOES_NOT_EXIST:
560                 if (!m_strPath[index].empty())
561                         sfolder = paths::GetParentPath(m_strPath[index]);
562                 break;
563         default:
564                 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
565                 break;
566         }
567
568         if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
569         {
570                 m_strPath[index] = s;
571                 m_strBrowsePath[index] = std::move(s);
572                 UpdateData(FALSE);
573                 UpdateButtonStates();
574         }       
575 }
576
577 void COpenView::OnSwapButton(int id1, int id2)
578 {
579         String s1, s2;
580         GetDlgItemText(id1, s1);
581         GetDlgItemText(id2, s2);
582         std::swap(s1, s2);
583         SetDlgItemText(id1, s1);
584         SetDlgItemText(id2, s2);
585 }
586
587 template<int id1, int id2>
588 void COpenView::OnSwapButton() 
589 {
590         OnSwapButton(id1, id2);
591 }
592
593 void COpenView::OnCompare(UINT nID)
594 {
595         int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
596         const String filterPrefix = _("[F] ");
597         auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
598
599         UpdateData(TRUE);
600         TrimPaths();
601
602         int nFiles = 0;
603         for (auto& strPath : m_strPath)
604         {
605                 if (nFiles >= 1 && strPath.empty())
606                         break;
607                 m_files.SetSize(nFiles + 1);
608                 m_files[nFiles] = strPath;
609                 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
610                 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
611                 nFiles++;
612         }
613         // If left path is a project-file, load it
614         String ext;
615         paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
616         if (nFiles == 1)
617         {
618                 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
619                 {
620                         theApp.LoadAndOpenProjectFile(m_strPath[0]);
621                 }
622                 else if (!paths::IsDirectory(m_strPath[0]))
623                 {
624                         PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
625                         if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
626                                 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
627                         PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
628                         GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo, &tmpPrediffingInfo);
629                 }
630                 return;
631         }
632
633         pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
634
635         if (pathsType == paths::DOES_NOT_EXIST &&
636                 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); }))
637         {
638                 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
639                 return;
640         }
641
642         for (int index = 0; index < nFiles; index++)
643         {
644                 // If user has edited path by hand, expand environment variables
645                 bool bExpand = false;
646                 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
647                         bExpand = true;
648
649                 if (!paths::IsURLorCLSID(m_files[index]))
650                 {
651                         m_files[index] = paths::GetLongPath(m_files[index], bExpand);
652         
653                         // Add trailing '\' for directories if its missing
654                         if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR && !IsArchiveFile(m_files[index]))
655                                 m_files[index] = paths::AddTrailingSlash(m_files[index]);
656                         m_strPath[index] = m_files[index];
657                 }
658         }
659
660         UpdateData(FALSE);
661         KillTimer(IDT_CHECKFILES);
662         KillTimer(IDT_RETRY);
663
664         String filter(strutils::trim_ws(m_strExt));
665
666         // If prefix found from start..
667         if (filter.substr(0, filterPrefix.length()) == filterPrefix)
668         {
669                 // Remove prefix + space
670                 filter.erase(0, filterPrefix.length());
671                 if (!pGlobalFileFilter->SetFilter(filter))
672                 {
673                         // If filtername is not found use default *.* mask
674                         pGlobalFileFilter->SetFilter(_T("*.*"));
675                         filter = _T("*.*");
676                 }
677                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
678         }
679         else
680         {
681                 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
682                 if (!bFilterSet)
683                         m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
684                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
685         }
686
687         SaveComboboxStates();
688         GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
689         LoadComboboxStates();
690
691         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
692         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
693         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
694         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
695         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_NUMBERS, m_bIgnoreNumbers);
696         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
697         GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
698         GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
699
700         m_constraint.Persist(true, false);
701
702         COpenDoc *pDoc = GetDocument();
703         pDoc->m_files = m_files;
704         pDoc->m_bRecurse = m_bRecurse;
705         pDoc->m_strExt = m_strExt;
706         pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
707         pDoc->m_strPredifferPipeline = m_strPredifferPipeline;
708         pDoc->m_dwFlags[0] = m_dwFlags[0];
709         pDoc->m_dwFlags[1] = m_dwFlags[1];
710         pDoc->m_dwFlags[2] = m_dwFlags[2];
711
712         if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
713                 GetParentFrame()->PostMessage(WM_CLOSE);
714
715         // Copy the values in pDoc as it will be invalid when COpenFrame is closed. 
716         PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
717         PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
718         PathContext tmpPathContext(pDoc->m_files);
719         std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
720         bool recurse = pDoc->m_bRecurse;
721         std::unique_ptr<CMainFrame::OpenFolderParams> pOpenFolderParams;
722         if (!pDoc->m_hiddenItems.empty())
723         {
724                 pOpenFolderParams = std::make_unique<CMainFrame::OpenFolderParams>();
725                 pOpenFolderParams->m_hiddenItems = pDoc->m_hiddenItems;
726         }
727         if (nID == IDOK)
728         {
729                 GetMainFrame()->DoFileOrFolderOpen(
730                         &tmpPathContext, dwFlags.data(),
731                         nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
732         }
733         else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
734         {
735                 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
736                 GetMainFrame()->DoFileOrFolderOpen(
737                         &tmpPathContext, dwFlags.data(),
738                         nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
739         }
740         else if (nID == ID_OPEN_WITH_UNPACKER)
741         {
742                 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0], 
743                         CSelectPluginDlg::PluginType::Unpacker, false, this);
744                 if (dlg.DoModal() == IDOK)
745                 {
746                         tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
747                         GetMainFrame()->DoFileOrFolderOpen(
748                                 &tmpPathContext, dwFlags.data(),
749                                 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
750                 }
751         }
752         else
753         {
754                 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo, &tmpPrediffingInfo, pOpenFolderParams.get());
755         }
756 }
757
758 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
759 {
760         bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
761         if (!bFile)
762         {
763                 UpdateData(true);
764                 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
765                 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
766                                 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
767                         });
768         }
769         pCmdUI->Enable(bFile);
770 }
771
772 /** 
773  * @brief Called when dialog is closed with "OK".
774  *
775  * Checks that paths are valid and sets filters.
776  */
777 void COpenView::OnOK() 
778 {
779         OnCompare(IDOK);
780 }
781
782 /** 
783  * @brief Called when dialog is closed via Cancel.
784  *
785  * Open-dialog is closed when `Cancel` button is selected or the
786  * `Esc` key is pressed.  Save combobox states, since user may have
787  * removed items from them (with `shift-del`) and doesn't want them 
788  * to re-appear.
789  * This is *not* called when the program is terminated, even if the 
790  * dialog is visible at the time.
791  */
792 void COpenView::OnCancel()
793 {
794         SaveComboboxStates();
795         AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
796 }
797
798 /** 
799  * @brief Called when Open-button for project file is selected.
800  */
801 void COpenView::OnLoadProject()
802 {
803         UpdateData(TRUE);
804
805         String fileName = AskProjectFileName(true);
806         if (fileName.empty())
807                 return;
808
809         ProjectFile project;
810         if (!theApp.LoadProjectFile(fileName, project))
811                 return;
812         if (project.Items().size() == 0)
813                 return;
814         PathContext paths;
815         ProjectFileItem& projItem = *project.Items().begin();
816         bool bRecurse = m_bRecurse;
817         projItem.GetPaths(paths, bRecurse);
818         if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::IncludeSubfolders))
819                 m_bRecurse = bRecurse;
820         if (paths.GetSize() < 3)
821         {
822                 m_strPath[0] = paths[0];
823                 m_strPath[1] = paths[1];
824                 m_strPath[2].clear();
825                 m_bReadOnly[0] = projItem.GetLeftReadOnly();
826                 m_bReadOnly[1] = projItem.GetRightReadOnly();
827                 m_bReadOnly[2] = false;
828         }
829         else
830         {
831                 m_strPath[0] = paths[0];
832                 m_strPath[1] = paths[1];
833                 m_strPath[2] = paths[2];
834                 m_bReadOnly[0] = projItem.GetLeftReadOnly();
835                 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
836                 m_bReadOnly[2] = projItem.GetRightReadOnly();
837         }
838         if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::FileFilter) && projItem.HasFilter())
839                 m_strExt = projItem.GetFilter();
840         if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::Plugin))
841         {
842                 if (projItem.HasUnpacker())
843                         m_strUnpackerPipeline = projItem.GetUnpacker();
844                 if (projItem.HasPrediffer())
845                         m_strPredifferPipeline = projItem.GetPrediffer();
846         }
847
848         if (projItem.HasWindowType())
849                 GetDocument()->m_nWindowType = projItem.GetWindowType();
850         if (projItem.HasTableDelimiter())
851                 GetDocument()->m_cTableDelimiter = projItem.GetTableDelimiter();
852         if (projItem.HasTableQuote())
853                 GetDocument()->m_cTableQuote = projItem.GetTableQuote();
854         if (projItem.HasTableAllowNewLinesInQuotes())
855                 GetDocument()->m_bTableAllowNewLinesInQuotes = projItem.GetTableAllowNewLinesInQuotes();
856
857         if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::CompareOptions))
858         {
859                 if (projItem.HasIgnoreWhite())
860                         m_nIgnoreWhite = projItem.GetIgnoreWhite();
861                 if (projItem.HasIgnoreBlankLines())
862                         m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
863                 if (projItem.HasIgnoreCase())
864                         m_bIgnoreCase = projItem.GetIgnoreCase();
865                 if (projItem.HasIgnoreEol())
866                         m_bIgnoreEol = projItem.GetIgnoreEol();
867                 if (projItem.HasIgnoreNumbers())
868                         m_bIgnoreNumbers = projItem.GetIgnoreNumbers();
869                 if (projItem.HasIgnoreCodepage())
870                         m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
871                 if (projItem.HasFilterCommentsLines())
872                         m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
873                 if (projItem.HasCompareMethod())
874                         m_nCompareMethod = projItem.GetCompareMethod();
875         }
876
877         if ((Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::HiddenItems)) && projItem.HasHiddenItems())
878         {
879                 GetDocument()->m_hiddenItems = projItem.GetHiddenItems();
880         }
881         UpdateData(FALSE);
882         UpdateButtonStates();
883         LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
884 }
885
886 /** 
887  * @brief Called when Save-button for project file is selected.
888  */
889 void COpenView::OnSaveProject()
890 {
891         UpdateData(TRUE);
892
893         String fileName = AskProjectFileName(false);
894         if (fileName.empty())
895                 return;
896
897         ProjectFile project;
898         ProjectFileItem projItem;
899
900         bool bSaveFileFilter = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::FileFilter);
901         bool bSaveIncludeSubfolders = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::IncludeSubfolders);
902         bool bSavePlugin = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::Plugin);
903         bool bSaveCompareOptions = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::CompareOptions);
904         bool bSaveHiddenItems = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::HiddenItems);
905
906         projItem.SetSaveFilter(bSaveFileFilter);
907         projItem.SetSaveSubfolders(bSaveIncludeSubfolders);
908         projItem.SetSaveUnpacker(bSavePlugin);
909         projItem.SetSavePrediffer(bSavePlugin);
910         projItem.SetSaveIgnoreWhite(bSaveCompareOptions);
911         projItem.SetSaveIgnoreBlankLines(bSaveCompareOptions);
912         projItem.SetSaveIgnoreCase(bSaveCompareOptions);
913         projItem.SetSaveIgnoreEol(bSaveCompareOptions);
914         projItem.SetSaveIgnoreNumbers(bSaveCompareOptions);
915         projItem.SetSaveIgnoreCodepage(bSaveCompareOptions);
916         projItem.SetSaveFilterCommentsLines(bSaveCompareOptions);
917         projItem.SetSaveCompareMethod(bSaveCompareOptions);
918         projItem.SetSaveHiddenItems(bSaveHiddenItems);
919
920         if (!m_strPath[0].empty())
921                 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
922         if (!GetDocument()->m_strDesc[0].empty())
923                 projItem.SetLeftDesc(GetDocument()->m_strDesc[0]);
924         if (m_strPath[2].empty())
925         {
926                 if (!m_strPath[1].empty())
927                         projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
928                 if (!GetDocument()->m_strDesc[1].empty())
929                         projItem.SetRightDesc(GetDocument()->m_strDesc[1]);
930         }
931         else
932         {
933                 if (!m_strPath[1].empty())
934                         projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
935                 if (!m_strPath[2].empty())
936                         projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
937                 if (!GetDocument()->m_strDesc[1].empty())
938                         projItem.SetMiddleDesc(GetDocument()->m_strDesc[1]);
939                 if (!GetDocument()->m_strDesc[2].empty())
940                         projItem.SetRightDesc(GetDocument()->m_strDesc[2]);
941         }
942         if (bSaveFileFilter && !m_strExt.empty())
943         {
944                 // Remove possbile prefix from the filter name
945                 String prefix = _("[F] ");
946                 String strExt = m_strExt;
947                 size_t ind = strExt.find(prefix, 0);
948                 if (ind == 0)
949                 {
950                         strExt.erase(0, prefix.length());
951                 }
952                 strExt = strutils::trim_ws_begin(strExt);
953                 projItem.SetFilter(strExt);
954         }
955         if (bSaveIncludeSubfolders)
956                 projItem.SetSubfolders(m_bRecurse);
957         if (bSavePlugin)
958         {
959                 if (!m_strUnpackerPipeline.empty())
960                         projItem.SetUnpacker(m_strUnpackerPipeline);
961                 if (!m_strPredifferPipeline.empty())
962                         projItem.SetPrediffer(m_strPredifferPipeline);
963         }
964         if (GetDocument()->m_nWindowType != -1)
965                 projItem.SetWindowType(GetDocument()->m_nWindowType);
966         if (GetDocument()->m_nWindowType == 2 /* table */)
967         {
968                 projItem.SetTableDelimiter(GetDocument()->m_cTableDelimiter);
969                 projItem.SetTableQuote(GetDocument()->m_cTableQuote);
970                 projItem.SetTableAllowNewLinesInQuotes(GetDocument()->m_bTableAllowNewLinesInQuotes);
971         }
972
973         if (bSaveCompareOptions)
974         {
975                 projItem.SetIgnoreWhite(m_nIgnoreWhite);
976                 projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
977                 projItem.SetIgnoreCase(m_bIgnoreCase);
978                 projItem.SetIgnoreEol(m_bIgnoreEol);
979                 projItem.SetIgnoreNumbers(m_bIgnoreNumbers);
980                 projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
981                 projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
982                 projItem.SetCompareMethod(m_nCompareMethod);
983         }
984
985         if (bSaveHiddenItems)
986                 projItem.SetHiddenItems(GetDocument()->m_hiddenItems);
987
988         project.Items().push_back(projItem);
989
990         if (!theApp.SaveProjectFile(fileName, project))
991                 return;
992
993         LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
994 }
995
996 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
997 {
998         CRect rcButton;
999         GetDlgItem(nID)->GetWindowRect(&rcButton);
1000         BCMenu menu;
1001         VERIFY(menu.LoadMenu(nPopupID));
1002         theApp.TranslateMenu(menu.m_hMenu);
1003         CMenu* pPopup = menu.GetSubMenu(0);
1004         if (pPopup != nullptr)
1005         {
1006                 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
1007                 {
1008                         UpdateData(TRUE);
1009                         String tmpPath[3];
1010                         for (int i = 0; i < 3; i++)
1011                                 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
1012                         String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
1013                         CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
1014                 }
1015                 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
1016                         rcButton.left, rcButton.bottom, GetMainFrame());
1017         }
1018         *pResult = 0;
1019 }
1020
1021 template<UINT id, UINT popupid>
1022 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
1023 {
1024         DropDown(pNMHDR, pResult, id, popupid);
1025 }
1026
1027 /** 
1028  * @brief Allow user to select a file to open/save.
1029  */
1030 String COpenView::AskProjectFileName(bool bOpen)
1031 {
1032         // get the default projects path
1033         String strProjectFileName;
1034         String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
1035
1036         if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
1037                         _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
1038                 return _T("");
1039
1040         if (strProjectFileName.empty())
1041                 return _T("");
1042
1043         // get the path part from the filename
1044         strProjectPath = paths::GetParentPath(strProjectFileName);
1045         // store this as the new project path
1046         GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
1047         return strProjectFileName;
1048 }
1049
1050 /** 
1051  * @brief Load File- and filter-combobox states.
1052  */
1053 void COpenView::LoadComboboxStates()
1054 {
1055         m_ctlPath[0].LoadState(_T("Files\\Left"));
1056         m_ctlPath[1].LoadState(_T("Files\\Right"));
1057         m_ctlPath[2].LoadState(_T("Files\\Option"));
1058         m_ctlExt.LoadState(_T("Files\\Ext"));
1059         m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
1060         m_ctlPredifferPipeline.LoadState(_T("Files\\Prediffer"));
1061 }
1062
1063 /** 
1064  * @brief Save File- and filter-combobox states.
1065  */
1066 void COpenView::SaveComboboxStates()
1067 {
1068         m_ctlPath[0].SaveState(_T("Files\\Left"));
1069         m_ctlPath[1].SaveState(_T("Files\\Right"));
1070         m_ctlPath[2].SaveState(_T("Files\\Option"));
1071         m_ctlExt.SaveState(_T("Files\\Ext"));
1072         m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
1073         m_ctlPredifferPipeline.SaveState(_T("Files\\Prediffer"));
1074 }
1075
1076 struct UpdateButtonStatesThreadParams
1077 {
1078         HWND m_hWnd;
1079         PathContext m_paths;
1080 };
1081
1082 static UINT UpdateButtonStatesThread(LPVOID lpParam)
1083 {
1084         MSG msg;
1085         BOOL bRet;
1086
1087         CoInitialize(nullptr);
1088         CAssureScriptsForThread scriptsForRescan;
1089
1090         while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
1091         { 
1092                 if (bRet == -1)
1093                         break;
1094                 if (msg.message != WM_USER + 2)
1095                         continue;
1096
1097                 bool bIsaFolderCompare = true;
1098                 bool bIsaFileCompare = true;
1099                 bool bInvalid[3] = {false, false, false};
1100                 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
1101                 int iStatusMsgId = IDS_OPEN_FILESDIRS;
1102
1103                 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
1104                 PathContext paths = pParams->m_paths;
1105                 HWND hWnd = pParams->m_hWnd;
1106                 delete pParams;
1107
1108                 // Check if we have project file as left side path
1109                 bool bProject = false;
1110                 String ext;
1111                 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
1112                 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1113                         bProject = true;
1114
1115                 if (!bProject)
1116                 {
1117                         for (int i = 0; i < paths.GetSize(); ++i)
1118                         {
1119                                 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
1120                                 if (pathType[i] == paths::DOES_NOT_EXIST)
1121                                 {
1122                                         if (paths::IsURL(paths[i]))
1123                                                 pathType[i] = paths::IS_EXISTING_FILE;
1124                                         else
1125                                                 bInvalid[i] = true;
1126                                 }
1127                         }
1128                 }
1129
1130                 // Enable buttons as appropriate
1131                 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1132                 {
1133                         paths::PATH_EXISTENCE pathsType = pathType[0];
1134
1135                         if (paths.GetSize() <= 2)
1136                         {
1137                                 if (bInvalid[0] && bInvalid[1])
1138                                         iStatusMsgId = IDS_OPEN_BOTHINVALID;
1139                                 else if (bInvalid[0])
1140                                         iStatusMsgId = IDS_OPEN_LEFTINVALID;
1141                                 else if (bInvalid[1])
1142                                 {
1143                                         if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
1144                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
1145                                         else
1146                                                 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
1147                                 }
1148                                 else if (!bInvalid[0] && !bInvalid[1])
1149                                 {
1150                                         if (pathType[0] != pathType[1])
1151                                                 iStatusMsgId = IDS_OPEN_MISMATCH;
1152                                         else
1153                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
1154                                 }
1155                         }
1156                         else
1157                         {
1158                                 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
1159                                         iStatusMsgId = IDS_OPEN_ALLINVALID;
1160                                 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
1161                                         iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
1162                                 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
1163                                         iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
1164                                 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
1165                                         iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
1166                                 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
1167                                         iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
1168                                 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
1169                                         iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
1170                                 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1171                                         iStatusMsgId = IDS_OPEN_LEFTINVALID;
1172                                 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1173                                 {
1174                                         if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
1175                                                 iStatusMsgId = IDS_OPEN_MISMATCH;
1176                                         else
1177                                                 iStatusMsgId = IDS_OPEN_FILESDIRS;
1178                                 }
1179                         }
1180                         if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1181                                 pathsType = paths::DOES_NOT_EXIST;
1182                         bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1183                         bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1184                         // Both will be `false` if incompatibilities or something is missing
1185                         // Both will end up `true` if file validity isn't being checked
1186                 }
1187
1188                 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject)); 
1189         }
1190
1191         CoUninitialize();
1192
1193         return 0;
1194 }
1195
1196 /**
1197  * @brief Update any resources necessary after a GUI language change
1198  */
1199 void COpenView::UpdateResources()
1200 {
1201         theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1202 }
1203
1204 /** 
1205  * @brief Enable/disable components based on validity of paths.
1206  */
1207 void COpenView::UpdateButtonStates()
1208 {
1209         UpdateData(TRUE); // load member variables from screen
1210         KillTimer(IDT_CHECKFILES);
1211         TrimPaths();
1212         
1213         if (m_pUpdateButtonStatusThread == nullptr)
1214         {
1215                 m_pUpdateButtonStatusThread = AfxBeginThread(
1216                         UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1217                 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1218                 m_pUpdateButtonStatusThread->ResumeThread();
1219                 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1220                         Sleep(1);
1221         }
1222
1223         UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1224         pParams->m_hWnd = this->m_hWnd;
1225         pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1226
1227         PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1228 }
1229
1230 void COpenView::TerminateThreadIfRunning()
1231 {
1232         if (m_pUpdateButtonStatusThread == nullptr)
1233                 return;
1234
1235         PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1236         DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1237         if (dwResult != WAIT_OBJECT_0)
1238         {
1239                 m_pUpdateButtonStatusThread->SuspendThread();
1240                 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1241         }
1242         delete m_pUpdateButtonStatusThread;
1243         m_pUpdateButtonStatusThread = nullptr;
1244 }
1245
1246 /**
1247  * @brief Called when user changes selection in left/middle/right path's combo box.
1248  */
1249 void COpenView::OnSelchangePathCombo(UINT nId) 
1250 {
1251         const int index = nId - IDC_PATH0_COMBO;
1252         int sel = m_ctlPath[index].GetCurSel();
1253         if (sel != CB_ERR)
1254         {
1255                 CString cstrPath;
1256                 m_ctlPath[index].GetLBText(sel, cstrPath);
1257                 m_strPath[index] = cstrPath;
1258                 m_ctlPath[index].SetWindowText(cstrPath);
1259                 UpdateData(TRUE);
1260         }
1261         UpdateButtonStates();
1262 }
1263
1264 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult) 
1265 {
1266         if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1267         {
1268                 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1269                 if (nSource > 0)
1270                         m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1271                 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1272         }
1273         *pResult = 0;
1274 }
1275
1276 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1277 {
1278         m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1279         SetCapture();
1280         *pResult = 0;
1281 }
1282
1283 /**
1284  * @brief Called every time paths are edited.
1285  */
1286 void COpenView::OnEditEvent(UINT nID)
1287 {
1288         const int N = nID - IDC_PATH0_COMBO;
1289         if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1290         {
1291                 int const len = edit->GetWindowTextLength();
1292                 if (edit->GetSel() == MAKEWPARAM(len, len))
1293                 {
1294                         CString text;
1295                         edit->GetWindowText(text);
1296                         // Remove any double quotes
1297                         text.Remove('"');
1298                         if (text.GetLength() != len)
1299                         {
1300                                 edit->SetSel(0, len);
1301                                 edit->ReplaceSel(text);
1302                         }
1303                 }
1304         }
1305         // (Re)start timer to path validity check delay
1306         // If timer starting fails, update buttonstates immediately
1307         if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1308                 UpdateButtonStates();
1309 }
1310
1311 /**
1312  * @brief Handle timer events.
1313  * Checks if paths are valid and sets control states accordingly.
1314  * @param [in] nIDEvent Timer ID that fired.
1315  */
1316 void COpenView::OnTimer(UINT_PTR nIDEvent)
1317 {
1318         if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1319                 UpdateButtonStates();
1320
1321         __super::OnTimer(nIDEvent);
1322 }
1323
1324 /**
1325  * @brief Called when users selects plugin browse button.
1326  */
1327 void COpenView::OnSelectPlugin(UINT nID)
1328 {
1329         paths::PATH_EXISTENCE pathsType;
1330         UpdateData(TRUE);
1331
1332         int nFiles = 0;
1333         for (auto& strPath: m_strPath)
1334         {
1335                 if (nFiles == 2 && strPath.empty())
1336                         break;
1337                 m_files.SetSize(nFiles + 1);
1338                 m_files[nFiles] = strPath;
1339                 nFiles++;
1340         }
1341         PathContext tmpFiles = m_files;
1342         if (tmpFiles.GetSize() == 2 && tmpFiles[1].empty())
1343                 tmpFiles[1] = tmpFiles[0];
1344         pathsType = paths::GetPairComparability(tmpFiles, IsArchiveFile);
1345
1346         if (pathsType == paths::IS_EXISTING_DIR || (pathsType == paths::DOES_NOT_EXIST &&
1347                 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); })))
1348                 return;
1349
1350         // let the user select a handler
1351         CSelectPluginDlg dlg(nID == IDC_SELECT_UNPACKER ? m_strUnpackerPipeline : m_strPredifferPipeline, m_files[0], 
1352                 nID == IDC_SELECT_UNPACKER ? CSelectPluginDlg::PluginType::Unpacker : CSelectPluginDlg::PluginType::Prediffer, false, this);
1353         if (dlg.DoModal() == IDOK)
1354         {
1355                 if (nID == IDC_SELECT_UNPACKER)
1356                         m_strUnpackerPipeline = dlg.GetPluginPipeline();
1357                 else
1358                         m_strPredifferPipeline = dlg.GetPluginPipeline();
1359                 UpdateData(FALSE);
1360         }
1361 }
1362
1363 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1364 {
1365         const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1366         const bool bIsaFileCompare = HIWORD(wParam) != 0;
1367         const bool bProject = HIWORD(lParam) != 0;
1368         const int iStatusMsgId = LOWORD(lParam);
1369
1370         EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1371
1372         for (auto nID : { IDC_FILES_DIRS_GROUP4, IDC_UNPACKER_COMBO, IDC_SELECT_UNPACKER })
1373         {
1374                 EnableDlgItem(nID, bIsaFileCompare);
1375         }
1376
1377         if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1378         {
1379                 for (auto nID : { IDC_FILES_DIRS_GROUP5, IDC_PREDIFFER_COMBO, IDC_SELECT_PREDIFFER })
1380                 {
1381                         GetDlgItem(nID)->ShowWindow(bIsaFileCompare ? SW_SHOW : SW_HIDE);
1382                         EnableDlgItem(nID, bIsaFileCompare);
1383                 }
1384
1385                 for (auto nID : { IDC_FILES_DIRS_GROUP3, IDC_EXT_COMBO, IDC_SELECT_FILTER, IDC_RECURS_CHECK })
1386                 {
1387                         GetDlgItem(nID)->ShowWindow((bIsaFolderCompare || !bIsaFileCompare) ? SW_SHOW : SW_HIDE);
1388                         EnableDlgItem(nID, bIsaFolderCompare);
1389                 }
1390         }
1391         
1392         SetStatus(iStatusMsgId);
1393
1394         if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1395         {
1396                 if (m_retryCount == 0)
1397                         SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1398                 m_retryCount++;
1399         }
1400         else
1401         {
1402                 KillTimer(IDT_RETRY);
1403                 m_retryCount = 0;
1404         }
1405         return 0;
1406 }
1407
1408 /**
1409  * @brief Sets the path status text.
1410  * The open dialog shows a status text of selected paths. This function
1411  * is used to set that status text.
1412  * @param [in] msgID Resource ID of status text to set.
1413  */
1414 void COpenView::SetStatus(UINT msgID)
1415 {
1416         String msg = theApp.LoadString(msgID);
1417         SetDlgItemText(IDC_OPEN_STATUS, msg);
1418 }
1419
1420 /** 
1421  * @brief Called when "Select..." button for filters is selected.
1422  */
1423 void COpenView::OnSelectFilter()
1424 {
1425         String curFilter;
1426         auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1427
1428         const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1429         GetDlgItemText(IDC_EXT_COMBO, curFilter);
1430         curFilter = strutils::trim_ws(curFilter);
1431
1432         GetMainFrame()->SelectFilter();
1433         
1434         String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1435         if (pGlobalFileFilter->IsUsingMask())
1436         {
1437                 // If we had filter chosen and now has mask we can overwrite filter
1438                 if (!bUseMask || curFilter[0] != '*')
1439                 {
1440                         SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1441                 }
1442         }
1443         else
1444         {
1445                 String filterPrefix = _("[F] ");
1446                 filterNameOrMask = filterPrefix + filterNameOrMask;
1447                 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1448         }
1449 }
1450
1451 void COpenView::OnOptions()
1452 {
1453         GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1454 }
1455
1456 /**
1457  * @brief Set "Whitespaces" setting.
1458  * @param [in] nID Menu ID of the selected item
1459  */
1460 void COpenView::OnDiffWhitespace(UINT nID)
1461 {
1462         assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
1463
1464         m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
1465 }
1466
1467 /**
1468  * @brief Update "Whitespaces" state.
1469  * @param [in] pCmdUI UI component to update.
1470  */
1471 void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
1472 {
1473         pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
1474 }
1475
1476 /**
1477  * @brief Toggle "Ignore blank lines" setting.
1478  */
1479 void COpenView::OnDiffIgnoreBlankLines()
1480 {
1481         m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1482 }
1483
1484 /**
1485  * @brief Update "Ignore blank lines" state.
1486  * @param [in] pCmdUI UI component to update.
1487  */
1488 void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
1489 {
1490         pCmdUI->SetCheck(m_bIgnoreBlankLines);
1491 }
1492
1493 /**
1494  * @brief Toggle "Ignore case" setting.
1495  */
1496 void COpenView::OnDiffIgnoreCase()
1497 {
1498         m_bIgnoreCase = !m_bIgnoreCase;
1499 }
1500
1501 /**
1502  * @brief Update "Ignore case" state.
1503  * @param [in] pCmdUI UI component to update.
1504  */
1505 void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
1506 {
1507         pCmdUI->SetCheck(m_bIgnoreCase);
1508 }
1509
1510 /**
1511  * @brief Toggle "Ignore carriage return differences" setting.
1512  */
1513 void COpenView::OnDiffIgnoreEOL()
1514 {
1515         m_bIgnoreEol = !m_bIgnoreEol;
1516 }
1517
1518 /**
1519  * @brief Update "Ignore carriage return differences" state.
1520  * @param [in] pCmdUI UI component to update.
1521  */
1522 void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
1523 {
1524         pCmdUI->SetCheck(m_bIgnoreEol);
1525 }
1526
1527 /**
1528  * @brief Toggle "Ignore numbers" setting.
1529  */
1530 void COpenView::OnDiffIgnoreNumbers()
1531 {
1532         m_bIgnoreNumbers = !m_bIgnoreNumbers;
1533 }
1534
1535 /**
1536  * @brief Update "Ignore numbers" state.
1537  * @param [in] pCmdUI UI component to update.
1538  */
1539 void COpenView::OnUpdateDiffIgnoreNumbers(CCmdUI* pCmdUI)
1540 {
1541         pCmdUI->SetCheck(m_bIgnoreNumbers);
1542 }
1543
1544 /**
1545  * @brief Toggle "Ignore codepage differences" setting.
1546  */
1547 void COpenView::OnDiffIgnoreCP()
1548 {
1549         m_bIgnoreCodepage = !m_bIgnoreCodepage;
1550 }
1551
1552 /**
1553  * @brief Update "Ignore codepage differences" state.
1554  * @param [in] pCmdUI UI component to update.
1555  */
1556 void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
1557 {
1558         pCmdUI->SetCheck(m_bIgnoreCodepage);
1559 }
1560
1561 /**
1562  * @brief Toggle "Ignore comment differences" setting.
1563  */
1564 void COpenView::OnDiffIgnoreComments()
1565 {
1566         m_bFilterCommentsLines = !m_bFilterCommentsLines;
1567 }
1568
1569 /**
1570  * @brief Update "Ignore comment differences" state.
1571  * @param [in] pCmdUI UI component to update.
1572  */
1573 void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
1574 {
1575         pCmdUI->SetCheck(m_bFilterCommentsLines);
1576 }
1577
1578 /**
1579  * @brief Set "Compare method" setting.
1580  * @param [in] nID Menu ID of the selected item
1581  */
1582 void COpenView::OnCompareMethod(UINT nID)
1583 {
1584         assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
1585
1586         m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
1587 }
1588
1589 /**
1590  * @brief Update "Compare method" state.
1591  * @param [in] pCmdUI UI component to update.
1592  */
1593 void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
1594 {
1595         pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
1596 }
1597
1598 /** 
1599  * @brief Removes whitespaces from left and right paths
1600  * @note Assumes UpdateData(TRUE) is called before this function.
1601  */
1602 void COpenView::TrimPaths()
1603 {
1604         for (auto& strPath: m_strPath)
1605                 strPath = strutils::trim_ws(strPath);
1606 }
1607
1608 /** 
1609  * @brief Update control states when dialog is activated.
1610  *
1611  * Update control states when user re-activates dialog. User might have
1612  * switched for other program to e.g. update files/folders and then
1613  * swiches back to WinMerge. Its nice to see WinMerge detects updated
1614  * files/folders.
1615  */
1616 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1617 {
1618         __super::OnActivate(nState, pWndOther, bMinimized);
1619
1620         if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1621                 UpdateButtonStates();
1622 }
1623
1624 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1625 {
1626         CWnd *pCtl = GetFocus();
1627         if (pCtl != nullptr)
1628                 pCtl->PostMessage(msg, wParam, lParam);
1629 }
1630
1631 template <int MSG, int WPARAM, int LPARAM>
1632 void COpenView::OnEditAction()
1633 {
1634         OnEditAction(MSG, WPARAM, LPARAM);
1635 }
1636
1637 /**
1638  * @brief Open help from mainframe when user presses F1.
1639  */
1640 void COpenView::OnHelp()
1641 {
1642         theApp.ShowHelp(OpenDlgHelpLocation);
1643 }
1644
1645 /////////////////////////////////////////////////////////////////////////////
1646 //
1647 //      OnDropFiles code from CDropEdit
1648 //      Copyright 1997 Chris Losinger
1649 //
1650 //      shortcut expansion code modified from :
1651 //      CShortcut, 1996 Rob Warner
1652 //
1653
1654 /**
1655  * @brief Drop paths(s) to the dialog.
1656  * One or two paths can be dropped to the dialog. The behaviour is:
1657  *   If 1 file:
1658  *     - drop to empty path edit box (check left first)
1659  *     - if both boxes have a path, drop to left path
1660  *   If two files:
1661  *    - overwrite both paths, empty or not
1662  * @param [in] dropInfo Dropped data, including paths.
1663  */
1664 void COpenView::OnDropFiles(const std::vector<String>& files)
1665 {
1666         const size_t fileCount = files.size();
1667
1668         // Add dropped paths to the dialog
1669         UpdateData(TRUE);
1670         if (fileCount == 3)
1671         {
1672                 m_strPath[0] = files[0];
1673                 m_strPath[1] = files[1];
1674                 m_strPath[2] = files[2];
1675                 UpdateData(FALSE);
1676                 UpdateButtonStates();
1677         }
1678         else if (fileCount == 2)
1679         {
1680                 m_strPath[0] = files[0];
1681                 m_strPath[1] = files[1];
1682                 UpdateData(FALSE);
1683                 UpdateButtonStates();
1684         }
1685         else if (fileCount == 1)
1686         {
1687                 CPoint point;
1688                 GetCursorPos(&point);
1689                 ScreenToClient(&point);
1690                 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1691                         CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1692                 {
1693                         switch (int const id = pwndHit->GetDlgCtrlID())
1694                         {
1695                         case IDC_PATH0_COMBO:
1696                         case IDC_PATH1_COMBO:
1697                         case IDC_PATH2_COMBO:
1698                                 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1699                                 break;
1700                         default:
1701                                 if (m_strPath[0].empty())
1702                                         m_strPath[0] = files[0];
1703                                 else if (m_strPath[1].empty())
1704                                         m_strPath[1] = files[0];
1705                                 else if (m_strPath[2].empty())
1706                                         m_strPath[2] = files[0];
1707                                 else
1708                                         m_strPath[0] = files[0];
1709                                 break;
1710                         }
1711                 }
1712                 UpdateData(FALSE);
1713                 UpdateButtonStates();
1714         }
1715 }