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 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
248 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
249 bool bMask = pGlobalFileFilter->IsUsingMask();
253 String filterPrefix = _("[F] ");
254 filterNameOrMask = filterPrefix + filterNameOrMask;
257 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
259 m_ctlExt.SetCurSel(ind);
262 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
264 m_ctlExt.SetCurSel(ind);
266 LogErrorString(_T("Failed to add string to filters combo list!"));
269 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
271 EnableDlgItem(IDOK, true);
272 EnableDlgItem(IDC_UNPACKER_COMBO, true);
273 EnableDlgItem(IDC_SELECT_UNPACKER, true);
276 UpdateButtonStates();
278 bool bOverwriteRecursive = false;
279 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
280 bOverwriteRecursive = true;
281 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
282 bOverwriteRecursive = true;
283 if (!bOverwriteRecursive)
284 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
287 SetStatus(IDS_OPEN_FILESDIRS);
289 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
290 RegisterDragDrop(m_hWnd, m_pDropHandler);
293 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
295 m_bRecurse = GetDocument()->m_bRecurse;
299 // COpenView diagnostics
302 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
304 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
305 return (COpenDoc*)m_pDocument;
309 /////////////////////////////////////////////////////////////////////////////
310 // COpenView message handlers
312 void COpenView::OnPaint()
318 // Draw the logo image
319 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
320 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
321 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
322 // And extend it to the Right boundary
323 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
325 // Draw the resize gripper in the Lower Right corner.
327 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
328 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
329 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
331 // Draw a line to separate the Status Line
332 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
333 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
336 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
337 ScreenToClient(&rcStatus);
338 dc.MoveTo(0, rcStatus.top - 3);
339 dc.LineTo(rc.right, rcStatus.top - 3);
340 dc.SelectObject(oldpen);
342 CFormView::OnPaint();
345 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
347 if (::GetCapture() == m_hWnd)
349 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
350 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
352 switch (int const id1 = pwndHit->GetDlgCtrlID())
354 case IDC_PATH0_COMBO:
355 case IDC_PATH1_COMBO:
356 case IDC_PATH2_COMBO:
358 CWnd *pwndChild = GetFocus();
359 if (IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
361 id2 = pwndChild->GetDlgCtrlID();
362 pwndChild = pwndChild->GetParent();
363 } while (pwndChild != this);
366 case IDC_PATH0_COMBO:
367 case IDC_PATH1_COMBO:
368 case IDC_PATH2_COMBO:
370 GetDlgItemText(id1, s1);
371 GetDlgItemText(id2, s2);
372 SetDlgItemText(id1, s2);
373 SetDlgItemText(id2, s1);
384 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
386 if (::GetCapture() == m_hWnd)
388 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
389 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
391 switch (pwndHit->GetDlgCtrlID())
393 case IDC_PATH0_COMBO:
394 case IDC_PATH1_COMBO:
395 case IDC_PATH2_COMBO:
396 if (!pwndHit->IsChild(GetFocus()))
398 SetCursor(m_hIconRotate);
403 SetCursor(m_hCursorNo);
410 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
412 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
414 CFrameWnd *const pFrameWnd = GetParentFrame();
415 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
418 pFrameWnd->GetClientRect(&rc);
419 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
420 lpwndpos->cy = m_sizeOrig.cy;
421 if (lpwndpos->flags & SWP_NOOWNERZORDER)
423 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
424 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
425 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
429 else if (pFrameWnd->IsZoomed())
431 lpwndpos->cx = m_totalLog.cx;
432 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
436 if (lpwndpos->cx > rc.Width())
437 lpwndpos->cx = rc.Width();
438 if (lpwndpos->cx < m_sizeOrig.cx)
439 lpwndpos->cx = m_sizeOrig.cx;
440 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
447 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
449 if (lpwndpos->flags & SWP_FRAMECHANGED)
451 m_constraint.UpdateSizes();
452 CFrameWnd *const pFrameWnd = GetParentFrame();
453 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
455 m_constraint.Persist(true, false);
456 WINDOWPLACEMENT wp = {};
457 wp.length = sizeof wp;
458 pFrameWnd->GetWindowPlacement(&wp);
461 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
462 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
463 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
464 pFrameWnd->SetWindowPlacement(&wp);
467 CFormView::OnWindowPosChanged(lpwndpos);
470 void COpenView::OnDestroy()
472 if (m_pDropHandler != nullptr)
473 RevokeDragDrop(m_hWnd);
475 CFormView::OnDestroy();
478 LRESULT COpenView::OnNcHitTest(CPoint point)
480 if (GetParentFrame()->IsZoomed())
484 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
485 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
486 if (PtInRect(&rc, point))
489 return CFormView::OnNcHitTest(point);
493 * @brief Called when "Browse..." button is selected for N path.
495 void COpenView::OnPathButton(UINT nId)
497 const int index = nId - IDC_PATH0_BUTTON;
502 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
505 case paths::IS_EXISTING_DIR:
506 sfolder = m_strPath[index];
508 case paths::IS_EXISTING_FILE:
509 sfolder = paths::GetPathOnly(m_strPath[index]);
511 case paths::DOES_NOT_EXIST:
512 if (!m_strPath[index].empty())
513 sfolder = paths::GetParentPath(m_strPath[index]);
516 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
520 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
522 m_strPath[index] = s;
523 m_strBrowsePath[index] = s;
525 UpdateButtonStates();
529 void COpenView::OnSwapButton(int id1, int id2)
532 GetDlgItemText(id1, s1);
533 GetDlgItemText(id2, s2);
535 SetDlgItemText(id1, s1);
536 SetDlgItemText(id2, s2);
539 template<int id1, int id2>
540 void COpenView::OnSwapButton()
542 OnSwapButton(id1, id2);
545 void COpenView::OnCompare(UINT nID)
547 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
548 const String filterPrefix = _("[F] ");
549 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
555 for (auto& strPath : m_strPath)
557 if (nFiles >= 1 && strPath.empty())
559 m_files.SetSize(nFiles + 1);
560 m_files[nFiles] = strPath;
561 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
562 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
565 // If left path is a project-file, load it
567 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
570 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
572 theApp.LoadAndOpenProjectFile(m_strPath[0]);
574 else if (!paths::IsDirectory(m_strPath[0]))
576 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
577 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
578 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
579 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo);
584 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
586 if (pathsType == paths::DOES_NOT_EXIST)
588 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
592 for (int index = 0; index < nFiles; index++)
594 // If user has edited path by hand, expand environment variables
595 bool bExpand = false;
596 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
599 if (!paths::IsURLorCLSID(m_files[index]))
601 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
603 // Add trailing '\' for directories if its missing
604 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
605 m_files[index] = paths::AddTrailingSlash(m_files[index]);
606 m_strPath[index] = m_files[index];
611 KillTimer(IDT_CHECKFILES);
612 KillTimer(IDT_RETRY);
614 String filter(strutils::trim_ws(m_strExt));
616 // If prefix found from start..
617 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
619 // Remove prefix + space
620 filter.erase(0, filterPrefix.length());
621 if (!pGlobalFileFilter->SetFilter(filter))
623 // If filtername is not found use default *.* mask
624 pGlobalFileFilter->SetFilter(_T("*.*"));
627 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
631 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
633 m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
634 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
637 SaveComboboxStates();
638 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
639 LoadComboboxStates();
641 m_constraint.Persist(true, false);
643 COpenDoc *pDoc = GetDocument();
644 pDoc->m_files = m_files;
645 pDoc->m_bRecurse = m_bRecurse;
646 pDoc->m_strExt = m_strExt;
647 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
648 pDoc->m_dwFlags[0] = m_dwFlags[0];
649 pDoc->m_dwFlags[1] = m_dwFlags[1];
650 pDoc->m_dwFlags[2] = m_dwFlags[2];
652 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
653 GetParentFrame()->PostMessage(WM_CLOSE);
655 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
656 PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
657 PathContext tmpPathContext(pDoc->m_files);
658 std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
659 bool recurse = pDoc->m_bRecurse;
662 GetMainFrame()->DoFileOrFolderOpen(
663 &tmpPathContext, dwFlags.data(),
664 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
666 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
668 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
669 GetMainFrame()->DoFileOrFolderOpen(
670 &tmpPathContext, dwFlags.data(),
671 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
673 else if (nID == ID_OPEN_WITH_UNPACKER)
675 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0],
676 CSelectPluginDlg::PluginType::Unpacker, false, this);
677 if (dlg.DoModal() == IDOK)
679 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
680 GetMainFrame()->DoFileOrFolderOpen(
681 &tmpPathContext, dwFlags.data(),
682 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
687 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo);
691 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
693 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
697 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
698 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
699 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
702 pCmdUI->Enable(bFile);
706 * @brief Called when dialog is closed with "OK".
708 * Checks that paths are valid and sets filters.
710 void COpenView::OnOK()
716 * @brief Called when dialog is closed via Cancel.
718 * Open-dialog is closed when `Cancel` button is selected or the
719 * `Esc` key is pressed. Save combobox states, since user may have
720 * removed items from them (with `shift-del`) and doesn't want them
722 * This is *not* called when the program is terminated, even if the
723 * dialog is visible at the time.
725 void COpenView::OnCancel()
727 SaveComboboxStates();
728 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
732 * @brief Callled when Open-button for project file is selected.
734 void COpenView::OnLoadProject()
736 String fileName = AskProjectFileName(true);
737 if (fileName.empty())
741 if (!theApp.LoadProjectFile(fileName, project))
743 if (project.Items().size() == 0)
746 ProjectFileItem& projItem = *project.Items().begin();
747 projItem.GetPaths(paths, m_bRecurse);
748 if (paths.GetSize() < 3)
750 m_strPath[0] = paths[0];
751 m_strPath[1] = paths[1];
752 m_strPath[2] = _T("");
753 m_bReadOnly[0] = projItem.GetLeftReadOnly();
754 m_bReadOnly[1] = projItem.GetRightReadOnly();
755 m_bReadOnly[2] = false;
759 m_strPath[0] = paths[0];
760 m_strPath[1] = paths[1];
761 m_strPath[2] = paths[2];
762 m_bReadOnly[0] = projItem.GetLeftReadOnly();
763 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
764 m_bReadOnly[2] = projItem.GetRightReadOnly();
766 m_strExt = projItem.GetFilter();
767 if (projItem.HasUnpacker())
768 m_strUnpackerPipeline = projItem.GetUnpacker();
771 UpdateButtonStates();
772 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
776 * @brief Called when Save-button for project file is selected.
778 void COpenView::OnSaveProject()
782 String fileName = AskProjectFileName(false);
783 if (fileName.empty())
787 ProjectFileItem projItem;
789 if (!m_strPath[0].empty())
790 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
791 if (m_strPath[2].empty())
793 if (!m_strPath[1].empty())
794 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
798 if (!m_strPath[1].empty())
799 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
800 if (!m_strPath[2].empty())
801 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
803 if (!m_strExt.empty())
805 // Remove possbile prefix from the filter name
806 String prefix = _("[F] ");
807 String strExt = m_strExt;
808 size_t ind = strExt.find(prefix, 0);
811 strExt.erase(0, prefix.length());
813 strExt = strutils::trim_ws_begin(strExt);
814 projItem.SetFilter(strExt);
816 projItem.SetSubfolders(m_bRecurse);
817 if (!m_strUnpackerPipeline.empty())
818 projItem.SetUnpacker(m_strUnpackerPipeline);
819 project.Items().push_back(projItem);
821 if (!theApp.SaveProjectFile(fileName, project))
824 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
827 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
829 CRect rcButton, rcView;
830 GetDlgItem(nID)->GetWindowRect(&rcButton);
832 VERIFY(menu.LoadMenu(nPopupID));
833 theApp.TranslateMenu(menu.m_hMenu);
834 CMenu* pPopup = menu.GetSubMenu(0);
835 if (pPopup != nullptr)
837 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
841 for (int i = 0; i < 3; i++)
842 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
843 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
844 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
846 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
847 rcButton.left, rcButton.bottom, GetMainFrame());
852 template<UINT id, UINT popupid>
853 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
855 DropDown(pNMHDR, pResult, id, popupid);
859 * @brief Allow user to select a file to open/save.
861 String COpenView::AskProjectFileName(bool bOpen)
863 // get the default projects path
864 String strProjectFileName;
865 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
867 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
868 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
871 if (strProjectFileName.empty())
874 // get the path part from the filename
875 strProjectPath = paths::GetParentPath(strProjectFileName);
876 // store this as the new project path
877 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
878 return strProjectFileName;
882 * @brief Load File- and filter-combobox states.
884 void COpenView::LoadComboboxStates()
886 m_ctlPath[0].LoadState(_T("Files\\Left"));
887 m_ctlPath[1].LoadState(_T("Files\\Right"));
888 m_ctlPath[2].LoadState(_T("Files\\Option"));
889 m_ctlExt.LoadState(_T("Files\\Ext"));
890 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
894 * @brief Save File- and filter-combobox states.
896 void COpenView::SaveComboboxStates()
898 m_ctlPath[0].SaveState(_T("Files\\Left"));
899 m_ctlPath[1].SaveState(_T("Files\\Right"));
900 m_ctlPath[2].SaveState(_T("Files\\Option"));
901 m_ctlExt.SaveState(_T("Files\\Ext"));
902 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
905 struct UpdateButtonStatesThreadParams
911 static UINT UpdateButtonStatesThread(LPVOID lpParam)
916 CoInitialize(nullptr);
917 CAssureScriptsForThread scriptsForRescan;
919 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
923 if (msg.message != WM_USER + 2)
926 bool bIsaFolderCompare = true;
927 bool bIsaFileCompare = true;
928 bool bInvalid[3] = {false, false, false};
929 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
930 int iStatusMsgId = IDS_OPEN_FILESDIRS;
932 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
933 PathContext paths = pParams->m_paths;
934 HWND hWnd = pParams->m_hWnd;
937 // Check if we have project file as left side path
938 bool bProject = false;
940 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
941 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
946 for (int i = 0; i < paths.GetSize(); ++i)
948 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
949 if (pathType[i] == paths::DOES_NOT_EXIST)
954 // Enable buttons as appropriate
955 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
957 paths::PATH_EXISTENCE pathsType = pathType[0];
959 if (paths.GetSize() <= 2)
961 if (bInvalid[0] && bInvalid[1])
962 iStatusMsgId = IDS_OPEN_BOTHINVALID;
963 else if (bInvalid[0])
964 iStatusMsgId = IDS_OPEN_LEFTINVALID;
965 else if (bInvalid[1])
967 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
968 iStatusMsgId = IDS_OPEN_FILESDIRS;
970 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
972 else if (!bInvalid[0] && !bInvalid[1])
974 if (pathType[0] != pathType[1])
975 iStatusMsgId = IDS_OPEN_MISMATCH;
977 iStatusMsgId = IDS_OPEN_FILESDIRS;
982 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
983 iStatusMsgId = IDS_OPEN_ALLINVALID;
984 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
985 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
986 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
987 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
988 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
989 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
990 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
991 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
992 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
993 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
994 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
995 iStatusMsgId = IDS_OPEN_LEFTINVALID;
996 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
998 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
999 iStatusMsgId = IDS_OPEN_MISMATCH;
1001 iStatusMsgId = IDS_OPEN_FILESDIRS;
1004 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1005 pathsType = paths::DOES_NOT_EXIST;
1006 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1007 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1008 // Both will be `false` if incompatibilities or something is missing
1009 // Both will end up `true` if file validity isn't being checked
1012 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1021 * @brief Update any resources necessary after a GUI language change
1023 void COpenView::UpdateResources()
1025 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1029 * @brief Enable/disable components based on validity of paths.
1031 void COpenView::UpdateButtonStates()
1033 UpdateData(TRUE); // load member variables from screen
1034 KillTimer(IDT_CHECKFILES);
1037 if (m_pUpdateButtonStatusThread == nullptr)
1039 m_pUpdateButtonStatusThread = AfxBeginThread(
1040 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1041 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1042 m_pUpdateButtonStatusThread->ResumeThread();
1043 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1047 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1048 pParams->m_hWnd = this->m_hWnd;
1049 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1051 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1054 void COpenView::TerminateThreadIfRunning()
1056 if (m_pUpdateButtonStatusThread == nullptr)
1059 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1060 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1061 if (dwResult != WAIT_OBJECT_0)
1063 m_pUpdateButtonStatusThread->SuspendThread();
1064 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1066 delete m_pUpdateButtonStatusThread;
1067 m_pUpdateButtonStatusThread = nullptr;
1071 * @brief Called when user changes selection in left/middle/right path's combo box.
1073 void COpenView::OnSelchangePathCombo(UINT nId)
1075 const int index = nId - IDC_PATH0_COMBO;
1076 int sel = m_ctlPath[index].GetCurSel();
1080 m_ctlPath[index].GetLBText(sel, cstrPath);
1081 m_strPath[index] = cstrPath;
1082 m_ctlPath[index].SetWindowText(cstrPath);
1085 UpdateButtonStates();
1088 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1090 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1092 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1094 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1095 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1100 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1102 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1108 * @brief Called every time paths are edited.
1110 void COpenView::OnEditEvent(UINT nID)
1112 const int N = nID - IDC_PATH0_COMBO;
1113 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1115 int const len = edit->GetWindowTextLength();
1116 if (edit->GetSel() == MAKEWPARAM(len, len))
1119 edit->GetWindowText(text);
1120 // Remove any double quotes
1122 if (text.GetLength() != len)
1124 edit->SetSel(0, len);
1125 edit->ReplaceSel(text);
1129 // (Re)start timer to path validity check delay
1130 // If timer starting fails, update buttonstates immediately
1131 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1132 UpdateButtonStates();
1136 * @brief Handle timer events.
1137 * Checks if paths are valid and sets control states accordingly.
1138 * @param [in] nIDEvent Timer ID that fired.
1140 void COpenView::OnTimer(UINT_PTR nIDEvent)
1142 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1143 UpdateButtonStates();
1145 CFormView::OnTimer(nIDEvent);
1149 * @brief Called when users selects plugin browse button.
1151 void COpenView::OnSelectUnpacker()
1153 paths::PATH_EXISTENCE pathsType;
1157 for (auto& strPath: m_strPath)
1159 if (nFiles == 2 && strPath.empty())
1161 m_files.SetSize(nFiles + 1);
1162 m_files[nFiles] = strPath;
1165 pathsType = paths::GetPairComparability(m_files);
1167 if (pathsType != paths::IS_EXISTING_FILE)
1170 // let the user select a handler
1171 CSelectPluginDlg dlg(m_strUnpackerPipeline, m_files[0],
1172 CSelectPluginDlg::PluginType::Unpacker, false, this);
1173 if (dlg.DoModal() == IDOK)
1175 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1180 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1182 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1183 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1184 const bool bProject = HIWORD(lParam) != 0;
1185 const int iStatusMsgId = LOWORD(lParam);
1187 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1189 EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
1190 EnableDlgItem(IDC_UNPACKER_COMBO, bIsaFileCompare);
1191 EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
1193 EnableDlgItem(IDC_FILES_DIRS_GROUP3, bIsaFolderCompare);
1194 EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
1195 EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
1196 EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
1198 SetStatus(iStatusMsgId);
1200 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1202 if (m_retryCount == 0)
1203 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1208 KillTimer(IDT_RETRY);
1215 * @brief Sets the path status text.
1216 * The open dialog shows a status text of selected paths. This function
1217 * is used to set that status text.
1218 * @param [in] msgID Resource ID of status text to set.
1220 void COpenView::SetStatus(UINT msgID)
1222 String msg = theApp.LoadString(msgID);
1223 SetDlgItemText(IDC_OPEN_STATUS, msg);
1227 * @brief Called when "Select..." button for filters is selected.
1229 void COpenView::OnSelectFilter()
1231 String filterPrefix = _("[F] ");
1233 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1235 const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1236 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1237 curFilter = strutils::trim_ws(curFilter);
1239 GetMainFrame()->SelectFilter();
1241 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1242 if (pGlobalFileFilter->IsUsingMask())
1244 // If we had filter chosen and now has mask we can overwrite filter
1245 if (!bUseMask || curFilter[0] != '*')
1247 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1252 filterNameOrMask = filterPrefix + filterNameOrMask;
1253 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1257 void COpenView::OnOptions()
1259 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1262 void COpenView::OnDropDownOptions(NMHDR *pNMHDR, LRESULT *pResult)
1264 NMTOOLBAR dropDown = { 0 };
1265 dropDown.hdr.code = TBN_DROPDOWN;
1266 dropDown.hdr.hwndFrom = GetMainFrame()->GetDescendantWindow(AFX_IDW_TOOLBAR)->GetSafeHwnd();
1267 dropDown.hdr.idFrom = AFX_IDW_TOOLBAR;
1268 GetDlgItem(IDC_OPTIONS)->GetWindowRect(&dropDown.rcButton);
1269 GetMainFrame()->ScreenToClient(&dropDown.rcButton);
1270 GetMainFrame()->SendMessage(WM_NOTIFY, dropDown.hdr.idFrom, reinterpret_cast<LPARAM>(&dropDown));
1275 * @brief Removes whitespaces from left and right paths
1276 * @note Assumes UpdateData(TRUE) is called before this function.
1278 void COpenView::TrimPaths()
1280 for (auto& strPath: m_strPath)
1281 strPath = strutils::trim_ws(strPath);
1285 * @brief Update control states when dialog is activated.
1287 * Update control states when user re-activates dialog. User might have
1288 * switched for other program to e.g. update files/folders and then
1289 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1292 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1294 CFormView::OnActivate(nState, pWndOther, bMinimized);
1296 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1297 UpdateButtonStates();
1300 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1302 CWnd *pCtl = GetFocus();
1303 if (pCtl != nullptr)
1304 pCtl->PostMessage(msg, wParam, lParam);
1307 template <int MSG, int WPARAM, int LPARAM>
1308 void COpenView::OnEditAction()
1310 OnEditAction(MSG, WPARAM, LPARAM);
1314 * @brief Open help from mainframe when user presses F1.
1316 void COpenView::OnHelp()
1318 theApp.ShowHelp(OpenDlgHelpLocation);
1321 /////////////////////////////////////////////////////////////////////////////
1323 // OnDropFiles code from CDropEdit
1324 // Copyright 1997 Chris Losinger
1326 // shortcut expansion code modified from :
1327 // CShortcut, 1996 Rob Warner
1331 * @brief Drop paths(s) to the dialog.
1332 * One or two paths can be dropped to the dialog. The behaviour is:
1334 * - drop to empty path edit box (check left first)
1335 * - if both boxes have a path, drop to left path
1337 * - overwrite both paths, empty or not
1338 * @param [in] dropInfo Dropped data, including paths.
1340 void COpenView::OnDropFiles(const std::vector<String>& files)
1342 const size_t fileCount = files.size();
1344 // Add dropped paths to the dialog
1348 m_strPath[0] = files[0];
1349 m_strPath[1] = files[1];
1350 m_strPath[2] = files[2];
1352 UpdateButtonStates();
1354 else if (fileCount == 2)
1356 m_strPath[0] = files[0];
1357 m_strPath[1] = files[1];
1359 UpdateButtonStates();
1361 else if (fileCount == 1)
1364 GetCursorPos(&point);
1365 ScreenToClient(&point);
1366 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1367 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1369 switch (int const id = pwndHit->GetDlgCtrlID())
1371 case IDC_PATH0_COMBO:
1372 case IDC_PATH1_COMBO:
1373 case IDC_PATH2_COMBO:
1374 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1377 if (m_strPath[0].empty())
1378 m_strPath[0] = files[0];
1379 else if (m_strPath[1].empty())
1380 m_strPath[1] = files[0];
1381 else if (m_strPath[2].empty())
1382 m_strPath[2] = files[0];
1384 m_strPath[0] = files[0];
1389 UpdateButtonStates();