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, (OnDropDown<IDC_OPTIONS, IDR_POPUP_PROJECT_DIFF_OPTIONS>))
77 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
78 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
79 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
80 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
81 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
82 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
83 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
84 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
85 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
86 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
87 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
88 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
89 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
90 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
92 ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
93 ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
94 ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
95 ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
96 ON_COMMAND(IDOK, OnOK)
97 ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
98 ON_COMMAND(IDCANCEL, OnCancel)
99 ON_COMMAND(ID_HELP, OnHelp)
100 ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
101 ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
102 ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
103 ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
104 ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
105 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, OnCompare)
106 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, OnUpdateCompare)
107 ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
108 ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
109 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
113 ON_WM_WINDOWPOSCHANGING()
114 ON_WM_WINDOWPOSCHANGED()
120 // COpenView construction/destruction
122 COpenView::COpenView()
123 : CFormView(COpenView::IDD)
124 , m_pUpdateButtonStatusThread(nullptr)
126 , m_pDropHandler(nullptr)
128 , m_bAutoCompleteReady()
129 , m_bReadOnly {false, false, false}
130 , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
131 , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
134 , m_bIgnoreBlankLines(false)
135 , m_bIgnoreCase(false)
136 , m_bIgnoreEol(false)
137 , m_bIgnoreCodepage(false)
138 , m_bFilterCommentsLines(false)
139 , m_nCompareMethod(0)
141 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
142 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
143 m_bInsideUpdate = TRUE;
146 COpenView::~COpenView()
148 TerminateThreadIfRunning();
151 void COpenView::DoDataExchange(CDataExchange* pDX)
153 CFormView::DoDataExchange(pDX);
154 //{{AFX_DATA_MAP(COpenView)
155 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
156 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
157 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
158 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
159 DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
160 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
161 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
162 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
163 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
164 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
165 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
166 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
167 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
168 DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
172 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
174 // TODO: Modify the Window class or styles here by modifying
175 // the CREATESTRUCT cs
176 cs.style &= ~WS_BORDER;
177 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
178 return CFormView::PreCreateWindow(cs);
181 void COpenView::OnInitialUpdate()
183 if (!IsVista_OrGreater())
186 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
187 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
188 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
191 m_sizeOrig = GetTotalSize();
193 theApp.TranslateDialog(m_hWnd);
195 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
197 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
198 m_image.Create(1, 1, 24, 0);
201 CFormView::OnInitialUpdate();
203 // set caption to "swap paths" button
205 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
206 lf.lfCharSet = SYMBOL_CHARSET;
207 lstrcpy(lf.lfFaceName, _T("Wingdings"));
208 m_fontSwapButton.CreateFontIndirect(&lf);
209 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
210 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
212 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
213 SetDlgItemText(ids[i], _T("\xf4"));
216 m_constraint.InitializeCurrentSize(this);
217 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
218 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
219 m_constraint.SetScrollScale(this, 1.0, 1.0);
220 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
221 m_constraint.DisallowHeightGrowth();
222 //m_constraint.SubclassWnd(); // install subclassing
224 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
225 m_constraint.UpdateSizes();
227 COpenDoc *pDoc = GetDocument();
230 GetWindowText(strTitle);
231 pDoc->SetTitle(strTitle);
233 m_files = pDoc->m_files;
234 m_bRecurse = pDoc->m_bRecurse;
235 m_strExt = pDoc->m_strExt;
236 m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
237 m_dwFlags[0] = pDoc->m_dwFlags[0];
238 m_dwFlags[1] = pDoc->m_dwFlags[1];
239 m_dwFlags[2] = pDoc->m_dwFlags[2];
241 m_ctlPath[0].SetFileControlStates();
242 m_ctlPath[1].SetFileControlStates(true);
243 m_ctlPath[2].SetFileControlStates(true);
244 m_ctlUnpackerPipeline.SetFileControlStates(true);
246 for (int file = 0; file < m_files.GetSize(); file++)
248 m_strPath[file] = m_files[file];
249 m_ctlPath[file].SetWindowText(m_files[file].c_str());
250 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
253 m_ctlPath[0].AttachSystemImageList();
254 m_ctlPath[1].AttachSystemImageList();
255 m_ctlPath[2].AttachSystemImageList();
256 LoadComboboxStates();
258 m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
260 bool bDoUpdateData = true;
261 for (auto& strPath: m_strPath)
263 if (!strPath.empty())
264 bDoUpdateData = false;
266 UpdateData(bDoUpdateData);
268 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
269 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
270 bool bMask = pGlobalFileFilter->IsUsingMask();
274 String filterPrefix = _("[F] ");
275 filterNameOrMask = filterPrefix + filterNameOrMask;
278 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
280 m_ctlExt.SetCurSel(ind);
283 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
285 m_ctlExt.SetCurSel(ind);
287 LogErrorString(_T("Failed to add string to filters combo list!"));
290 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
292 EnableDlgItem(IDOK, true);
293 EnableDlgItem(IDC_UNPACKER_COMBO, true);
294 EnableDlgItem(IDC_SELECT_UNPACKER, true);
297 UpdateButtonStates();
299 bool bOverwriteRecursive = false;
300 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
301 bOverwriteRecursive = true;
302 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
303 bOverwriteRecursive = true;
304 if (!bOverwriteRecursive)
305 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
307 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
308 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
309 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
310 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
311 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
312 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
313 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
316 SetStatus(IDS_OPEN_FILESDIRS);
318 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
319 RegisterDragDrop(m_hWnd, m_pDropHandler);
322 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
324 m_bRecurse = GetDocument()->m_bRecurse;
326 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
327 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
328 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
329 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
330 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
331 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
332 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
337 // COpenView diagnostics
340 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
342 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
343 return (COpenDoc*)m_pDocument;
347 /////////////////////////////////////////////////////////////////////////////
348 // COpenView message handlers
350 void COpenView::OnPaint()
356 // Draw the logo image
357 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
358 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
359 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
360 // And extend it to the Right boundary
361 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
363 // Draw the resize gripper in the Lower Right corner.
365 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
366 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
367 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
369 // Draw a line to separate the Status Line
370 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
371 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
374 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
375 ScreenToClient(&rcStatus);
376 dc.MoveTo(0, rcStatus.top - 3);
377 dc.LineTo(rc.right, rcStatus.top - 3);
378 dc.SelectObject(oldpen);
380 CFormView::OnPaint();
383 void COpenView::OnLButtonUp(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 (int const id1 = pwndHit->GetDlgCtrlID())
392 case IDC_PATH0_COMBO:
393 case IDC_PATH1_COMBO:
394 case IDC_PATH2_COMBO:
396 CWnd *pwndChild = GetFocus();
397 if (IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
399 id2 = pwndChild->GetDlgCtrlID();
400 pwndChild = pwndChild->GetParent();
401 } while (pwndChild != this);
404 case IDC_PATH0_COMBO:
405 case IDC_PATH1_COMBO:
406 case IDC_PATH2_COMBO:
408 GetDlgItemText(id1, s1);
409 GetDlgItemText(id2, s2);
410 SetDlgItemText(id1, s2);
411 SetDlgItemText(id2, s1);
422 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
424 if (::GetCapture() == m_hWnd)
426 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
427 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
429 switch (pwndHit->GetDlgCtrlID())
431 case IDC_PATH0_COMBO:
432 case IDC_PATH1_COMBO:
433 case IDC_PATH2_COMBO:
434 if (!pwndHit->IsChild(GetFocus()))
436 SetCursor(m_hIconRotate);
441 SetCursor(m_hCursorNo);
448 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
450 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
452 CFrameWnd *const pFrameWnd = GetParentFrame();
453 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
456 pFrameWnd->GetClientRect(&rc);
457 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
458 lpwndpos->cy = m_sizeOrig.cy;
459 if (lpwndpos->flags & SWP_NOOWNERZORDER)
461 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
462 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
463 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
467 else if (pFrameWnd->IsZoomed())
469 lpwndpos->cx = m_totalLog.cx;
470 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
474 if (lpwndpos->cx > rc.Width())
475 lpwndpos->cx = rc.Width();
476 if (lpwndpos->cx < m_sizeOrig.cx)
477 lpwndpos->cx = m_sizeOrig.cx;
478 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
485 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
487 if (lpwndpos->flags & SWP_FRAMECHANGED)
489 m_constraint.UpdateSizes();
490 CFrameWnd *const pFrameWnd = GetParentFrame();
491 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
493 m_constraint.Persist(true, false);
494 WINDOWPLACEMENT wp = {};
495 wp.length = sizeof wp;
496 pFrameWnd->GetWindowPlacement(&wp);
499 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
500 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
501 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
502 pFrameWnd->SetWindowPlacement(&wp);
505 CFormView::OnWindowPosChanged(lpwndpos);
508 void COpenView::OnDestroy()
510 if (m_pDropHandler != nullptr)
511 RevokeDragDrop(m_hWnd);
513 CFormView::OnDestroy();
516 LRESULT COpenView::OnNcHitTest(CPoint point)
518 if (GetParentFrame()->IsZoomed())
522 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
523 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
524 if (PtInRect(&rc, point))
527 return CFormView::OnNcHitTest(point);
531 * @brief Called when "Browse..." button is selected for N path.
533 void COpenView::OnPathButton(UINT nId)
535 const int index = nId - IDC_PATH0_BUTTON;
540 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
543 case paths::IS_EXISTING_DIR:
544 sfolder = m_strPath[index];
546 case paths::IS_EXISTING_FILE:
547 sfolder = paths::GetPathOnly(m_strPath[index]);
549 case paths::DOES_NOT_EXIST:
550 if (!m_strPath[index].empty())
551 sfolder = paths::GetParentPath(m_strPath[index]);
554 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
558 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
560 m_strPath[index] = s;
561 m_strBrowsePath[index] = s;
563 UpdateButtonStates();
567 void COpenView::OnSwapButton(int id1, int id2)
570 GetDlgItemText(id1, s1);
571 GetDlgItemText(id2, s2);
573 SetDlgItemText(id1, s1);
574 SetDlgItemText(id2, s2);
577 template<int id1, int id2>
578 void COpenView::OnSwapButton()
580 OnSwapButton(id1, id2);
583 void COpenView::OnCompare(UINT nID)
585 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
586 const String filterPrefix = _("[F] ");
587 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
593 for (auto& strPath : m_strPath)
595 if (nFiles >= 1 && strPath.empty())
597 m_files.SetSize(nFiles + 1);
598 m_files[nFiles] = strPath;
599 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
600 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
603 // If left path is a project-file, load it
605 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
608 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
610 theApp.LoadAndOpenProjectFile(m_strPath[0]);
612 else if (!paths::IsDirectory(m_strPath[0]))
614 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
615 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
616 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
617 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo);
622 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
624 if (pathsType == paths::DOES_NOT_EXIST)
626 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
630 for (int index = 0; index < nFiles; index++)
632 // If user has edited path by hand, expand environment variables
633 bool bExpand = false;
634 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
637 if (!paths::IsURLorCLSID(m_files[index]))
639 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
641 // Add trailing '\' for directories if its missing
642 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
643 m_files[index] = paths::AddTrailingSlash(m_files[index]);
644 m_strPath[index] = m_files[index];
649 KillTimer(IDT_CHECKFILES);
650 KillTimer(IDT_RETRY);
652 String filter(strutils::trim_ws(m_strExt));
654 // If prefix found from start..
655 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
657 // Remove prefix + space
658 filter.erase(0, filterPrefix.length());
659 if (!pGlobalFileFilter->SetFilter(filter))
661 // If filtername is not found use default *.* mask
662 pGlobalFileFilter->SetFilter(_T("*.*"));
665 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
669 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
671 m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
672 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
675 SaveComboboxStates();
676 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
677 LoadComboboxStates();
679 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
680 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
681 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
682 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
683 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
684 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
685 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
687 m_constraint.Persist(true, false);
689 COpenDoc *pDoc = GetDocument();
690 pDoc->m_files = m_files;
691 pDoc->m_bRecurse = m_bRecurse;
692 pDoc->m_strExt = m_strExt;
693 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
694 pDoc->m_dwFlags[0] = m_dwFlags[0];
695 pDoc->m_dwFlags[1] = m_dwFlags[1];
696 pDoc->m_dwFlags[2] = m_dwFlags[2];
698 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
699 GetParentFrame()->PostMessage(WM_CLOSE);
701 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
702 PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
703 PathContext tmpPathContext(pDoc->m_files);
704 std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
705 bool recurse = pDoc->m_bRecurse;
708 GetMainFrame()->DoFileOrFolderOpen(
709 &tmpPathContext, dwFlags.data(),
710 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
712 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
714 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
715 GetMainFrame()->DoFileOrFolderOpen(
716 &tmpPathContext, dwFlags.data(),
717 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
719 else if (nID == ID_OPEN_WITH_UNPACKER)
721 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0],
722 CSelectPluginDlg::PluginType::Unpacker, false, this);
723 if (dlg.DoModal() == IDOK)
725 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
726 GetMainFrame()->DoFileOrFolderOpen(
727 &tmpPathContext, dwFlags.data(),
728 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
733 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo);
737 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
739 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
743 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
744 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
745 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
748 pCmdUI->Enable(bFile);
752 * @brief Called when dialog is closed with "OK".
754 * Checks that paths are valid and sets filters.
756 void COpenView::OnOK()
762 * @brief Called when dialog is closed via Cancel.
764 * Open-dialog is closed when `Cancel` button is selected or the
765 * `Esc` key is pressed. Save combobox states, since user may have
766 * removed items from them (with `shift-del`) and doesn't want them
768 * This is *not* called when the program is terminated, even if the
769 * dialog is visible at the time.
771 void COpenView::OnCancel()
773 SaveComboboxStates();
774 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
778 * @brief Callled when Open-button for project file is selected.
780 void COpenView::OnLoadProject()
782 String fileName = AskProjectFileName(true);
783 if (fileName.empty())
787 if (!theApp.LoadProjectFile(fileName, project))
789 if (project.Items().size() == 0)
792 ProjectFileItem& projItem = *project.Items().begin();
793 projItem.GetPaths(paths, m_bRecurse);
794 if (paths.GetSize() < 3)
796 m_strPath[0] = paths[0];
797 m_strPath[1] = paths[1];
798 m_strPath[2] = _T("");
799 m_bReadOnly[0] = projItem.GetLeftReadOnly();
800 m_bReadOnly[1] = projItem.GetRightReadOnly();
801 m_bReadOnly[2] = false;
805 m_strPath[0] = paths[0];
806 m_strPath[1] = paths[1];
807 m_strPath[2] = paths[2];
808 m_bReadOnly[0] = projItem.GetLeftReadOnly();
809 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
810 m_bReadOnly[2] = projItem.GetRightReadOnly();
812 m_strExt = projItem.GetFilter();
813 if (projItem.HasUnpacker())
814 m_strUnpackerPipeline = projItem.GetUnpacker();
816 if (projItem.HasIgnoreWhite())
817 m_nIgnoreWhite = projItem.GetIgnoreWhite();
818 if (projItem.HasIgnoreBlankLines())
819 m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
820 if (projItem.HasIgnoreCase())
821 m_bIgnoreCase = projItem.GetIgnoreCase();
822 if (projItem.HasIgnoreEol())
823 m_bIgnoreEol = projItem.GetIgnoreEol();
824 if (projItem.HasIgnoreCodepage())
825 m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
826 if (projItem.HasFilterCommentsLines())
827 m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
828 if (projItem.HasCompareMethod())
829 m_nCompareMethod = projItem.GetCompareMethod();
832 UpdateButtonStates();
833 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
837 * @brief Called when Save-button for project file is selected.
839 void COpenView::OnSaveProject()
843 String fileName = AskProjectFileName(false);
844 if (fileName.empty())
848 ProjectFileItem projItem;
850 if (!m_strPath[0].empty())
851 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
852 if (m_strPath[2].empty())
854 if (!m_strPath[1].empty())
855 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
859 if (!m_strPath[1].empty())
860 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
861 if (!m_strPath[2].empty())
862 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
864 if (!m_strExt.empty())
866 // Remove possbile prefix from the filter name
867 String prefix = _("[F] ");
868 String strExt = m_strExt;
869 size_t ind = strExt.find(prefix, 0);
872 strExt.erase(0, prefix.length());
874 strExt = strutils::trim_ws_begin(strExt);
875 projItem.SetFilter(strExt);
877 projItem.SetSubfolders(m_bRecurse);
878 if (!m_strUnpackerPipeline.empty())
879 projItem.SetUnpacker(m_strUnpackerPipeline);
881 projItem.SetIgnoreWhite(m_nIgnoreWhite);
882 projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
883 projItem.SetIgnoreCase(m_bIgnoreCase);
884 projItem.SetIgnoreEol(m_bIgnoreEol);
885 projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
886 projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
887 projItem.SetCompareMethod(m_nCompareMethod);
889 project.Items().push_back(projItem);
891 if (!theApp.SaveProjectFile(fileName, project))
894 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
897 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
899 CRect rcButton, rcView;
900 GetDlgItem(nID)->GetWindowRect(&rcButton);
902 VERIFY(menu.LoadMenu(nPopupID));
903 theApp.TranslateMenu(menu.m_hMenu);
904 CMenu* pPopup = menu.GetSubMenu(0);
905 if (pPopup != nullptr)
907 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
911 for (int i = 0; i < 3; i++)
912 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
913 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
914 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
916 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
917 rcButton.left, rcButton.bottom, GetMainFrame());
922 template<UINT id, UINT popupid>
923 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
925 DropDown(pNMHDR, pResult, id, popupid);
929 * @brief Allow user to select a file to open/save.
931 String COpenView::AskProjectFileName(bool bOpen)
933 // get the default projects path
934 String strProjectFileName;
935 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
937 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
938 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
941 if (strProjectFileName.empty())
944 // get the path part from the filename
945 strProjectPath = paths::GetParentPath(strProjectFileName);
946 // store this as the new project path
947 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
948 return strProjectFileName;
952 * @brief Load File- and filter-combobox states.
954 void COpenView::LoadComboboxStates()
956 m_ctlPath[0].LoadState(_T("Files\\Left"));
957 m_ctlPath[1].LoadState(_T("Files\\Right"));
958 m_ctlPath[2].LoadState(_T("Files\\Option"));
959 m_ctlExt.LoadState(_T("Files\\Ext"));
960 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
964 * @brief Save File- and filter-combobox states.
966 void COpenView::SaveComboboxStates()
968 m_ctlPath[0].SaveState(_T("Files\\Left"));
969 m_ctlPath[1].SaveState(_T("Files\\Right"));
970 m_ctlPath[2].SaveState(_T("Files\\Option"));
971 m_ctlExt.SaveState(_T("Files\\Ext"));
972 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
975 struct UpdateButtonStatesThreadParams
981 static UINT UpdateButtonStatesThread(LPVOID lpParam)
986 CoInitialize(nullptr);
987 CAssureScriptsForThread scriptsForRescan;
989 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
993 if (msg.message != WM_USER + 2)
996 bool bIsaFolderCompare = true;
997 bool bIsaFileCompare = true;
998 bool bInvalid[3] = {false, false, false};
999 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
1000 int iStatusMsgId = IDS_OPEN_FILESDIRS;
1002 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
1003 PathContext paths = pParams->m_paths;
1004 HWND hWnd = pParams->m_hWnd;
1007 // Check if we have project file as left side path
1008 bool bProject = false;
1010 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
1011 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1016 for (int i = 0; i < paths.GetSize(); ++i)
1018 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
1019 if (pathType[i] == paths::DOES_NOT_EXIST)
1024 // Enable buttons as appropriate
1025 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1027 paths::PATH_EXISTENCE pathsType = pathType[0];
1029 if (paths.GetSize() <= 2)
1031 if (bInvalid[0] && bInvalid[1])
1032 iStatusMsgId = IDS_OPEN_BOTHINVALID;
1033 else if (bInvalid[0])
1034 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1035 else if (bInvalid[1])
1037 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
1038 iStatusMsgId = IDS_OPEN_FILESDIRS;
1040 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
1042 else if (!bInvalid[0] && !bInvalid[1])
1044 if (pathType[0] != pathType[1])
1045 iStatusMsgId = IDS_OPEN_MISMATCH;
1047 iStatusMsgId = IDS_OPEN_FILESDIRS;
1052 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
1053 iStatusMsgId = IDS_OPEN_ALLINVALID;
1054 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
1055 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
1056 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
1057 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
1058 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
1059 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
1060 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
1061 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
1062 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
1063 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
1064 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1065 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1066 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1068 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
1069 iStatusMsgId = IDS_OPEN_MISMATCH;
1071 iStatusMsgId = IDS_OPEN_FILESDIRS;
1074 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1075 pathsType = paths::DOES_NOT_EXIST;
1076 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1077 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1078 // Both will be `false` if incompatibilities or something is missing
1079 // Both will end up `true` if file validity isn't being checked
1082 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1091 * @brief Update any resources necessary after a GUI language change
1093 void COpenView::UpdateResources()
1095 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1099 * @brief Enable/disable components based on validity of paths.
1101 void COpenView::UpdateButtonStates()
1103 UpdateData(TRUE); // load member variables from screen
1104 KillTimer(IDT_CHECKFILES);
1107 if (m_pUpdateButtonStatusThread == nullptr)
1109 m_pUpdateButtonStatusThread = AfxBeginThread(
1110 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1111 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1112 m_pUpdateButtonStatusThread->ResumeThread();
1113 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1117 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1118 pParams->m_hWnd = this->m_hWnd;
1119 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1121 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1124 void COpenView::TerminateThreadIfRunning()
1126 if (m_pUpdateButtonStatusThread == nullptr)
1129 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1130 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1131 if (dwResult != WAIT_OBJECT_0)
1133 m_pUpdateButtonStatusThread->SuspendThread();
1134 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1136 delete m_pUpdateButtonStatusThread;
1137 m_pUpdateButtonStatusThread = nullptr;
1141 * @brief Called when user changes selection in left/middle/right path's combo box.
1143 void COpenView::OnSelchangePathCombo(UINT nId)
1145 const int index = nId - IDC_PATH0_COMBO;
1146 int sel = m_ctlPath[index].GetCurSel();
1150 m_ctlPath[index].GetLBText(sel, cstrPath);
1151 m_strPath[index] = cstrPath;
1152 m_ctlPath[index].SetWindowText(cstrPath);
1155 UpdateButtonStates();
1158 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1160 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1162 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1164 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1165 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1170 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1172 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1178 * @brief Called every time paths are edited.
1180 void COpenView::OnEditEvent(UINT nID)
1182 const int N = nID - IDC_PATH0_COMBO;
1183 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1185 int const len = edit->GetWindowTextLength();
1186 if (edit->GetSel() == MAKEWPARAM(len, len))
1189 edit->GetWindowText(text);
1190 // Remove any double quotes
1192 if (text.GetLength() != len)
1194 edit->SetSel(0, len);
1195 edit->ReplaceSel(text);
1199 // (Re)start timer to path validity check delay
1200 // If timer starting fails, update buttonstates immediately
1201 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1202 UpdateButtonStates();
1206 * @brief Handle timer events.
1207 * Checks if paths are valid and sets control states accordingly.
1208 * @param [in] nIDEvent Timer ID that fired.
1210 void COpenView::OnTimer(UINT_PTR nIDEvent)
1212 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1213 UpdateButtonStates();
1215 CFormView::OnTimer(nIDEvent);
1219 * @brief Called when users selects plugin browse button.
1221 void COpenView::OnSelectUnpacker()
1223 paths::PATH_EXISTENCE pathsType;
1227 for (auto& strPath: m_strPath)
1229 if (nFiles == 2 && strPath.empty())
1231 m_files.SetSize(nFiles + 1);
1232 m_files[nFiles] = strPath;
1235 pathsType = paths::GetPairComparability(m_files);
1237 if (pathsType != paths::IS_EXISTING_FILE)
1240 // let the user select a handler
1241 CSelectPluginDlg dlg(m_strUnpackerPipeline, m_files[0],
1242 CSelectPluginDlg::PluginType::Unpacker, false, this);
1243 if (dlg.DoModal() == IDOK)
1245 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1250 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1252 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1253 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1254 const bool bProject = HIWORD(lParam) != 0;
1255 const int iStatusMsgId = LOWORD(lParam);
1257 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1259 EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
1260 EnableDlgItem(IDC_UNPACKER_COMBO, bIsaFileCompare);
1261 EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
1263 EnableDlgItem(IDC_FILES_DIRS_GROUP3, bIsaFolderCompare);
1264 EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
1265 EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
1266 EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
1268 SetStatus(iStatusMsgId);
1270 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1272 if (m_retryCount == 0)
1273 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1278 KillTimer(IDT_RETRY);
1285 * @brief Sets the path status text.
1286 * The open dialog shows a status text of selected paths. This function
1287 * is used to set that status text.
1288 * @param [in] msgID Resource ID of status text to set.
1290 void COpenView::SetStatus(UINT msgID)
1292 String msg = theApp.LoadString(msgID);
1293 SetDlgItemText(IDC_OPEN_STATUS, msg);
1297 * @brief Called when "Select..." button for filters is selected.
1299 void COpenView::OnSelectFilter()
1301 String filterPrefix = _("[F] ");
1303 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1305 const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1306 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1307 curFilter = strutils::trim_ws(curFilter);
1309 GetMainFrame()->SelectFilter();
1311 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1312 if (pGlobalFileFilter->IsUsingMask())
1314 // If we had filter chosen and now has mask we can overwrite filter
1315 if (!bUseMask || curFilter[0] != '*')
1317 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1322 filterNameOrMask = filterPrefix + filterNameOrMask;
1323 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1327 void COpenView::OnOptions()
1329 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1333 * @brief Set "Whitespaces" setting.
1334 * @param [in] nID Menu ID of the selected item
1336 void COpenView::OnDiffWhitespace(UINT nID)
1338 assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
1340 m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
1344 * @brief Update "Whitespaces" state.
1345 * @param [in] pCmdUI UI component to update.
1347 void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
1349 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
1353 * @brief Toggle "Ignore blank lines" setting.
1355 void COpenView::OnDiffIgnoreBlankLines()
1357 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1361 * @brief Update "Ignore blank lines" state.
1362 * @param [in] pCmdUI UI component to update.
1364 void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
1366 pCmdUI->SetCheck(m_bIgnoreBlankLines);
1370 * @brief Toggle "Ignore case" setting.
1372 void COpenView::OnDiffIgnoreCase()
1374 m_bIgnoreCase = !m_bIgnoreCase;
1378 * @brief Update "Ignore case" state.
1379 * @param [in] pCmdUI UI component to update.
1381 void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
1383 pCmdUI->SetCheck(m_bIgnoreCase);
1387 * @brief Toggle "Ignore carriage return differences" setting.
1389 void COpenView::OnDiffIgnoreEOL()
1391 m_bIgnoreEol = !m_bIgnoreEol;
1395 * @brief Update "Ignore carriage return differences" state.
1396 * @param [in] pCmdUI UI component to update.
1398 void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
1400 pCmdUI->SetCheck(m_bIgnoreEol);
1404 * @brief Toggle "Ignore codepage differences" setting.
1406 void COpenView::OnDiffIgnoreCP()
1408 m_bIgnoreCodepage = !m_bIgnoreCodepage;
1412 * @brief Update "Ignore codepage differences" state.
1413 * @param [in] pCmdUI UI component to update.
1415 void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
1417 pCmdUI->SetCheck(m_bIgnoreCodepage);
1421 * @brief Toggle "Ignore comment differences" setting.
1423 void COpenView::OnDiffIgnoreComments()
1425 m_bFilterCommentsLines = !m_bFilterCommentsLines;
1429 * @brief Update "Ignore comment differences" state.
1430 * @param [in] pCmdUI UI component to update.
1432 void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
1434 pCmdUI->SetCheck(m_bFilterCommentsLines);
1438 * @brief Set "Compare method" setting.
1439 * @param [in] nID Menu ID of the selected item
1441 void COpenView::OnCompareMethod(UINT nID)
1443 assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
1445 m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
1449 * @brief Update "Compare method" state.
1450 * @param [in] pCmdUI UI component to update.
1452 void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
1454 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
1458 * @brief Removes whitespaces from left and right paths
1459 * @note Assumes UpdateData(TRUE) is called before this function.
1461 void COpenView::TrimPaths()
1463 for (auto& strPath: m_strPath)
1464 strPath = strutils::trim_ws(strPath);
1468 * @brief Update control states when dialog is activated.
1470 * Update control states when user re-activates dialog. User might have
1471 * switched for other program to e.g. update files/folders and then
1472 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1475 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1477 CFormView::OnActivate(nState, pWndOther, bMinimized);
1479 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1480 UpdateButtonStates();
1483 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1485 CWnd *pCtl = GetFocus();
1486 if (pCtl != nullptr)
1487 pCtl->PostMessage(msg, wParam, lParam);
1490 template <int MSG, int WPARAM, int LPARAM>
1491 void COpenView::OnEditAction()
1493 OnEditAction(MSG, WPARAM, LPARAM);
1497 * @brief Open help from mainframe when user presses F1.
1499 void COpenView::OnHelp()
1501 theApp.ShowHelp(OpenDlgHelpLocation);
1504 /////////////////////////////////////////////////////////////////////////////
1506 // OnDropFiles code from CDropEdit
1507 // Copyright 1997 Chris Losinger
1509 // shortcut expansion code modified from :
1510 // CShortcut, 1996 Rob Warner
1514 * @brief Drop paths(s) to the dialog.
1515 * One or two paths can be dropped to the dialog. The behaviour is:
1517 * - drop to empty path edit box (check left first)
1518 * - if both boxes have a path, drop to left path
1520 * - overwrite both paths, empty or not
1521 * @param [in] dropInfo Dropped data, including paths.
1523 void COpenView::OnDropFiles(const std::vector<String>& files)
1525 const size_t fileCount = files.size();
1527 // Add dropped paths to the dialog
1531 m_strPath[0] = files[0];
1532 m_strPath[1] = files[1];
1533 m_strPath[2] = files[2];
1535 UpdateButtonStates();
1537 else if (fileCount == 2)
1539 m_strPath[0] = files[0];
1540 m_strPath[1] = files[1];
1542 UpdateButtonStates();
1544 else if (fileCount == 1)
1547 GetCursorPos(&point);
1548 ScreenToClient(&point);
1549 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1550 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1552 switch (int const id = pwndHit->GetDlgCtrlID())
1554 case IDC_PATH0_COMBO:
1555 case IDC_PATH1_COMBO:
1556 case IDC_PATH2_COMBO:
1557 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1560 if (m_strPath[0].empty())
1561 m_strPath[0] = files[0];
1562 else if (m_strPath[1].empty())
1563 m_strPath[1] = files[0];
1564 else if (m_strPath[2].empty())
1565 m_strPath[2] = files[0];
1567 m_strPath[0] = files[0];
1572 UpdateButtonStates();