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"
36 #include "OptionsProject.h"
43 #define BCN_DROPDOWN (BCN_FIRST + 0x0002)
46 // Timer ID and timeout for delaying path validity check
47 const UINT IDT_CHECKFILES = 1;
48 const UINT IDT_RETRY = 2;
49 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
50 const int RETRY_MAX = 3;
51 static const TCHAR EMPTY_EXTENSION[] = _T(".*");
53 /** @brief Location for Open-dialog specific help to open. */
54 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
58 IMPLEMENT_DYNCREATE(COpenView, CFormView)
60 BEGIN_MESSAGE_MAP(COpenView, CFormView)
61 //{{AFX_MSG_MAP(COpenView)
62 ON_CONTROL_RANGE(BN_CLICKED, IDC_PATH0_BUTTON, IDC_PATH2_BUTTON, OnPathButton)
63 ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
64 ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
65 ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
66 ON_CONTROL_RANGE(CBN_SELCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSelchangePathCombo)
67 ON_CONTROL_RANGE(CBN_EDITCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnEditEvent)
68 ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
69 ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
70 ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
71 ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
72 ON_NOTIFY_RANGE(CBEN_BEGINEDIT, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSetfocusPathCombo)
73 ON_NOTIFY_RANGE(CBEN_DRAGBEGIN, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnDragBeginPathCombo)
75 ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
76 ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
77 ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, (OnDropDown<IDC_OPTIONS, IDR_POPUP_PROJECT_DIFF_OPTIONS>))
78 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
79 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
80 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
81 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
82 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
83 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
84 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
85 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
86 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnDiffIgnoreNumbers)
87 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnUpdateDiffIgnoreNumbers)
88 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
89 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
90 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
91 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
92 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
93 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
95 ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
96 ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
97 ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
98 ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
99 ON_COMMAND(IDOK, OnOK)
100 ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
101 ON_COMMAND(IDCANCEL, OnCancel)
102 ON_COMMAND(ID_HELP, OnHelp)
103 ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
104 ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
105 ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
106 ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
107 ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
108 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnCompare)
109 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateCompare)
110 ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
111 ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
112 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
116 ON_WM_WINDOWPOSCHANGING()
117 ON_WM_WINDOWPOSCHANGED()
123 // COpenView construction/destruction
125 COpenView::COpenView()
126 : CFormView(COpenView::IDD)
127 , m_pUpdateButtonStatusThread(nullptr)
129 , m_pDropHandler(nullptr)
131 , m_bAutoCompleteReady()
132 , m_bReadOnly {false, false, false}
133 , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
134 , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
137 , m_bIgnoreBlankLines(false)
138 , m_bIgnoreCase(false)
139 , m_bIgnoreEol(false)
140 , m_bIgnoreNumbers(false)
141 , m_bIgnoreCodepage(false)
142 , m_bFilterCommentsLines(false)
143 , m_nCompareMethod(0)
145 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
146 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
147 m_bInsideUpdate = TRUE;
150 COpenView::~COpenView()
152 TerminateThreadIfRunning();
155 void COpenView::DoDataExchange(CDataExchange* pDX)
157 CFormView::DoDataExchange(pDX);
158 //{{AFX_DATA_MAP(COpenView)
159 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
160 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
161 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
162 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
163 DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
164 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
165 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
166 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
167 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
168 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
169 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
170 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
171 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
172 DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
176 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
178 // TODO: Modify the Window class or styles here by modifying
179 // the CREATESTRUCT cs
180 cs.style &= ~WS_BORDER;
181 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
182 return CFormView::PreCreateWindow(cs);
185 void COpenView::OnInitialUpdate()
187 if (!IsVista_OrGreater())
190 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
191 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
192 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
195 m_sizeOrig = GetTotalSize();
197 theApp.TranslateDialog(m_hWnd);
199 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
201 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
202 m_image.Create(1, 1, 24, 0);
205 CFormView::OnInitialUpdate();
207 // set caption to "swap paths" button
209 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
210 lf.lfCharSet = SYMBOL_CHARSET;
211 lstrcpy(lf.lfFaceName, _T("Wingdings"));
212 m_fontSwapButton.CreateFontIndirect(&lf);
213 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
214 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
216 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
217 SetDlgItemText(ids[i], _T("\xf4"));
220 m_constraint.InitializeCurrentSize(this);
221 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
222 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
223 m_constraint.SetScrollScale(this, 1.0, 1.0);
224 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
225 m_constraint.DisallowHeightGrowth();
226 //m_constraint.SubclassWnd(); // install subclassing
228 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
229 m_constraint.UpdateSizes();
231 COpenDoc *pDoc = GetDocument();
234 GetWindowText(strTitle);
235 pDoc->SetTitle(strTitle);
237 m_files = pDoc->m_files;
238 m_bRecurse = pDoc->m_bRecurse;
239 m_strExt = pDoc->m_strExt;
240 m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
241 m_dwFlags[0] = pDoc->m_dwFlags[0];
242 m_dwFlags[1] = pDoc->m_dwFlags[1];
243 m_dwFlags[2] = pDoc->m_dwFlags[2];
245 m_ctlPath[0].SetFileControlStates();
246 m_ctlPath[1].SetFileControlStates(true);
247 m_ctlPath[2].SetFileControlStates(true);
248 m_ctlUnpackerPipeline.SetFileControlStates(true);
250 for (int file = 0; file < m_files.GetSize(); file++)
252 m_strPath[file] = m_files[file];
253 m_ctlPath[file].SetWindowText(m_files[file].c_str());
254 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
257 m_ctlPath[0].AttachSystemImageList();
258 m_ctlPath[1].AttachSystemImageList();
259 m_ctlPath[2].AttachSystemImageList();
260 LoadComboboxStates();
262 m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
264 bool bDoUpdateData = true;
265 for (auto& strPath: m_strPath)
267 if (!strPath.empty())
268 bDoUpdateData = false;
270 UpdateData(bDoUpdateData);
272 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
273 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
274 bool bMask = pGlobalFileFilter->IsUsingMask();
278 String filterPrefix = _("[F] ");
279 filterNameOrMask = filterPrefix + filterNameOrMask;
282 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
284 m_ctlExt.SetCurSel(ind);
287 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
289 m_ctlExt.SetCurSel(ind);
291 LogErrorString(_T("Failed to add string to filters combo list!"));
294 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
296 EnableDlgItem(IDOK, true);
297 EnableDlgItem(IDC_UNPACKER_COMBO, true);
298 EnableDlgItem(IDC_SELECT_UNPACKER, true);
301 UpdateButtonStates();
303 bool bOverwriteRecursive = false;
304 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
305 bOverwriteRecursive = true;
306 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
307 bOverwriteRecursive = true;
308 if (!bOverwriteRecursive)
309 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
311 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
312 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
313 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
314 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
315 m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
316 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
317 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
318 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
321 SetStatus(IDS_OPEN_FILESDIRS);
323 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
324 RegisterDragDrop(m_hWnd, m_pDropHandler);
327 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
329 m_bRecurse = GetDocument()->m_bRecurse;
331 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
332 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
333 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
334 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
335 m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
336 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
337 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
338 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
343 // COpenView diagnostics
346 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
348 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
349 return (COpenDoc*)m_pDocument;
353 /////////////////////////////////////////////////////////////////////////////
354 // COpenView message handlers
356 void COpenView::OnPaint()
362 // Draw the logo image
363 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
364 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
365 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
366 // And extend it to the Right boundary
367 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
369 // Draw the resize gripper in the Lower Right corner.
371 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
372 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
373 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
375 // Draw a line to separate the Status Line
376 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
377 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
380 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
381 ScreenToClient(&rcStatus);
382 dc.MoveTo(0, rcStatus.top - 3);
383 dc.LineTo(rc.right, rcStatus.top - 3);
384 dc.SelectObject(oldpen);
386 CFormView::OnPaint();
389 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
391 if (::GetCapture() == m_hWnd)
393 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
394 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
396 switch (int const id1 = pwndHit->GetDlgCtrlID())
398 case IDC_PATH0_COMBO:
399 case IDC_PATH1_COMBO:
400 case IDC_PATH2_COMBO:
402 CWnd *pwndChild = GetFocus();
403 if (pwndChild && IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
405 id2 = pwndChild->GetDlgCtrlID();
406 pwndChild = pwndChild->GetParent();
407 } while (pwndChild != this);
410 case IDC_PATH0_COMBO:
411 case IDC_PATH1_COMBO:
412 case IDC_PATH2_COMBO:
414 GetDlgItemText(id1, s1);
415 GetDlgItemText(id2, s2);
416 SetDlgItemText(id1, s2);
417 SetDlgItemText(id2, s1);
428 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
430 if (::GetCapture() == m_hWnd)
432 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
433 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
435 switch (pwndHit->GetDlgCtrlID())
437 case IDC_PATH0_COMBO:
438 case IDC_PATH1_COMBO:
439 case IDC_PATH2_COMBO:
440 if (!pwndHit->IsChild(GetFocus()))
442 SetCursor(m_hIconRotate);
447 SetCursor(m_hCursorNo);
454 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
456 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
458 CFrameWnd *const pFrameWnd = GetParentFrame();
459 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
462 pFrameWnd->GetClientRect(&rc);
463 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
464 lpwndpos->cy = m_sizeOrig.cy;
465 if (lpwndpos->flags & SWP_NOOWNERZORDER)
467 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
468 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
469 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
473 else if (pFrameWnd->IsZoomed())
475 lpwndpos->cx = m_totalLog.cx;
476 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
480 if (lpwndpos->cx > rc.Width())
481 lpwndpos->cx = rc.Width();
482 if (lpwndpos->cx < m_sizeOrig.cx)
483 lpwndpos->cx = m_sizeOrig.cx;
484 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
491 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
493 if (lpwndpos->flags & SWP_FRAMECHANGED)
495 m_constraint.UpdateSizes();
496 CFrameWnd *const pFrameWnd = GetParentFrame();
497 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
499 m_constraint.Persist(true, false);
500 WINDOWPLACEMENT wp = {};
501 wp.length = sizeof wp;
502 pFrameWnd->GetWindowPlacement(&wp);
505 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
506 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
507 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
508 pFrameWnd->SetWindowPlacement(&wp);
511 CFormView::OnWindowPosChanged(lpwndpos);
514 void COpenView::OnDestroy()
516 if (m_pDropHandler != nullptr)
517 RevokeDragDrop(m_hWnd);
519 CFormView::OnDestroy();
522 LRESULT COpenView::OnNcHitTest(CPoint point)
524 if (GetParentFrame()->IsZoomed())
528 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
529 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
530 if (PtInRect(&rc, point))
533 return CFormView::OnNcHitTest(point);
537 * @brief Called when "Browse..." button is selected for N path.
539 void COpenView::OnPathButton(UINT nId)
541 const int index = nId - IDC_PATH0_BUTTON;
546 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
549 case paths::IS_EXISTING_DIR:
550 sfolder = m_strPath[index];
552 case paths::IS_EXISTING_FILE:
553 sfolder = paths::GetPathOnly(m_strPath[index]);
555 case paths::DOES_NOT_EXIST:
556 if (!m_strPath[index].empty())
557 sfolder = paths::GetParentPath(m_strPath[index]);
560 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
564 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
566 m_strPath[index] = s;
567 m_strBrowsePath[index] = std::move(s);
569 UpdateButtonStates();
573 void COpenView::OnSwapButton(int id1, int id2)
576 GetDlgItemText(id1, s1);
577 GetDlgItemText(id2, s2);
579 SetDlgItemText(id1, s1);
580 SetDlgItemText(id2, s2);
583 template<int id1, int id2>
584 void COpenView::OnSwapButton()
586 OnSwapButton(id1, id2);
589 void COpenView::OnCompare(UINT nID)
591 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
592 const String filterPrefix = _("[F] ");
593 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
599 for (auto& strPath : m_strPath)
601 if (nFiles >= 1 && strPath.empty())
603 m_files.SetSize(nFiles + 1);
604 m_files[nFiles] = strPath;
605 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
606 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
609 // If left path is a project-file, load it
611 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
614 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
616 theApp.LoadAndOpenProjectFile(m_strPath[0]);
618 else if (!paths::IsDirectory(m_strPath[0]))
620 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
621 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
622 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
623 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo);
628 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
630 if (pathsType == paths::DOES_NOT_EXIST &&
631 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); }))
633 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
637 for (int index = 0; index < nFiles; index++)
639 // If user has edited path by hand, expand environment variables
640 bool bExpand = false;
641 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
644 if (!paths::IsURLorCLSID(m_files[index]))
646 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
648 // Add trailing '\' for directories if its missing
649 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR && !IsArchiveFile(m_files[index]))
650 m_files[index] = paths::AddTrailingSlash(m_files[index]);
651 m_strPath[index] = m_files[index];
656 KillTimer(IDT_CHECKFILES);
657 KillTimer(IDT_RETRY);
659 String filter(strutils::trim_ws(m_strExt));
661 // If prefix found from start..
662 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
664 // Remove prefix + space
665 filter.erase(0, filterPrefix.length());
666 if (!pGlobalFileFilter->SetFilter(filter))
668 // If filtername is not found use default *.* mask
669 pGlobalFileFilter->SetFilter(_T("*.*"));
672 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
676 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
678 m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
679 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
682 SaveComboboxStates();
683 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
684 LoadComboboxStates();
686 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
687 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
688 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
689 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
690 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_NUMBERS, m_bIgnoreNumbers);
691 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
692 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
693 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
695 m_constraint.Persist(true, false);
697 COpenDoc *pDoc = GetDocument();
698 pDoc->m_files = m_files;
699 pDoc->m_bRecurse = m_bRecurse;
700 pDoc->m_strExt = m_strExt;
701 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
702 pDoc->m_dwFlags[0] = m_dwFlags[0];
703 pDoc->m_dwFlags[1] = m_dwFlags[1];
704 pDoc->m_dwFlags[2] = m_dwFlags[2];
706 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
707 GetParentFrame()->PostMessage(WM_CLOSE);
709 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
710 PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
711 PathContext tmpPathContext(pDoc->m_files);
712 std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
713 bool recurse = pDoc->m_bRecurse;
714 std::unique_ptr<CMainFrame::OpenFolderParams> pOpenFolderParams;
715 if (!pDoc->m_hiddenItems.empty())
717 pOpenFolderParams = std::make_unique<CMainFrame::OpenFolderParams>();
718 pOpenFolderParams->m_hiddenItems = pDoc->m_hiddenItems;
722 GetMainFrame()->DoFileOrFolderOpen(
723 &tmpPathContext, dwFlags.data(),
724 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr, 0, pOpenFolderParams.get());
726 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
728 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
729 GetMainFrame()->DoFileOrFolderOpen(
730 &tmpPathContext, dwFlags.data(),
731 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr, 0, pOpenFolderParams.get());
733 else if (nID == ID_OPEN_WITH_UNPACKER)
735 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0],
736 CSelectPluginDlg::PluginType::Unpacker, false, this);
737 if (dlg.DoModal() == IDOK)
739 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
740 GetMainFrame()->DoFileOrFolderOpen(
741 &tmpPathContext, dwFlags.data(),
742 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr, 0, pOpenFolderParams.get());
747 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo, nullptr, pOpenFolderParams.get());
751 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
753 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
757 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
758 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
759 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
762 pCmdUI->Enable(bFile);
766 * @brief Called when dialog is closed with "OK".
768 * Checks that paths are valid and sets filters.
770 void COpenView::OnOK()
776 * @brief Called when dialog is closed via Cancel.
778 * Open-dialog is closed when `Cancel` button is selected or the
779 * `Esc` key is pressed. Save combobox states, since user may have
780 * removed items from them (with `shift-del`) and doesn't want them
782 * This is *not* called when the program is terminated, even if the
783 * dialog is visible at the time.
785 void COpenView::OnCancel()
787 SaveComboboxStates();
788 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
792 * @brief Called when Open-button for project file is selected.
794 void COpenView::OnLoadProject()
798 String fileName = AskProjectFileName(true);
799 if (fileName.empty())
803 if (!theApp.LoadProjectFile(fileName, project))
805 if (project.Items().size() == 0)
808 ProjectFileItem& projItem = *project.Items().begin();
809 bool bRecurse = m_bRecurse;
810 projItem.GetPaths(paths, bRecurse);
811 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::IncludeSubfolders))
812 m_bRecurse = bRecurse;
813 if (paths.GetSize() < 3)
815 m_strPath[0] = paths[0];
816 m_strPath[1] = paths[1];
817 m_strPath[2].clear();
818 m_bReadOnly[0] = projItem.GetLeftReadOnly();
819 m_bReadOnly[1] = projItem.GetRightReadOnly();
820 m_bReadOnly[2] = false;
824 m_strPath[0] = paths[0];
825 m_strPath[1] = paths[1];
826 m_strPath[2] = paths[2];
827 m_bReadOnly[0] = projItem.GetLeftReadOnly();
828 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
829 m_bReadOnly[2] = projItem.GetRightReadOnly();
831 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::FileFilter) && projItem.HasFilter())
832 m_strExt = projItem.GetFilter();
833 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::UnpackerPlugin) && projItem.HasUnpacker())
834 m_strUnpackerPipeline = projItem.GetUnpacker();
836 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::CompareOptions))
838 if (projItem.HasIgnoreWhite())
839 m_nIgnoreWhite = projItem.GetIgnoreWhite();
840 if (projItem.HasIgnoreBlankLines())
841 m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
842 if (projItem.HasIgnoreCase())
843 m_bIgnoreCase = projItem.GetIgnoreCase();
844 if (projItem.HasIgnoreEol())
845 m_bIgnoreEol = projItem.GetIgnoreEol();
846 if (projItem.HasIgnoreNumbers())
847 m_bIgnoreNumbers = projItem.GetIgnoreNumbers();
848 if (projItem.HasIgnoreCodepage())
849 m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
850 if (projItem.HasFilterCommentsLines())
851 m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
852 if (projItem.HasCompareMethod())
853 m_nCompareMethod = projItem.GetCompareMethod();
856 if ((Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::HiddenItems)) && projItem.HasHiddenItems())
858 GetDocument()->m_hiddenItems = projItem.GetHiddenItems();
861 UpdateButtonStates();
862 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
866 * @brief Called when Save-button for project file is selected.
868 void COpenView::OnSaveProject()
872 String fileName = AskProjectFileName(false);
873 if (fileName.empty())
877 ProjectFileItem projItem;
879 bool bSaveFileFilter = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::FileFilter);
880 bool bSaveIncludeSubfolders = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::IncludeSubfolders);
881 bool bSaveUnpackerPlugin = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::UnpackerPlugin);
882 bool bSaveCompareOptions = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::CompareOptions);
883 bool bSaveHiddenItems = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::HiddenItems);
885 projItem.SetSaveFilter(bSaveFileFilter);
886 projItem.SetSaveSubfolders(bSaveIncludeSubfolders);
887 projItem.SetSaveUnpacker(bSaveUnpackerPlugin);
888 projItem.SetSaveIgnoreWhite(bSaveCompareOptions);
889 projItem.SetSaveIgnoreBlankLines(bSaveCompareOptions);
890 projItem.SetSaveIgnoreCase(bSaveCompareOptions);
891 projItem.SetSaveIgnoreEol(bSaveCompareOptions);
892 projItem.SetSaveIgnoreNumbers(bSaveCompareOptions);
893 projItem.SetSaveIgnoreCodepage(bSaveCompareOptions);
894 projItem.SetSaveFilterCommentsLines(bSaveCompareOptions);
895 projItem.SetSaveCompareMethod(bSaveCompareOptions);
896 projItem.SetSaveHiddenItems(bSaveHiddenItems);
898 if (!m_strPath[0].empty())
899 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
900 if (m_strPath[2].empty())
902 if (!m_strPath[1].empty())
903 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
907 if (!m_strPath[1].empty())
908 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
909 if (!m_strPath[2].empty())
910 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
912 if (bSaveFileFilter && !m_strExt.empty())
914 // Remove possbile prefix from the filter name
915 String prefix = _("[F] ");
916 String strExt = m_strExt;
917 size_t ind = strExt.find(prefix, 0);
920 strExt.erase(0, prefix.length());
922 strExt = strutils::trim_ws_begin(strExt);
923 projItem.SetFilter(strExt);
925 if (bSaveIncludeSubfolders)
926 projItem.SetSubfolders(m_bRecurse);
927 if (bSaveUnpackerPlugin && !m_strUnpackerPipeline.empty())
928 projItem.SetUnpacker(m_strUnpackerPipeline);
930 if (bSaveCompareOptions)
932 projItem.SetIgnoreWhite(m_nIgnoreWhite);
933 projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
934 projItem.SetIgnoreCase(m_bIgnoreCase);
935 projItem.SetIgnoreEol(m_bIgnoreEol);
936 projItem.SetIgnoreNumbers(m_bIgnoreNumbers);
937 projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
938 projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
939 projItem.SetCompareMethod(m_nCompareMethod);
942 if (bSaveHiddenItems)
943 projItem.SetHiddenItems(GetDocument()->m_hiddenItems);
945 project.Items().push_back(projItem);
947 if (!theApp.SaveProjectFile(fileName, project))
950 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
953 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
956 GetDlgItem(nID)->GetWindowRect(&rcButton);
958 VERIFY(menu.LoadMenu(nPopupID));
959 theApp.TranslateMenu(menu.m_hMenu);
960 CMenu* pPopup = menu.GetSubMenu(0);
961 if (pPopup != nullptr)
963 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
967 for (int i = 0; i < 3; i++)
968 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
969 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
970 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
972 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
973 rcButton.left, rcButton.bottom, GetMainFrame());
978 template<UINT id, UINT popupid>
979 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
981 DropDown(pNMHDR, pResult, id, popupid);
985 * @brief Allow user to select a file to open/save.
987 String COpenView::AskProjectFileName(bool bOpen)
989 // get the default projects path
990 String strProjectFileName;
991 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
993 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
994 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
997 if (strProjectFileName.empty())
1000 // get the path part from the filename
1001 strProjectPath = paths::GetParentPath(strProjectFileName);
1002 // store this as the new project path
1003 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
1004 return strProjectFileName;
1008 * @brief Load File- and filter-combobox states.
1010 void COpenView::LoadComboboxStates()
1012 m_ctlPath[0].LoadState(_T("Files\\Left"));
1013 m_ctlPath[1].LoadState(_T("Files\\Right"));
1014 m_ctlPath[2].LoadState(_T("Files\\Option"));
1015 m_ctlExt.LoadState(_T("Files\\Ext"));
1016 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
1020 * @brief Save File- and filter-combobox states.
1022 void COpenView::SaveComboboxStates()
1024 m_ctlPath[0].SaveState(_T("Files\\Left"));
1025 m_ctlPath[1].SaveState(_T("Files\\Right"));
1026 m_ctlPath[2].SaveState(_T("Files\\Option"));
1027 m_ctlExt.SaveState(_T("Files\\Ext"));
1028 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
1031 struct UpdateButtonStatesThreadParams
1034 PathContext m_paths;
1037 static UINT UpdateButtonStatesThread(LPVOID lpParam)
1042 CoInitialize(nullptr);
1043 CAssureScriptsForThread scriptsForRescan;
1045 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
1049 if (msg.message != WM_USER + 2)
1052 bool bIsaFolderCompare = true;
1053 bool bIsaFileCompare = true;
1054 bool bInvalid[3] = {false, false, false};
1055 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
1056 int iStatusMsgId = IDS_OPEN_FILESDIRS;
1058 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
1059 PathContext paths = pParams->m_paths;
1060 HWND hWnd = pParams->m_hWnd;
1063 // Check if we have project file as left side path
1064 bool bProject = false;
1066 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
1067 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1072 for (int i = 0; i < paths.GetSize(); ++i)
1074 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
1075 if (pathType[i] == paths::DOES_NOT_EXIST)
1077 if (paths::IsURL(paths[i]))
1078 pathType[i] = paths::IS_EXISTING_FILE;
1085 // Enable buttons as appropriate
1086 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1088 paths::PATH_EXISTENCE pathsType = pathType[0];
1090 if (paths.GetSize() <= 2)
1092 if (bInvalid[0] && bInvalid[1])
1093 iStatusMsgId = IDS_OPEN_BOTHINVALID;
1094 else if (bInvalid[0])
1095 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1096 else if (bInvalid[1])
1098 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
1099 iStatusMsgId = IDS_OPEN_FILESDIRS;
1101 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
1103 else if (!bInvalid[0] && !bInvalid[1])
1105 if (pathType[0] != pathType[1])
1106 iStatusMsgId = IDS_OPEN_MISMATCH;
1108 iStatusMsgId = IDS_OPEN_FILESDIRS;
1113 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
1114 iStatusMsgId = IDS_OPEN_ALLINVALID;
1115 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
1116 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
1117 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
1118 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
1119 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
1120 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
1121 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
1122 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
1123 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
1124 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
1125 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1126 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1127 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1129 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
1130 iStatusMsgId = IDS_OPEN_MISMATCH;
1132 iStatusMsgId = IDS_OPEN_FILESDIRS;
1135 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1136 pathsType = paths::DOES_NOT_EXIST;
1137 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1138 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1139 // Both will be `false` if incompatibilities or something is missing
1140 // Both will end up `true` if file validity isn't being checked
1143 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1152 * @brief Update any resources necessary after a GUI language change
1154 void COpenView::UpdateResources()
1156 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1160 * @brief Enable/disable components based on validity of paths.
1162 void COpenView::UpdateButtonStates()
1164 UpdateData(TRUE); // load member variables from screen
1165 KillTimer(IDT_CHECKFILES);
1168 if (m_pUpdateButtonStatusThread == nullptr)
1170 m_pUpdateButtonStatusThread = AfxBeginThread(
1171 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1172 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1173 m_pUpdateButtonStatusThread->ResumeThread();
1174 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1178 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1179 pParams->m_hWnd = this->m_hWnd;
1180 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1182 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1185 void COpenView::TerminateThreadIfRunning()
1187 if (m_pUpdateButtonStatusThread == nullptr)
1190 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1191 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1192 if (dwResult != WAIT_OBJECT_0)
1194 m_pUpdateButtonStatusThread->SuspendThread();
1195 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1197 delete m_pUpdateButtonStatusThread;
1198 m_pUpdateButtonStatusThread = nullptr;
1202 * @brief Called when user changes selection in left/middle/right path's combo box.
1204 void COpenView::OnSelchangePathCombo(UINT nId)
1206 const int index = nId - IDC_PATH0_COMBO;
1207 int sel = m_ctlPath[index].GetCurSel();
1211 m_ctlPath[index].GetLBText(sel, cstrPath);
1212 m_strPath[index] = cstrPath;
1213 m_ctlPath[index].SetWindowText(cstrPath);
1216 UpdateButtonStates();
1219 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1221 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1223 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1225 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1226 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1231 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1233 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1239 * @brief Called every time paths are edited.
1241 void COpenView::OnEditEvent(UINT nID)
1243 const int N = nID - IDC_PATH0_COMBO;
1244 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1246 int const len = edit->GetWindowTextLength();
1247 if (edit->GetSel() == MAKEWPARAM(len, len))
1250 edit->GetWindowText(text);
1251 // Remove any double quotes
1253 if (text.GetLength() != len)
1255 edit->SetSel(0, len);
1256 edit->ReplaceSel(text);
1260 // (Re)start timer to path validity check delay
1261 // If timer starting fails, update buttonstates immediately
1262 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1263 UpdateButtonStates();
1267 * @brief Handle timer events.
1268 * Checks if paths are valid and sets control states accordingly.
1269 * @param [in] nIDEvent Timer ID that fired.
1271 void COpenView::OnTimer(UINT_PTR nIDEvent)
1273 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1274 UpdateButtonStates();
1276 CFormView::OnTimer(nIDEvent);
1280 * @brief Called when users selects plugin browse button.
1282 void COpenView::OnSelectUnpacker()
1284 paths::PATH_EXISTENCE pathsType;
1288 for (auto& strPath: m_strPath)
1290 if (nFiles == 2 && strPath.empty())
1292 m_files.SetSize(nFiles + 1);
1293 m_files[nFiles] = strPath;
1296 PathContext tmpFiles = m_files;
1297 if (tmpFiles.GetSize() == 2 && tmpFiles[1].empty())
1298 tmpFiles[1] = tmpFiles[0];
1299 pathsType = paths::GetPairComparability(tmpFiles, IsArchiveFile);
1301 if (pathsType == paths::IS_EXISTING_DIR || (pathsType == paths::DOES_NOT_EXIST &&
1302 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); })))
1305 // let the user select a handler
1306 CSelectPluginDlg dlg(m_strUnpackerPipeline, m_files[0],
1307 CSelectPluginDlg::PluginType::Unpacker, false, this);
1308 if (dlg.DoModal() == IDOK)
1310 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1315 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1317 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1318 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1319 const bool bProject = HIWORD(lParam) != 0;
1320 const int iStatusMsgId = LOWORD(lParam);
1322 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1324 EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
1325 EnableDlgItem(IDC_UNPACKER_COMBO, bIsaFileCompare);
1326 EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
1328 EnableDlgItem(IDC_FILES_DIRS_GROUP3, bIsaFolderCompare);
1329 EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
1330 EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
1331 EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
1333 SetStatus(iStatusMsgId);
1335 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1337 if (m_retryCount == 0)
1338 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1343 KillTimer(IDT_RETRY);
1350 * @brief Sets the path status text.
1351 * The open dialog shows a status text of selected paths. This function
1352 * is used to set that status text.
1353 * @param [in] msgID Resource ID of status text to set.
1355 void COpenView::SetStatus(UINT msgID)
1357 String msg = theApp.LoadString(msgID);
1358 SetDlgItemText(IDC_OPEN_STATUS, msg);
1362 * @brief Called when "Select..." button for filters is selected.
1364 void COpenView::OnSelectFilter()
1367 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1369 const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1370 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1371 curFilter = strutils::trim_ws(curFilter);
1373 GetMainFrame()->SelectFilter();
1375 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1376 if (pGlobalFileFilter->IsUsingMask())
1378 // If we had filter chosen and now has mask we can overwrite filter
1379 if (!bUseMask || curFilter[0] != '*')
1381 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1386 String filterPrefix = _("[F] ");
1387 filterNameOrMask = filterPrefix + filterNameOrMask;
1388 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1392 void COpenView::OnOptions()
1394 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1398 * @brief Set "Whitespaces" setting.
1399 * @param [in] nID Menu ID of the selected item
1401 void COpenView::OnDiffWhitespace(UINT nID)
1403 assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
1405 m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
1409 * @brief Update "Whitespaces" state.
1410 * @param [in] pCmdUI UI component to update.
1412 void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
1414 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
1418 * @brief Toggle "Ignore blank lines" setting.
1420 void COpenView::OnDiffIgnoreBlankLines()
1422 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1426 * @brief Update "Ignore blank lines" state.
1427 * @param [in] pCmdUI UI component to update.
1429 void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
1431 pCmdUI->SetCheck(m_bIgnoreBlankLines);
1435 * @brief Toggle "Ignore case" setting.
1437 void COpenView::OnDiffIgnoreCase()
1439 m_bIgnoreCase = !m_bIgnoreCase;
1443 * @brief Update "Ignore case" state.
1444 * @param [in] pCmdUI UI component to update.
1446 void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
1448 pCmdUI->SetCheck(m_bIgnoreCase);
1452 * @brief Toggle "Ignore carriage return differences" setting.
1454 void COpenView::OnDiffIgnoreEOL()
1456 m_bIgnoreEol = !m_bIgnoreEol;
1460 * @brief Update "Ignore carriage return differences" state.
1461 * @param [in] pCmdUI UI component to update.
1463 void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
1465 pCmdUI->SetCheck(m_bIgnoreEol);
1469 * @brief Toggle "Ignore numbers" setting.
1471 void COpenView::OnDiffIgnoreNumbers()
1473 m_bIgnoreNumbers = !m_bIgnoreNumbers;
1477 * @brief Update "Ignore numbers" state.
1478 * @param [in] pCmdUI UI component to update.
1480 void COpenView::OnUpdateDiffIgnoreNumbers(CCmdUI* pCmdUI)
1482 pCmdUI->SetCheck(m_bIgnoreNumbers);
1486 * @brief Toggle "Ignore codepage differences" setting.
1488 void COpenView::OnDiffIgnoreCP()
1490 m_bIgnoreCodepage = !m_bIgnoreCodepage;
1494 * @brief Update "Ignore codepage differences" state.
1495 * @param [in] pCmdUI UI component to update.
1497 void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
1499 pCmdUI->SetCheck(m_bIgnoreCodepage);
1503 * @brief Toggle "Ignore comment differences" setting.
1505 void COpenView::OnDiffIgnoreComments()
1507 m_bFilterCommentsLines = !m_bFilterCommentsLines;
1511 * @brief Update "Ignore comment differences" state.
1512 * @param [in] pCmdUI UI component to update.
1514 void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
1516 pCmdUI->SetCheck(m_bFilterCommentsLines);
1520 * @brief Set "Compare method" setting.
1521 * @param [in] nID Menu ID of the selected item
1523 void COpenView::OnCompareMethod(UINT nID)
1525 assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
1527 m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
1531 * @brief Update "Compare method" state.
1532 * @param [in] pCmdUI UI component to update.
1534 void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
1536 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
1540 * @brief Removes whitespaces from left and right paths
1541 * @note Assumes UpdateData(TRUE) is called before this function.
1543 void COpenView::TrimPaths()
1545 for (auto& strPath: m_strPath)
1546 strPath = strutils::trim_ws(strPath);
1550 * @brief Update control states when dialog is activated.
1552 * Update control states when user re-activates dialog. User might have
1553 * switched for other program to e.g. update files/folders and then
1554 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1557 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1559 CFormView::OnActivate(nState, pWndOther, bMinimized);
1561 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1562 UpdateButtonStates();
1565 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1567 CWnd *pCtl = GetFocus();
1568 if (pCtl != nullptr)
1569 pCtl->PostMessage(msg, wParam, lParam);
1572 template <int MSG, int WPARAM, int LPARAM>
1573 void COpenView::OnEditAction()
1575 OnEditAction(MSG, WPARAM, LPARAM);
1579 * @brief Open help from mainframe when user presses F1.
1581 void COpenView::OnHelp()
1583 theApp.ShowHelp(OpenDlgHelpLocation);
1586 /////////////////////////////////////////////////////////////////////////////
1588 // OnDropFiles code from CDropEdit
1589 // Copyright 1997 Chris Losinger
1591 // shortcut expansion code modified from :
1592 // CShortcut, 1996 Rob Warner
1596 * @brief Drop paths(s) to the dialog.
1597 * One or two paths can be dropped to the dialog. The behaviour is:
1599 * - drop to empty path edit box (check left first)
1600 * - if both boxes have a path, drop to left path
1602 * - overwrite both paths, empty or not
1603 * @param [in] dropInfo Dropped data, including paths.
1605 void COpenView::OnDropFiles(const std::vector<String>& files)
1607 const size_t fileCount = files.size();
1609 // Add dropped paths to the dialog
1613 m_strPath[0] = files[0];
1614 m_strPath[1] = files[1];
1615 m_strPath[2] = files[2];
1617 UpdateButtonStates();
1619 else if (fileCount == 2)
1621 m_strPath[0] = files[0];
1622 m_strPath[1] = files[1];
1624 UpdateButtonStates();
1626 else if (fileCount == 1)
1629 GetCursorPos(&point);
1630 ScreenToClient(&point);
1631 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1632 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1634 switch (int const id = pwndHit->GetDlgCtrlID())
1636 case IDC_PATH0_COMBO:
1637 case IDC_PATH1_COMBO:
1638 case IDC_PATH2_COMBO:
1639 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1642 if (m_strPath[0].empty())
1643 m_strPath[0] = files[0];
1644 else if (m_strPath[1].empty())
1645 m_strPath[1] = files[0];
1646 else if (m_strPath[2].empty())
1647 m_strPath[2] = files[0];
1649 m_strPath[0] = files[0];
1654 UpdateButtonStates();