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 "SelectPluginDlg.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_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
94 ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
95 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
99 ON_WM_WINDOWPOSCHANGING()
100 ON_WM_WINDOWPOSCHANGED()
106 // COpenView construction/destruction
108 COpenView::COpenView()
109 : CFormView(COpenView::IDD)
110 , m_pUpdateButtonStatusThread(nullptr)
112 , m_pDropHandler(nullptr)
114 , m_bAutoCompleteReady()
115 , m_bReadOnly {false, false, false}
116 , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
117 , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
120 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
121 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
122 m_bInsideUpdate = TRUE;
125 COpenView::~COpenView()
127 TerminateThreadIfRunning();
130 void COpenView::DoDataExchange(CDataExchange* pDX)
132 CFormView::DoDataExchange(pDX);
133 //{{AFX_DATA_MAP(COpenView)
134 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
135 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
136 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
137 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
138 DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
139 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
140 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
141 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
142 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
143 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
144 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
145 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
146 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
147 DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
151 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
153 // TODO: Modify the Window class or styles here by modifying
154 // the CREATESTRUCT cs
155 cs.style &= ~WS_BORDER;
156 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
157 return CFormView::PreCreateWindow(cs);
160 void COpenView::OnInitialUpdate()
162 if (!IsVista_OrGreater())
165 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
166 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
167 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
170 m_sizeOrig = GetTotalSize();
172 theApp.TranslateDialog(m_hWnd);
174 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
176 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
177 m_image.Create(1, 1, 24, 0);
180 CFormView::OnInitialUpdate();
182 // set caption to "swap paths" button
184 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
185 lf.lfCharSet = SYMBOL_CHARSET;
186 lstrcpy(lf.lfFaceName, _T("Wingdings"));
187 m_fontSwapButton.CreateFontIndirect(&lf);
188 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
189 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
191 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
192 SetDlgItemText(ids[i], _T("\xf4"));
195 m_constraint.InitializeCurrentSize(this);
196 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
197 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
198 m_constraint.SetScrollScale(this, 1.0, 1.0);
199 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
200 m_constraint.DisallowHeightGrowth();
201 //m_constraint.SubclassWnd(); // install subclassing
203 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
204 m_constraint.UpdateSizes();
206 COpenDoc *pDoc = GetDocument();
209 GetWindowText(strTitle);
210 pDoc->SetTitle(strTitle);
212 m_files = pDoc->m_files;
213 m_bRecurse = pDoc->m_bRecurse;
214 m_strExt = pDoc->m_strExt;
215 m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
216 m_dwFlags[0] = pDoc->m_dwFlags[0];
217 m_dwFlags[1] = pDoc->m_dwFlags[1];
218 m_dwFlags[2] = pDoc->m_dwFlags[2];
220 m_ctlPath[0].SetFileControlStates();
221 m_ctlPath[1].SetFileControlStates(true);
222 m_ctlPath[2].SetFileControlStates(true);
223 m_ctlUnpackerPipeline.SetFileControlStates(true);
225 for (int file = 0; file < m_files.GetSize(); file++)
227 m_strPath[file] = m_files[file];
228 m_ctlPath[file].SetWindowText(m_files[file].c_str());
229 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
232 m_ctlPath[0].AttachSystemImageList();
233 m_ctlPath[1].AttachSystemImageList();
234 m_ctlPath[2].AttachSystemImageList();
235 LoadComboboxStates();
237 m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
239 bool bDoUpdateData = true;
240 for (auto& strPath: m_strPath)
242 if (!strPath.empty())
243 bDoUpdateData = false;
245 UpdateData(bDoUpdateData);
247 String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
248 bool bMask = theApp.m_pGlobalFileFilter->IsUsingMask();
252 String filterPrefix = _("[F] ");
253 filterNameOrMask = filterPrefix + filterNameOrMask;
256 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
258 m_ctlExt.SetCurSel(ind);
261 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
263 m_ctlExt.SetCurSel(ind);
265 LogErrorString(_T("Failed to add string to filters combo list!"));
268 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
270 EnableDlgItem(IDOK, true);
271 EnableDlgItem(IDC_UNPACKER_COMBO, true);
272 EnableDlgItem(IDC_SELECT_UNPACKER, true);
275 UpdateButtonStates();
277 bool bOverwriteRecursive = false;
278 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
279 bOverwriteRecursive = true;
280 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
281 bOverwriteRecursive = true;
282 if (!bOverwriteRecursive)
283 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
286 SetStatus(IDS_OPEN_FILESDIRS);
288 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
289 RegisterDragDrop(m_hWnd, m_pDropHandler);
292 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
294 m_bRecurse = GetDocument()->m_bRecurse;
298 // COpenView diagnostics
301 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
303 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
304 return (COpenDoc*)m_pDocument;
308 /////////////////////////////////////////////////////////////////////////////
309 // COpenView message handlers
311 void COpenView::OnPaint()
317 // Draw the logo image
318 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
319 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
320 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
321 // And extend it to the Right boundary
322 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
324 // Draw the resize gripper in the Lower Right corner.
326 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
327 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
328 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
330 // Draw a line to separate the Status Line
331 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
332 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
335 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
336 ScreenToClient(&rcStatus);
337 dc.MoveTo(0, rcStatus.top - 3);
338 dc.LineTo(rc.right, rcStatus.top - 3);
339 dc.SelectObject(oldpen);
341 CFormView::OnPaint();
344 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
346 if (::GetCapture() == m_hWnd)
348 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
349 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
351 switch (int const id1 = pwndHit->GetDlgCtrlID())
353 case IDC_PATH0_COMBO:
354 case IDC_PATH1_COMBO:
355 case IDC_PATH2_COMBO:
357 CWnd *pwndChild = GetFocus();
358 if (IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
360 id2 = pwndChild->GetDlgCtrlID();
361 pwndChild = pwndChild->GetParent();
362 } while (pwndChild != this);
365 case IDC_PATH0_COMBO:
366 case IDC_PATH1_COMBO:
367 case IDC_PATH2_COMBO:
369 GetDlgItemText(id1, s1);
370 GetDlgItemText(id2, s2);
371 SetDlgItemText(id1, s2);
372 SetDlgItemText(id2, s1);
383 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
385 if (::GetCapture() == m_hWnd)
387 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
388 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
390 switch (pwndHit->GetDlgCtrlID())
392 case IDC_PATH0_COMBO:
393 case IDC_PATH1_COMBO:
394 case IDC_PATH2_COMBO:
395 if (!pwndHit->IsChild(GetFocus()))
397 SetCursor(m_hIconRotate);
402 SetCursor(m_hCursorNo);
409 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
411 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
413 CFrameWnd *const pFrameWnd = GetParentFrame();
414 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
417 pFrameWnd->GetClientRect(&rc);
418 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
419 lpwndpos->cy = m_sizeOrig.cy;
420 if (lpwndpos->flags & SWP_NOOWNERZORDER)
422 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
423 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
424 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
428 else if (pFrameWnd->IsZoomed())
430 lpwndpos->cx = m_totalLog.cx;
431 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
435 if (lpwndpos->cx > rc.Width())
436 lpwndpos->cx = rc.Width();
437 if (lpwndpos->cx < m_sizeOrig.cx)
438 lpwndpos->cx = m_sizeOrig.cx;
439 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
446 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
448 if (lpwndpos->flags & SWP_FRAMECHANGED)
450 m_constraint.UpdateSizes();
451 CFrameWnd *const pFrameWnd = GetParentFrame();
452 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
454 m_constraint.Persist(true, false);
455 WINDOWPLACEMENT wp = {};
456 wp.length = sizeof wp;
457 pFrameWnd->GetWindowPlacement(&wp);
460 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
461 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
462 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
463 pFrameWnd->SetWindowPlacement(&wp);
466 CFormView::OnWindowPosChanged(lpwndpos);
469 void COpenView::OnDestroy()
471 if (m_pDropHandler != nullptr)
472 RevokeDragDrop(m_hWnd);
474 CFormView::OnDestroy();
477 LRESULT COpenView::OnNcHitTest(CPoint point)
479 if (GetParentFrame()->IsZoomed())
483 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
484 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
485 if (PtInRect(&rc, point))
488 return CFormView::OnNcHitTest(point);
492 * @brief Called when "Browse..." button is selected for N path.
494 void COpenView::OnPathButton(UINT nId)
496 const int index = nId - IDC_PATH0_BUTTON;
501 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
504 case paths::IS_EXISTING_DIR:
505 sfolder = m_strPath[index];
507 case paths::IS_EXISTING_FILE:
508 sfolder = paths::GetPathOnly(m_strPath[index]);
510 case paths::DOES_NOT_EXIST:
511 if (!m_strPath[index].empty())
512 sfolder = paths::GetParentPath(m_strPath[index]);
515 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
519 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
521 m_strPath[index] = s;
522 m_strBrowsePath[index] = s;
524 UpdateButtonStates();
528 void COpenView::OnSwapButton(int id1, int id2)
531 GetDlgItemText(id1, s1);
532 GetDlgItemText(id2, s2);
534 SetDlgItemText(id1, s1);
535 SetDlgItemText(id2, s2);
538 template<int id1, int id2>
539 void COpenView::OnSwapButton()
541 OnSwapButton(id1, id2);
544 void COpenView::OnCompare(UINT nID)
546 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
547 const String filterPrefix = _("[F] ");
553 for (auto& strPath : m_strPath)
555 if (nFiles >= 1 && strPath.empty())
557 m_files.SetSize(nFiles + 1);
558 m_files[nFiles] = strPath;
559 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
560 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
563 // If left path is a project-file, load it
565 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
568 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
570 theApp.LoadAndOpenProjectFile(m_strPath[0]);
572 else if (!paths::IsDirectory(m_strPath[0]))
574 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
575 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
576 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
577 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo);
582 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
584 if (pathsType == paths::DOES_NOT_EXIST)
586 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
590 for (int index = 0; index < nFiles; index++)
592 // If user has edited path by hand, expand environment variables
593 bool bExpand = false;
594 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
597 if (!paths::IsURLorCLSID(m_files[index]))
599 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
601 // Add trailing '\' for directories if its missing
602 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
603 m_files[index] = paths::AddTrailingSlash(m_files[index]);
604 m_strPath[index] = m_files[index];
609 KillTimer(IDT_CHECKFILES);
610 KillTimer(IDT_RETRY);
612 String filter(strutils::trim_ws(m_strExt));
614 // If prefix found from start..
615 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
617 // Remove prefix + space
618 filter.erase(0, filterPrefix.length());
619 if (!theApp.m_pGlobalFileFilter->SetFilter(filter))
621 // If filtername is not found use default *.* mask
622 theApp.m_pGlobalFileFilter->SetFilter(_T("*.*"));
625 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
629 bool bFilterSet = theApp.m_pGlobalFileFilter->SetFilter(filter);
631 m_strExt = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
632 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
635 SaveComboboxStates();
636 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
637 LoadComboboxStates();
639 m_constraint.Persist(true, false);
641 COpenDoc *pDoc = GetDocument();
642 pDoc->m_files = m_files;
643 pDoc->m_bRecurse = m_bRecurse;
644 pDoc->m_strExt = m_strExt;
645 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
646 pDoc->m_dwFlags[0] = m_dwFlags[0];
647 pDoc->m_dwFlags[1] = m_dwFlags[1];
648 pDoc->m_dwFlags[2] = m_dwFlags[2];
650 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
651 GetParentFrame()->PostMessage(WM_CLOSE);
653 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
654 PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
655 PathContext tmpPathContext(pDoc->m_files);
656 std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
657 bool recurse = pDoc->m_bRecurse;
660 GetMainFrame()->DoFileOpen(
661 &tmpPathContext, dwFlags.data(),
662 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
664 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
666 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
667 GetMainFrame()->DoFileOpen(
668 &tmpPathContext, dwFlags.data(),
669 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
671 else if (nID == ID_OPEN_WITH_UNPACKER)
673 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0], true, false, this);
674 if (dlg.DoModal() == IDOK)
676 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
677 GetMainFrame()->DoFileOpen(
678 &tmpPathContext, dwFlags.data(),
679 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
684 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo);
688 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
690 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
694 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
695 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
696 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
699 pCmdUI->Enable(bFile);
703 * @brief Called when dialog is closed with "OK".
705 * Checks that paths are valid and sets filters.
707 void COpenView::OnOK()
713 * @brief Called when dialog is closed via Cancel.
715 * Open-dialog is closed when `Cancel` button is selected or the
716 * `Esc` key is pressed. Save combobox states, since user may have
717 * removed items from them (with `shift-del`) and doesn't want them
719 * This is *not* called when the program is terminated, even if the
720 * dialog is visible at the time.
722 void COpenView::OnCancel()
724 SaveComboboxStates();
725 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
729 * @brief Callled when Open-button for project file is selected.
731 void COpenView::OnLoadProject()
733 String fileName = AskProjectFileName(true);
734 if (fileName.empty())
738 if (!theApp.LoadProjectFile(fileName, project))
740 if (project.Items().size() == 0)
743 ProjectFileItem& projItem = *project.Items().begin();
744 projItem.GetPaths(paths, m_bRecurse);
745 projItem.GetLeftReadOnly();
746 if (paths.GetSize() < 3)
748 m_strPath[0] = paths[0];
749 m_strPath[1] = paths[1];
750 m_strPath[2] = _T("");
751 m_bReadOnly[0] = projItem.GetLeftReadOnly();
752 m_bReadOnly[1] = projItem.GetRightReadOnly();
753 m_bReadOnly[2] = false;
757 m_strPath[0] = paths[0];
758 m_strPath[1] = paths[1];
759 m_strPath[2] = paths[2];
760 m_bReadOnly[0] = projItem.GetLeftReadOnly();
761 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
762 m_bReadOnly[2] = projItem.GetRightReadOnly();
764 m_strExt = projItem.GetFilter();
765 if (projItem.HasUnpacker())
766 m_strUnpackerPipeline = projItem.GetUnpacker();
769 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
773 * @brief Called when Save-button for project file is selected.
775 void COpenView::OnSaveProject()
779 String fileName = AskProjectFileName(false);
780 if (fileName.empty())
784 ProjectFileItem projItem;
786 if (!m_strPath[0].empty())
787 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
788 if (m_strPath[2].empty())
790 if (!m_strPath[1].empty())
791 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
795 if (!m_strPath[1].empty())
796 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
797 if (!m_strPath[2].empty())
798 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
800 if (!m_strExt.empty())
802 // Remove possbile prefix from the filter name
803 String prefix = _("[F] ");
804 String strExt = m_strExt;
805 size_t ind = strExt.find(prefix, 0);
808 strExt.erase(0, prefix.length());
810 strExt = strutils::trim_ws_begin(strExt);
811 projItem.SetFilter(strExt);
813 projItem.SetSubfolders(m_bRecurse);
814 if (!m_strUnpackerPipeline.empty())
815 projItem.SetUnpacker(m_strUnpackerPipeline);
816 project.Items().push_back(projItem);
818 if (!theApp.SaveProjectFile(fileName, project))
821 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
824 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
826 CRect rcButton, rcView;
827 GetDlgItem(nID)->GetWindowRect(&rcButton);
829 VERIFY(menu.LoadMenu(nPopupID));
830 theApp.TranslateMenu(menu.m_hMenu);
831 CMenu* pPopup = menu.GetSubMenu(0);
832 if (pPopup != nullptr)
834 if (GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
838 for (int i = 0; i < 3; i++)
839 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
840 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
841 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
843 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
844 rcButton.left, rcButton.bottom, GetMainFrame());
849 template<UINT id, UINT popupid>
850 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
852 DropDown(pNMHDR, pResult, id, popupid);
856 * @brief Allow user to select a file to open/save.
858 String COpenView::AskProjectFileName(bool bOpen)
860 // get the default projects path
861 String strProjectFileName;
862 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
864 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
865 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
868 if (strProjectFileName.empty())
871 // get the path part from the filename
872 strProjectPath = paths::GetParentPath(strProjectFileName);
873 // store this as the new project path
874 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
875 return strProjectFileName;
879 * @brief Load File- and filter-combobox states.
881 void COpenView::LoadComboboxStates()
883 m_ctlPath[0].LoadState(_T("Files\\Left"));
884 m_ctlPath[1].LoadState(_T("Files\\Right"));
885 m_ctlPath[2].LoadState(_T("Files\\Option"));
886 m_ctlExt.LoadState(_T("Files\\Ext"));
887 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
891 * @brief Save File- and filter-combobox states.
893 void COpenView::SaveComboboxStates()
895 m_ctlPath[0].SaveState(_T("Files\\Left"));
896 m_ctlPath[1].SaveState(_T("Files\\Right"));
897 m_ctlPath[2].SaveState(_T("Files\\Option"));
898 m_ctlExt.SaveState(_T("Files\\Ext"));
899 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
902 struct UpdateButtonStatesThreadParams
908 static UINT UpdateButtonStatesThread(LPVOID lpParam)
913 CoInitialize(nullptr);
914 CAssureScriptsForThread scriptsForRescan;
916 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
920 if (msg.message != WM_USER + 2)
923 bool bIsaFolderCompare = true;
924 bool bIsaFileCompare = true;
925 bool bInvalid[3] = {false, false, false};
926 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
927 int iStatusMsgId = IDS_OPEN_FILESDIRS;
929 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
930 PathContext paths = pParams->m_paths;
931 HWND hWnd = pParams->m_hWnd;
934 // Check if we have project file as left side path
935 bool bProject = false;
937 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
938 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
943 for (int i = 0; i < paths.GetSize(); ++i)
945 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
946 if (pathType[i] == paths::DOES_NOT_EXIST)
951 // Enable buttons as appropriate
952 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
954 paths::PATH_EXISTENCE pathsType = pathType[0];
956 if (paths.GetSize() <= 2)
958 if (bInvalid[0] && bInvalid[1])
959 iStatusMsgId = IDS_OPEN_BOTHINVALID;
960 else if (bInvalid[0])
961 iStatusMsgId = IDS_OPEN_LEFTINVALID;
962 else if (bInvalid[1])
964 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
965 iStatusMsgId = IDS_OPEN_FILESDIRS;
967 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
969 else if (!bInvalid[0] && !bInvalid[1])
971 if (pathType[0] != pathType[1])
972 iStatusMsgId = IDS_OPEN_MISMATCH;
974 iStatusMsgId = IDS_OPEN_FILESDIRS;
979 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
980 iStatusMsgId = IDS_OPEN_ALLINVALID;
981 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
982 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
983 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
984 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
985 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
986 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
987 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
988 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
989 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
990 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
991 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
992 iStatusMsgId = IDS_OPEN_LEFTINVALID;
993 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
995 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
996 iStatusMsgId = IDS_OPEN_MISMATCH;
998 iStatusMsgId = IDS_OPEN_FILESDIRS;
1001 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1002 pathsType = paths::DOES_NOT_EXIST;
1003 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1004 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1005 // Both will be `false` if incompatibilities or something is missing
1006 // Both will end up `true` if file validity isn't being checked
1009 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1018 * @brief Update any resources necessary after a GUI language change
1020 void COpenView::UpdateResources()
1022 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1026 * @brief Enable/disable components based on validity of paths.
1028 void COpenView::UpdateButtonStates()
1030 UpdateData(TRUE); // load member variables from screen
1031 KillTimer(IDT_CHECKFILES);
1034 if (m_pUpdateButtonStatusThread == nullptr)
1036 m_pUpdateButtonStatusThread = AfxBeginThread(
1037 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1038 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1039 m_pUpdateButtonStatusThread->ResumeThread();
1040 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1044 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1045 pParams->m_hWnd = this->m_hWnd;
1046 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1048 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1051 void COpenView::TerminateThreadIfRunning()
1053 if (m_pUpdateButtonStatusThread == nullptr)
1056 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1057 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1058 if (dwResult != WAIT_OBJECT_0)
1060 m_pUpdateButtonStatusThread->SuspendThread();
1061 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1063 delete m_pUpdateButtonStatusThread;
1064 m_pUpdateButtonStatusThread = nullptr;
1068 * @brief Called when user changes selection in left/middle/right path's combo box.
1070 void COpenView::OnSelchangePathCombo(UINT nId)
1072 const int index = nId - IDC_PATH0_COMBO;
1073 int sel = m_ctlPath[index].GetCurSel();
1077 m_ctlPath[index].GetLBText(sel, cstrPath);
1078 m_strPath[index] = cstrPath;
1079 m_ctlPath[index].SetWindowText(cstrPath);
1082 UpdateButtonStates();
1085 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1087 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1089 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1091 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1092 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1097 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1099 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1105 * @brief Called every time paths are edited.
1107 void COpenView::OnEditEvent(UINT nID)
1109 const int N = nID - IDC_PATH0_COMBO;
1110 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1112 int const len = edit->GetWindowTextLength();
1113 if (edit->GetSel() == MAKEWPARAM(len, len))
1116 edit->GetWindowText(text);
1117 // Remove any double quotes
1119 if (text.GetLength() != len)
1121 edit->SetSel(0, len);
1122 edit->ReplaceSel(text);
1126 // (Re)start timer to path validity check delay
1127 // If timer starting fails, update buttonstates immediately
1128 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1129 UpdateButtonStates();
1133 * @brief Handle timer events.
1134 * Checks if paths are valid and sets control states accordingly.
1135 * @param [in] nIDEvent Timer ID that fired.
1137 void COpenView::OnTimer(UINT_PTR nIDEvent)
1139 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1140 UpdateButtonStates();
1142 CFormView::OnTimer(nIDEvent);
1146 * @brief Called when users selects plugin browse button.
1148 void COpenView::OnSelectUnpacker()
1150 paths::PATH_EXISTENCE pathsType;
1154 for (auto& strPath: m_strPath)
1156 if (nFiles == 2 && strPath.empty())
1158 m_files.SetSize(nFiles + 1);
1159 m_files[nFiles] = strPath;
1162 pathsType = paths::GetPairComparability(m_files);
1164 if (pathsType != paths::IS_EXISTING_FILE)
1167 // let the user select a handler
1168 CSelectPluginDlg dlg(m_strUnpackerPipeline, m_files[0], true, false, this);
1169 if (dlg.DoModal() == IDOK)
1171 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1176 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1178 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1179 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1180 const bool bProject = HIWORD(lParam) != 0;
1181 const int iStatusMsgId = LOWORD(lParam);
1183 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1185 EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
1186 EnableDlgItem(IDC_UNPACKER_COMBO, bIsaFileCompare);
1187 EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
1189 EnableDlgItem(IDC_FILES_DIRS_GROUP3, bIsaFolderCompare);
1190 EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
1191 EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
1192 EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
1194 SetStatus(iStatusMsgId);
1196 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1198 if (m_retryCount == 0)
1199 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1204 KillTimer(IDT_RETRY);
1211 * @brief Sets the path status text.
1212 * The open dialog shows a status text of selected paths. This function
1213 * is used to set that status text.
1214 * @param [in] msgID Resource ID of status text to set.
1216 void COpenView::SetStatus(UINT msgID)
1218 String msg = theApp.LoadString(msgID);
1219 SetDlgItemText(IDC_OPEN_STATUS, msg);
1223 * @brief Called when "Select..." button for filters is selected.
1225 void COpenView::OnSelectFilter()
1227 String filterPrefix = _("[F] ");
1230 const bool bUseMask = theApp.m_pGlobalFileFilter->IsUsingMask();
1231 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1232 curFilter = strutils::trim_ws(curFilter);
1234 GetMainFrame()->SelectFilter();
1236 String filterNameOrMask = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1237 if (theApp.m_pGlobalFileFilter->IsUsingMask())
1239 // If we had filter chosen and now has mask we can overwrite filter
1240 if (!bUseMask || curFilter[0] != '*')
1242 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1247 filterNameOrMask = filterPrefix + filterNameOrMask;
1248 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1252 void COpenView::OnOptions()
1254 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1257 void COpenView::OnDropDownOptions(NMHDR *pNMHDR, LRESULT *pResult)
1259 NMTOOLBAR dropDown = { 0 };
1260 dropDown.hdr.code = TBN_DROPDOWN;
1261 dropDown.hdr.hwndFrom = GetMainFrame()->GetDescendantWindow(AFX_IDW_TOOLBAR)->GetSafeHwnd();
1262 dropDown.hdr.idFrom = AFX_IDW_TOOLBAR;
1263 GetDlgItem(IDC_OPTIONS)->GetWindowRect(&dropDown.rcButton);
1264 GetMainFrame()->ScreenToClient(&dropDown.rcButton);
1265 GetMainFrame()->SendMessage(WM_NOTIFY, dropDown.hdr.idFrom, reinterpret_cast<LPARAM>(&dropDown));
1270 * @brief Removes whitespaces from left and right paths
1271 * @note Assumes UpdateData(TRUE) is called before this function.
1273 void COpenView::TrimPaths()
1275 for (auto& strPath: m_strPath)
1276 strPath = strutils::trim_ws(strPath);
1280 * @brief Update control states when dialog is activated.
1282 * Update control states when user re-activates dialog. User might have
1283 * switched for other program to e.g. update files/folders and then
1284 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1287 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1289 CFormView::OnActivate(nState, pWndOther, bMinimized);
1291 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1292 UpdateButtonStates();
1295 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1297 CWnd *pCtl = GetFocus();
1298 if (pCtl != nullptr)
1299 pCtl->PostMessage(msg, wParam, lParam);
1302 template <int MSG, int WPARAM, int LPARAM>
1303 void COpenView::OnEditAction()
1305 OnEditAction(MSG, WPARAM, LPARAM);
1309 * @brief Open help from mainframe when user presses F1.
1311 void COpenView::OnHelp()
1313 theApp.ShowHelp(OpenDlgHelpLocation);
1316 /////////////////////////////////////////////////////////////////////////////
1318 // OnDropFiles code from CDropEdit
1319 // Copyright 1997 Chris Losinger
1321 // shortcut expansion code modified from :
1322 // CShortcut, 1996 Rob Warner
1326 * @brief Drop paths(s) to the dialog.
1327 * One or two paths can be dropped to the dialog. The behaviour is:
1329 * - drop to empty path edit box (check left first)
1330 * - if both boxes have a path, drop to left path
1332 * - overwrite both paths, empty or not
1333 * @param [in] dropInfo Dropped data, including paths.
1335 void COpenView::OnDropFiles(const std::vector<String>& files)
1337 const size_t fileCount = files.size();
1339 // Add dropped paths to the dialog
1343 m_strPath[0] = files[0];
1344 m_strPath[1] = files[1];
1345 m_strPath[2] = files[2];
1347 UpdateButtonStates();
1349 else if (fileCount == 2)
1351 m_strPath[0] = files[0];
1352 m_strPath[1] = files[1];
1354 UpdateButtonStates();
1356 else if (fileCount == 1)
1359 GetCursorPos(&point);
1360 ScreenToClient(&point);
1361 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1362 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1364 switch (int const id = pwndHit->GetDlgCtrlID())
1366 case IDC_PATH0_COMBO:
1367 case IDC_PATH1_COMBO:
1368 case IDC_PATH2_COMBO:
1369 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1372 if (m_strPath[0].empty())
1373 m_strPath[0] = files[0];
1374 else if (m_strPath[1].empty())
1375 m_strPath[1] = files[0];
1376 else if (m_strPath[2].empty())
1377 m_strPath[2] = files[0];
1379 m_strPath[0] = files[0];
1384 UpdateButtonStates();