1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
10 * @brief Implementation of the COpenView class
17 #include "UnicodeString.h"
20 #include "ProjectFile.h"
22 #include "SelectUnpackerDlg.h"
23 #include "OptionsDef.h"
25 #include "OptionsMgr.h"
26 #include "FileOrFolderSelect.h"
28 #include "Constants.h"
30 #include "DropHandler.h"
31 #include "FileFilterHelper.h"
34 #include "LanguageSelect.h"
35 #include "Win_VersionHelper.h"
42 #define BCN_DROPDOWN (BCN_FIRST + 0x0002)
45 // Timer ID and timeout for delaying path validity check
46 const UINT IDT_CHECKFILES = 1;
47 const UINT IDT_RETRY = 2;
48 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
49 const int RETRY_MAX = 3;
50 static const TCHAR EMPTY_EXTENSION[] = _T(".*");
52 /** @brief Location for Open-dialog specific help to open. */
53 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
57 IMPLEMENT_DYNCREATE(COpenView, CFormView)
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_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
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)
74 ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
75 ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
76 ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, OnDropDownOptions)
78 ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
79 ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
80 ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
81 ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
82 ON_COMMAND(IDOK, OnOK)
83 ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
84 ON_COMMAND(IDCANCEL, OnCancel)
85 ON_COMMAND(ID_HELP, OnHelp)
86 ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
87 ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
88 ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
89 ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
90 ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
91 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, OnCompare)
92 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, OnUpdateCompare)
93 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
97 ON_WM_WINDOWPOSCHANGING()
98 ON_WM_WINDOWPOSCHANGED()
104 // COpenView construction/destruction
106 COpenView::COpenView()
107 : CFormView(COpenView::IDD)
108 , m_pUpdateButtonStatusThread(nullptr)
110 , m_pDropHandler(nullptr)
112 , m_bAutoCompleteReady()
113 , m_bReadOnly {false, false, false}
114 , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
115 , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
118 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
119 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
120 m_bInsideUpdate = TRUE;
123 COpenView::~COpenView()
125 TerminateThreadIfRunning();
128 void COpenView::DoDataExchange(CDataExchange* pDX)
130 CFormView::DoDataExchange(pDX);
131 //{{AFX_DATA_MAP(COpenView)
132 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
133 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
134 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
135 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
136 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
137 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
138 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
139 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
140 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
141 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
142 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
143 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
144 DDX_Text(pDX, IDC_UNPACKER_EDIT, m_strUnpacker);
148 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
150 // TODO: Modify the Window class or styles here by modifying
151 // the CREATESTRUCT cs
152 cs.style &= ~WS_BORDER;
153 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
154 return CFormView::PreCreateWindow(cs);
157 void COpenView::OnInitialUpdate()
159 if (!IsVista_OrGreater())
162 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
163 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
164 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
167 m_sizeOrig = GetTotalSize();
169 theApp.TranslateDialog(m_hWnd);
171 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
173 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
174 m_image.Create(1, 1, 24, 0);
177 CFormView::OnInitialUpdate();
179 // set caption to "swap paths" button
181 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
182 lf.lfCharSet = SYMBOL_CHARSET;
183 lstrcpy(lf.lfFaceName, _T("Wingdings"));
184 m_fontSwapButton.CreateFontIndirect(&lf);
185 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
186 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
188 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
189 SetDlgItemText(ids[i], _T("\xf4"));
192 m_constraint.InitializeCurrentSize(this);
193 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
194 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
195 m_constraint.SetScrollScale(this, 1.0, 1.0);
196 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
197 m_constraint.DisallowHeightGrowth();
198 //m_constraint.SubclassWnd(); // install subclassing
200 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
201 m_constraint.UpdateSizes();
203 COpenDoc *pDoc = GetDocument();
206 GetWindowText(strTitle);
207 pDoc->SetTitle(strTitle);
209 m_files = pDoc->m_files;
210 m_bRecurse = pDoc->m_bRecurse;
211 m_strExt = pDoc->m_strExt;
212 m_strUnpacker = pDoc->m_strUnpacker;
213 m_infoHandler = pDoc->m_infoHandler;
214 m_dwFlags[0] = pDoc->m_dwFlags[0];
215 m_dwFlags[1] = pDoc->m_dwFlags[1];
216 m_dwFlags[2] = pDoc->m_dwFlags[2];
218 m_ctlPath[0].SetFileControlStates();
219 m_ctlPath[1].SetFileControlStates(true);
220 m_ctlPath[2].SetFileControlStates(true);
222 for (int file = 0; file < m_files.GetSize(); file++)
224 m_strPath[file] = m_files[file];
225 m_ctlPath[file].SetWindowText(m_files[file].c_str());
226 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
229 m_ctlPath[0].AttachSystemImageList();
230 m_ctlPath[1].AttachSystemImageList();
231 m_ctlPath[2].AttachSystemImageList();
232 LoadComboboxStates();
234 bool bDoUpdateData = true;
235 for (auto& strPath: m_strPath)
237 if (!strPath.empty())
238 bDoUpdateData = false;
240 UpdateData(bDoUpdateData);
242 String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
243 bool bMask = theApp.m_pGlobalFileFilter->IsUsingMask();
247 String filterPrefix = _("[F] ");
248 filterNameOrMask = filterPrefix + filterNameOrMask;
251 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
253 m_ctlExt.SetCurSel(ind);
256 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
258 m_ctlExt.SetCurSel(ind);
260 LogErrorString(_T("Failed to add string to filters combo list!"));
263 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
265 EnableDlgItem(IDOK, true);
266 EnableDlgItem(IDC_UNPACKER_EDIT, true);
267 EnableDlgItem(IDC_SELECT_UNPACKER, true);
270 UpdateButtonStates();
272 bool bOverwriteRecursive = false;
273 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
274 bOverwriteRecursive = true;
275 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
276 bOverwriteRecursive = true;
277 if (!bOverwriteRecursive)
278 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
280 m_strUnpacker = m_infoHandler.m_PluginName;
282 SetStatus(IDS_OPEN_FILESDIRS);
283 SetUnpackerStatus(IDS_USERCHOICE_NONE);
285 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
286 RegisterDragDrop(m_hWnd, m_pDropHandler);
289 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
291 m_bRecurse = GetDocument()->m_bRecurse;
295 // COpenView diagnostics
298 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
300 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
301 return (COpenDoc*)m_pDocument;
305 /////////////////////////////////////////////////////////////////////////////
306 // COpenView message handlers
308 void COpenView::OnPaint()
314 // Draw the logo image
315 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
316 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
317 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
318 // And extend it to the Right boundary
319 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
321 // Draw the resize gripper in the Lower Right corner.
323 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
324 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
325 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
327 // Draw a line to separate the Status Line
328 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
329 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
332 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
333 ScreenToClient(&rcStatus);
334 dc.MoveTo(0, rcStatus.top - 3);
335 dc.LineTo(rc.right, rcStatus.top - 3);
336 dc.SelectObject(oldpen);
338 CFormView::OnPaint();
341 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
343 if (::GetCapture() == m_hWnd)
345 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
346 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
348 switch (int const id1 = pwndHit->GetDlgCtrlID())
350 case IDC_PATH0_COMBO:
351 case IDC_PATH1_COMBO:
352 case IDC_PATH2_COMBO:
354 CWnd *pwndChild = GetFocus();
355 if (IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
357 id2 = pwndChild->GetDlgCtrlID();
358 pwndChild = pwndChild->GetParent();
359 } while (pwndChild != this);
362 case IDC_PATH0_COMBO:
363 case IDC_PATH1_COMBO:
364 case IDC_PATH2_COMBO:
366 GetDlgItemText(id1, s1);
367 GetDlgItemText(id2, s2);
368 SetDlgItemText(id1, s2);
369 SetDlgItemText(id2, s1);
380 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
382 if (::GetCapture() == m_hWnd)
384 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
385 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
387 switch (pwndHit->GetDlgCtrlID())
389 case IDC_PATH0_COMBO:
390 case IDC_PATH1_COMBO:
391 case IDC_PATH2_COMBO:
392 if (!pwndHit->IsChild(GetFocus()))
394 SetCursor(m_hIconRotate);
399 SetCursor(m_hCursorNo);
406 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
408 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
410 CFrameWnd *const pFrameWnd = GetParentFrame();
411 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
414 pFrameWnd->GetClientRect(&rc);
415 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
416 lpwndpos->cy = m_sizeOrig.cy;
417 if (lpwndpos->flags & SWP_NOOWNERZORDER)
419 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
420 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
421 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
425 else if (pFrameWnd->IsZoomed())
427 lpwndpos->cx = m_totalLog.cx;
428 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
432 if (lpwndpos->cx > rc.Width())
433 lpwndpos->cx = rc.Width();
434 if (lpwndpos->cx < m_sizeOrig.cx)
435 lpwndpos->cx = m_sizeOrig.cx;
436 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
443 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
445 if (lpwndpos->flags & SWP_FRAMECHANGED)
447 m_constraint.UpdateSizes();
448 CFrameWnd *const pFrameWnd = GetParentFrame();
449 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
451 m_constraint.Persist(true, false);
452 WINDOWPLACEMENT wp = {};
453 wp.length = sizeof wp;
454 pFrameWnd->GetWindowPlacement(&wp);
457 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
458 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
459 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
460 pFrameWnd->SetWindowPlacement(&wp);
463 CFormView::OnWindowPosChanged(lpwndpos);
466 void COpenView::OnDestroy()
468 if (m_pDropHandler != nullptr)
469 RevokeDragDrop(m_hWnd);
471 CFormView::OnDestroy();
474 LRESULT COpenView::OnNcHitTest(CPoint point)
476 if (GetParentFrame()->IsZoomed())
480 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
481 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
482 if (PtInRect(&rc, point))
485 return CFormView::OnNcHitTest(point);
489 * @brief Called when "Browse..." button is selected for N path.
491 void COpenView::OnPathButton(UINT nId)
493 const int index = nId - IDC_PATH0_BUTTON;
498 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
501 case paths::IS_EXISTING_DIR:
502 sfolder = m_strPath[index];
504 case paths::IS_EXISTING_FILE:
505 sfolder = paths::GetPathOnly(m_strPath[index]);
507 case paths::DOES_NOT_EXIST:
508 if (!m_strPath[index].empty())
509 sfolder = paths::GetParentPath(m_strPath[index]);
512 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
516 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
518 m_strPath[index] = s;
519 m_strBrowsePath[index] = s;
521 UpdateButtonStates();
525 void COpenView::OnSwapButton(int id1, int id2)
528 GetDlgItemText(id1, s1);
529 GetDlgItemText(id2, s2);
531 SetDlgItemText(id1, s1);
532 SetDlgItemText(id2, s2);
535 template<int id1, int id2>
536 void COpenView::OnSwapButton()
538 OnSwapButton(id1, id2);
541 void COpenView::OnCompare(UINT nID)
543 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
544 const String filterPrefix = _("[F] ");
550 for (auto& strPath : m_strPath)
552 if (nFiles >= 1 && strPath.empty())
554 m_files.SetSize(nFiles + 1);
555 m_files[nFiles] = strPath;
556 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
557 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
560 // If left path is a project-file, load it
562 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
565 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
566 LoadProjectFile(m_strPath[0]);
568 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr);
572 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
574 if (pathsType == paths::DOES_NOT_EXIST)
576 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
580 for (int index = 0; index < nFiles; index++)
582 // If user has edited path by hand, expand environment variables
583 bool bExpand = false;
584 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
587 if (!paths::IsURLorCLSID(m_files[index]))
589 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
591 // Add trailing '\' for directories if its missing
592 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
593 m_files[index] = paths::AddTrailingSlash(m_files[index]);
594 m_strPath[index] = m_files[index];
599 KillTimer(IDT_CHECKFILES);
600 KillTimer(IDT_RETRY);
602 String filter(strutils::trim_ws(m_strExt));
604 // If prefix found from start..
605 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
607 // Remove prefix + space
608 filter.erase(0, filterPrefix.length());
609 if (!theApp.m_pGlobalFileFilter->SetFilter(filter))
611 // If filtername is not found use default *.* mask
612 theApp.m_pGlobalFileFilter->SetFilter(_T("*.*"));
615 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
619 bool bFilterSet = theApp.m_pGlobalFileFilter->SetFilter(filter);
621 m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
622 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
625 SaveComboboxStates();
626 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
627 LoadComboboxStates();
629 m_constraint.Persist(true, false);
631 COpenDoc *pDoc = GetDocument();
632 pDoc->m_files = m_files;
633 pDoc->m_bRecurse = m_bRecurse;
634 pDoc->m_strExt = m_strExt;
635 pDoc->m_strUnpacker = m_strUnpacker;
636 pDoc->m_infoHandler = m_infoHandler;
637 pDoc->m_dwFlags[0] = m_dwFlags[0];
638 pDoc->m_dwFlags[1] = m_dwFlags[1];
639 pDoc->m_dwFlags[2] = m_dwFlags[2];
641 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
642 GetParentFrame()->PostMessage(WM_CLOSE);
644 PathContext tmpPathContext(pDoc->m_files);
647 PackingInfo tmpPackingInfo(pDoc->m_infoHandler);
648 GetMainFrame()->DoFileOpen(
649 &tmpPathContext, std::array<DWORD, 3>(pDoc->m_dwFlags).data(),
650 nullptr, _T(""), pDoc->m_bRecurse, nullptr, _T(""), &tmpPackingInfo);
654 GetMainFrame()->DoFileOpen(nID, &m_files, pDoc->m_dwFlags.data());
658 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
660 pCmdUI->Enable(GetDlgItem(IDC_UNPACKER_EDIT)->IsWindowEnabled());
664 * @brief Called when dialog is closed with "OK".
666 * Checks that paths are valid and sets filters.
668 void COpenView::OnOK()
674 * @brief Called when dialog is closed via Cancel.
676 * Open-dialog is closed when `Cancel` button is selected or the
677 * `Esc` key is pressed. Save combobox states, since user may have
678 * removed items from them (with `shift-del`) and doesn't want them
680 * This is *not* called when the program is terminated, even if the
681 * dialog is visible at the time.
683 void COpenView::OnCancel()
685 SaveComboboxStates();
686 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
690 * @brief Callled when Open-button for project file is selected.
692 void COpenView::OnLoadProject()
694 String fileName = AskProjectFileName(true);
695 if (fileName.empty())
699 if (!theApp.LoadProjectFile(fileName, project))
701 if (project.Items().size() == 0)
704 ProjectFileItem& projItem = *project.Items().begin();
705 projItem.GetPaths(paths, m_bRecurse);
706 projItem.GetLeftReadOnly();
707 if (paths.GetSize() < 3)
709 m_strPath[0] = paths[0];
710 m_strPath[1] = paths[1];
711 m_strPath[2] = _T("");
712 m_bReadOnly[0] = projItem.GetLeftReadOnly();
713 m_bReadOnly[1] = projItem.GetRightReadOnly();
714 m_bReadOnly[2] = false;
718 m_strPath[0] = paths[0];
719 m_strPath[1] = paths[1];
720 m_strPath[2] = paths[2];
721 m_bReadOnly[0] = projItem.GetLeftReadOnly();
722 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
723 m_bReadOnly[2] = projItem.GetRightReadOnly();
725 m_strExt = projItem.GetFilter();
728 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
732 * @brief Called when Save-button for project file is selected.
734 void COpenView::OnSaveProject()
738 String fileName = AskProjectFileName(false);
739 if (fileName.empty())
743 ProjectFileItem projItem;
745 if (!m_strPath[0].empty())
746 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
747 if (m_strPath[2].empty())
749 if (!m_strPath[1].empty())
750 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
754 if (!m_strPath[1].empty())
755 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
756 if (!m_strPath[2].empty())
757 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
759 if (!m_strExt.empty())
761 // Remove possbile prefix from the filter name
762 String prefix = _("[F] ");
763 String strExt = m_strExt;
764 size_t ind = strExt.find(prefix, 0);
767 strExt.erase(0, prefix.length());
769 strExt = strutils::trim_ws_begin(strExt);
770 projItem.SetFilter(strExt);
772 projItem.SetSubfolders(m_bRecurse);
773 project.Items().push_back(projItem);
775 if (!theApp.SaveProjectFile(fileName, project))
778 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
781 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
783 CRect rcButton, rcView;
784 GetDlgItem(nID)->GetWindowRect(&rcButton);
786 VERIFY(menu.LoadMenu(nPopupID));
787 theApp.TranslateMenu(menu.m_hMenu);
788 CMenu* pPopup = menu.GetSubMenu(0);
789 if (pPopup != nullptr)
791 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
792 rcButton.left, rcButton.bottom, GetMainFrame());
797 template<UINT id, UINT popupid>
798 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
800 DropDown(pNMHDR, pResult, id, popupid);
804 * @brief Allow user to select a file to open/save.
806 String COpenView::AskProjectFileName(bool bOpen)
808 // get the default projects path
809 String strProjectFileName;
810 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
812 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
813 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
816 if (strProjectFileName.empty())
819 // get the path part from the filename
820 strProjectPath = paths::GetParentPath(strProjectFileName);
821 // store this as the new project path
822 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
823 return strProjectFileName;
827 * @brief Load File- and filter-combobox states.
829 void COpenView::LoadComboboxStates()
831 m_ctlPath[0].LoadState(_T("Files\\Left"));
832 m_ctlPath[1].LoadState(_T("Files\\Right"));
833 m_ctlPath[2].LoadState(_T("Files\\Option"));
834 m_ctlExt.LoadState(_T("Files\\Ext"));
838 * @brief Save File- and filter-combobox states.
840 void COpenView::SaveComboboxStates()
842 m_ctlPath[0].SaveState(_T("Files\\Left"));
843 m_ctlPath[1].SaveState(_T("Files\\Right"));
844 m_ctlPath[2].SaveState(_T("Files\\Option"));
845 m_ctlExt.SaveState(_T("Files\\Ext"));
848 struct UpdateButtonStatesThreadParams
854 static UINT UpdateButtonStatesThread(LPVOID lpParam)
859 CoInitialize(nullptr);
860 CAssureScriptsForThread scriptsForRescan;
862 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
866 if (msg.message != WM_USER + 2)
869 bool bIsaFolderCompare = true;
870 bool bIsaFileCompare = true;
871 bool bInvalid[3] = {false, false, false};
872 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
873 int iStatusMsgId = IDS_OPEN_FILESDIRS;
875 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
876 PathContext paths = pParams->m_paths;
877 HWND hWnd = pParams->m_hWnd;
880 // Check if we have project file as left side path
881 bool bProject = false;
883 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
884 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
889 for (int i = 0; i < paths.GetSize(); ++i)
891 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
892 if (pathType[i] == paths::DOES_NOT_EXIST)
897 // Enable buttons as appropriate
898 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
900 paths::PATH_EXISTENCE pathsType = pathType[0];
902 if (paths.GetSize() <= 2)
904 if (bInvalid[0] && bInvalid[1])
905 iStatusMsgId = IDS_OPEN_BOTHINVALID;
906 else if (bInvalid[0])
907 iStatusMsgId = IDS_OPEN_LEFTINVALID;
908 else if (bInvalid[1])
910 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
911 iStatusMsgId = IDS_OPEN_FILESDIRS;
913 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
915 else if (!bInvalid[0] && !bInvalid[1])
917 if (pathType[0] != pathType[1])
918 iStatusMsgId = IDS_OPEN_MISMATCH;
920 iStatusMsgId = IDS_OPEN_FILESDIRS;
925 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
926 iStatusMsgId = IDS_OPEN_ALLINVALID;
927 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
928 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
929 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
930 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
931 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
932 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
933 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
934 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
935 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
936 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
937 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
938 iStatusMsgId = IDS_OPEN_LEFTINVALID;
939 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
941 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
942 iStatusMsgId = IDS_OPEN_MISMATCH;
944 iStatusMsgId = IDS_OPEN_FILESDIRS;
947 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
948 pathsType = paths::DOES_NOT_EXIST;
949 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
950 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
951 // Both will be `false` if incompatibilities or something is missing
952 // Both will end up `true` if file validity isn't being checked
955 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
964 * @brief Update any resources necessary after a GUI language change
966 void COpenView::UpdateResources()
968 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
969 if (m_strUnpacker != m_infoHandler.m_PluginName)
970 m_strUnpacker = theApp.LoadString(IDS_OPEN_UNPACKERDISABLED);
974 * @brief Enable/disable components based on validity of paths.
976 void COpenView::UpdateButtonStates()
978 UpdateData(TRUE); // load member variables from screen
979 KillTimer(IDT_CHECKFILES);
982 if (m_pUpdateButtonStatusThread == nullptr)
984 m_pUpdateButtonStatusThread = AfxBeginThread(
985 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
986 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
987 m_pUpdateButtonStatusThread->ResumeThread();
988 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
992 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
993 pParams->m_hWnd = this->m_hWnd;
994 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
996 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
999 void COpenView::TerminateThreadIfRunning()
1001 if (m_pUpdateButtonStatusThread == nullptr)
1004 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1005 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1006 if (dwResult != WAIT_OBJECT_0)
1008 m_pUpdateButtonStatusThread->SuspendThread();
1009 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1011 delete m_pUpdateButtonStatusThread;
1012 m_pUpdateButtonStatusThread = nullptr;
1016 * @brief Called when user changes selection in left/middle/right path's combo box.
1018 void COpenView::OnSelchangePathCombo(UINT nId)
1020 const int index = nId - IDC_PATH0_COMBO;
1021 int sel = m_ctlPath[index].GetCurSel();
1025 m_ctlPath[index].GetLBText(sel, cstrPath);
1026 m_strPath[index] = cstrPath;
1027 m_ctlPath[index].SetWindowText(cstrPath);
1030 UpdateButtonStates();
1033 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1035 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1037 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1039 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1040 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1045 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1047 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1053 * @brief Called every time paths are edited.
1055 void COpenView::OnEditEvent(UINT nID)
1057 const int N = nID - IDC_PATH0_COMBO;
1058 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1060 int const len = edit->GetWindowTextLength();
1061 if (edit->GetSel() == MAKEWPARAM(len, len))
1064 edit->GetWindowText(text);
1065 // Remove any double quotes
1067 if (text.GetLength() != len)
1069 edit->SetSel(0, len);
1070 edit->ReplaceSel(text);
1074 // (Re)start timer to path validity check delay
1075 // If timer starting fails, update buttonstates immediately
1076 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1077 UpdateButtonStates();
1081 * @brief Handle timer events.
1082 * Checks if paths are valid and sets control states accordingly.
1083 * @param [in] nIDEvent Timer ID that fired.
1085 void COpenView::OnTimer(UINT_PTR nIDEvent)
1087 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1088 UpdateButtonStates();
1090 CFormView::OnTimer(nIDEvent);
1094 * @brief Called when users selects plugin browse button.
1096 void COpenView::OnSelectUnpacker()
1098 paths::PATH_EXISTENCE pathsType;
1102 for (auto& strPath: m_strPath)
1104 if (nFiles == 2 && strPath.empty())
1106 m_files.SetSize(nFiles + 1);
1107 m_files[nFiles] = strPath;
1110 pathsType = paths::GetPairComparability(m_files);
1112 if (pathsType != paths::IS_EXISTING_FILE)
1115 // let the user select a handler
1116 CSelectUnpackerDlg dlg(m_files[0], this);
1117 PackingInfo infoUnpacker(PLUGIN_MODE::PLUGIN_AUTO);
1118 dlg.SetInitialInfoHandler(&infoUnpacker);
1120 if (dlg.DoModal() == IDOK)
1122 m_infoHandler = dlg.GetInfoHandler();
1124 m_strUnpacker = m_infoHandler.m_PluginName;
1130 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1132 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1133 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1134 const bool bProject = HIWORD(lParam) != 0;
1135 const int iStatusMsgId = LOWORD(lParam);
1137 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1139 EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
1140 EnableDlgItem(IDC_UNPACKER_EDIT, bIsaFileCompare);
1141 EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
1143 EnableDlgItem(IDC_FILES_DIRS_GROUP3, bIsaFolderCompare);
1144 EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
1145 EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
1146 EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
1148 SetStatus(iStatusMsgId);
1150 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1152 if (m_retryCount == 0)
1153 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1158 KillTimer(IDT_RETRY);
1165 * @brief Sets the path status text.
1166 * The open dialog shows a status text of selected paths. This function
1167 * is used to set that status text.
1168 * @param [in] msgID Resource ID of status text to set.
1170 void COpenView::SetStatus(UINT msgID)
1172 String msg = theApp.LoadString(msgID);
1173 SetDlgItemText(IDC_OPEN_STATUS, msg);
1177 * @brief Set the plugin edit box text.
1178 * Plugin edit box is at the same time a plugin status view. This function
1179 * sets the status text.
1180 * @param [in] msgID Resource ID of status text to set.
1182 void COpenView::SetUnpackerStatus(UINT msgID)
1184 String msg = (msgID == 0 ? m_strUnpacker : theApp.LoadString(msgID));
1185 SetDlgItemText(IDC_UNPACKER_EDIT, msg);
1189 * @brief Called when "Select..." button for filters is selected.
1191 void COpenView::OnSelectFilter()
1193 String filterPrefix = _("[F] ");
1196 const bool bUseMask = theApp.m_pGlobalFileFilter->IsUsingMask();
1197 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1198 curFilter = strutils::trim_ws(curFilter);
1200 GetMainFrame()->SelectFilter();
1202 String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1203 if (theApp.m_pGlobalFileFilter->IsUsingMask())
1205 // If we had filter chosen and now has mask we can overwrite filter
1206 if (!bUseMask || curFilter[0] != '*')
1208 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1213 filterNameOrMask = filterPrefix + filterNameOrMask;
1214 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1218 void COpenView::OnOptions()
1220 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1223 void COpenView::OnDropDownOptions(NMHDR *pNMHDR, LRESULT *pResult)
1225 NMTOOLBAR dropDown = { 0 };
1226 dropDown.hdr.code = TBN_DROPDOWN;
1227 dropDown.hdr.hwndFrom = GetMainFrame()->GetDescendantWindow(AFX_IDW_TOOLBAR)->GetSafeHwnd();
1228 dropDown.hdr.idFrom = AFX_IDW_TOOLBAR;
1229 GetDlgItem(IDC_OPTIONS)->GetWindowRect(&dropDown.rcButton);
1230 GetMainFrame()->ScreenToClient(&dropDown.rcButton);
1231 GetMainFrame()->SendMessage(WM_NOTIFY, dropDown.hdr.idFrom, reinterpret_cast<LPARAM>(&dropDown));
1236 * @brief Read paths and filter from project file.
1237 * Reads the given project file. After the file is read, found paths and
1238 * filter is updated to dialog GUI. Other possible settings found in the
1239 * project file are kept in memory and used later when loading paths
1241 * @param [in] path Path to the project file.
1242 * @return `true` if the project file was successfully loaded, `false` otherwise.
1244 bool COpenView::LoadProjectFile(const String &path)
1246 String filterPrefix = _("[F] ");
1249 if (!theApp.LoadProjectFile(path, prj))
1251 if (prj.Items().size() == 0)
1254 ProjectFileItem& projItem = *prj.Items().begin();
1255 projItem.GetPaths(m_files, recurse);
1256 m_bRecurse = recurse;
1257 m_dwFlags[0] &= ~FFILEOPEN_READONLY;
1258 m_dwFlags[0] |= projItem.GetLeftReadOnly() ? FFILEOPEN_READONLY : 0;
1259 if (m_files.GetSize() < 3)
1261 m_dwFlags[1] &= ~FFILEOPEN_READONLY;
1262 m_dwFlags[1] |= projItem.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
1266 m_dwFlags[1] &= ~FFILEOPEN_READONLY;
1267 m_dwFlags[1] |= projItem.GetMiddleReadOnly() ? FFILEOPEN_READONLY : 0;
1268 m_dwFlags[2] &= ~FFILEOPEN_READONLY;
1269 m_dwFlags[2] |= projItem.GetRightReadOnly() ? FFILEOPEN_READONLY : 0;
1271 if (projItem.HasFilter())
1273 m_strExt = strutils::trim_ws(projItem.GetFilter());
1274 if (m_strExt[0] != '*')
1275 m_strExt.insert(0, filterPrefix);
1281 * @brief Removes whitespaces from left and right paths
1282 * @note Assumes UpdateData(TRUE) is called before this function.
1284 void COpenView::TrimPaths()
1286 for (auto& strPath: m_strPath)
1287 strPath = strutils::trim_ws(strPath);
1291 * @brief Update control states when dialog is activated.
1293 * Update control states when user re-activates dialog. User might have
1294 * switched for other program to e.g. update files/folders and then
1295 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1298 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1300 CFormView::OnActivate(nState, pWndOther, bMinimized);
1302 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1303 UpdateButtonStates();
1306 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1308 CWnd *pCtl = GetFocus();
1309 if (pCtl != nullptr)
1310 pCtl->PostMessage(msg, wParam, lParam);
1313 template <int MSG, int WPARAM, int LPARAM>
1314 void COpenView::OnEditAction()
1316 OnEditAction(MSG, WPARAM, LPARAM);
1320 * @brief Open help from mainframe when user presses F1.
1322 void COpenView::OnHelp()
1324 theApp.ShowHelp(OpenDlgHelpLocation);
1327 /////////////////////////////////////////////////////////////////////////////
1329 // OnDropFiles code from CDropEdit
1330 // Copyright 1997 Chris Losinger
1332 // shortcut expansion code modified from :
1333 // CShortcut, 1996 Rob Warner
1337 * @brief Drop paths(s) to the dialog.
1338 * One or two paths can be dropped to the dialog. The behaviour is:
1340 * - drop to empty path edit box (check left first)
1341 * - if both boxes have a path, drop to left path
1343 * - overwrite both paths, empty or not
1344 * @param [in] dropInfo Dropped data, including paths.
1346 void COpenView::OnDropFiles(const std::vector<String>& files)
1348 const size_t fileCount = files.size();
1350 // Add dropped paths to the dialog
1354 m_strPath[0] = files[0];
1355 m_strPath[1] = files[1];
1356 m_strPath[2] = files[2];
1358 UpdateButtonStates();
1360 else if (fileCount == 2)
1362 m_strPath[0] = files[0];
1363 m_strPath[1] = files[1];
1365 UpdateButtonStates();
1367 else if (fileCount == 1)
1370 GetCursorPos(&point);
1371 ScreenToClient(&point);
1372 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1373 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1375 switch (int const id = pwndHit->GetDlgCtrlID())
1377 case IDC_PATH0_COMBO:
1378 case IDC_PATH1_COMBO:
1379 case IDC_PATH2_COMBO:
1380 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1383 if (m_strPath[0].empty())
1384 m_strPath[0] = files[0];
1385 else if (m_strPath[1].empty())
1386 m_strPath[1] = files[0];
1387 else if (m_strPath[2].empty())
1388 m_strPath[2] = files[0];
1390 m_strPath[0] = files[0];
1395 UpdateButtonStates();