OSDN Git Service

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