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;
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_CONTROL_RANGE(BN_CLICKED, IDC_SELECT_UNPACKER, IDC_SELECT_PREDIFFER, OnSelectPlugin)
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_NUMBERS, OnDiffIgnoreNumbers)
86 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnUpdateDiffIgnoreNumbers)
87 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
88 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
89 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
90 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
91 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
92 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
94 ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
95 ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
96 ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
97 ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
98 ON_COMMAND(IDOK, OnOK)
99 ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
100 ON_COMMAND(IDCANCEL, OnCancel)
101 ON_COMMAND(ID_HELP, OnHelp)
102 ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
103 ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
104 ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
105 ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
106 ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
107 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnCompare)
108 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateCompare)
109 ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
110 ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
111 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
115 ON_WM_WINDOWPOSCHANGING()
116 ON_WM_WINDOWPOSCHANGED()
122 // COpenView construction/destruction
124 COpenView::COpenView()
125 : CFormView(COpenView::IDD)
126 , m_pUpdateButtonStatusThread(nullptr)
128 , m_pDropHandler(nullptr)
130 , m_bAutoCompleteReady()
131 , m_bReadOnly {false, false, false}
132 , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
133 , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
136 , m_bIgnoreBlankLines(false)
137 , m_bIgnoreCase(false)
138 , m_bIgnoreEol(false)
139 , m_bIgnoreNumbers(false)
140 , m_bIgnoreCodepage(false)
141 , m_bFilterCommentsLines(false)
142 , m_nCompareMethod(0)
144 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
145 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
146 m_bInsideUpdate = TRUE;
149 COpenView::~COpenView()
151 TerminateThreadIfRunning();
154 void COpenView::DoDataExchange(CDataExchange* pDX)
156 __super::DoDataExchange(pDX);
157 //{{AFX_DATA_MAP(COpenView)
158 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
159 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
160 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
161 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
162 DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
163 DDX_Control(pDX, IDC_PREDIFFER_COMBO, m_ctlPredifferPipeline);
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);
173 DDX_CBStringExact(pDX, IDC_PREDIFFER_COMBO, m_strPredifferPipeline);
177 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
179 // TODO: Modify the Window class or styles here by modifying
180 // the CREATESTRUCT cs
181 cs.style &= ~WS_BORDER;
182 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
183 return __super::PreCreateWindow(cs);
186 void COpenView::OnInitialUpdate()
188 if (!IsVista_OrGreater())
191 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
192 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
193 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
196 m_sizeOrig = GetTotalSize();
198 theApp.TranslateDialog(m_hWnd);
200 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
202 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
203 m_image.Create(1, 1, 24, 0);
206 __super::OnInitialUpdate();
208 // set caption to "swap paths" button
210 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
211 lf.lfCharSet = SYMBOL_CHARSET;
212 lstrcpy(lf.lfFaceName, _T("Wingdings"));
213 m_fontSwapButton.CreateFontIndirect(&lf);
214 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
215 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
217 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
218 SetDlgItemText(ids[i], _T("\xf4"));
221 m_constraint.InitializeCurrentSize(this);
222 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
223 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
224 m_constraint.SetScrollScale(this, 1.0, 1.0);
225 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
226 m_constraint.DisallowHeightGrowth();
227 //m_constraint.SubclassWnd(); // install subclassing
229 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
230 m_constraint.UpdateSizes();
232 COpenDoc *pDoc = GetDocument();
235 GetWindowText(strTitle);
236 pDoc->SetTitle(strTitle);
238 m_files = pDoc->m_files;
239 m_bRecurse = pDoc->m_bRecurse;
240 m_strExt = pDoc->m_strExt;
241 m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
242 m_strPredifferPipeline = pDoc->m_strPredifferPipeline;
243 m_dwFlags[0] = pDoc->m_dwFlags[0];
244 m_dwFlags[1] = pDoc->m_dwFlags[1];
245 m_dwFlags[2] = pDoc->m_dwFlags[2];
247 m_ctlPath[0].SetFileControlStates();
248 m_ctlPath[1].SetFileControlStates(true);
249 m_ctlPath[2].SetFileControlStates(true);
250 m_ctlUnpackerPipeline.SetFileControlStates(true);
251 m_ctlPredifferPipeline.SetFileControlStates(true);
253 for (int file = 0; file < m_files.GetSize(); file++)
255 m_strPath[file] = m_files[file];
256 m_ctlPath[file].SetWindowText(m_files[file].c_str());
257 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
260 m_ctlPath[0].AttachSystemImageList();
261 m_ctlPath[1].AttachSystemImageList();
262 m_ctlPath[2].AttachSystemImageList();
263 LoadComboboxStates();
265 m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
266 m_ctlPredifferPipeline.SetWindowText(m_strPredifferPipeline.c_str());
268 bool bDoUpdateData = true;
269 for (auto& strPath: m_strPath)
271 if (!strPath.empty())
272 bDoUpdateData = false;
274 UpdateData(bDoUpdateData);
276 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
277 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
278 bool bMask = pGlobalFileFilter->IsUsingMask();
282 String filterPrefix = _("[F] ");
283 filterNameOrMask = filterPrefix + filterNameOrMask;
286 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
288 m_ctlExt.SetCurSel(ind);
291 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
293 m_ctlExt.SetCurSel(ind);
295 LogErrorString(_T("Failed to add string to filters combo list!"));
298 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
300 EnableDlgItem(IDOK, true);
301 EnableDlgItem(IDC_UNPACKER_COMBO, true);
302 EnableDlgItem(IDC_SELECT_UNPACKER, true);
305 UpdateButtonStates();
307 bool bOverwriteRecursive = false;
308 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
309 bOverwriteRecursive = true;
310 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
311 bOverwriteRecursive = true;
312 if (!bOverwriteRecursive)
313 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
315 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
316 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
317 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
318 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
319 m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
320 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
321 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
322 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
325 SetStatus(IDS_OPEN_FILESDIRS);
327 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
328 RegisterDragDrop(m_hWnd, m_pDropHandler);
331 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
333 m_bRecurse = GetDocument()->m_bRecurse;
335 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
336 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
337 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
338 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
339 m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
340 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
341 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
342 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
347 // COpenView diagnostics
350 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
352 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
353 return (COpenDoc*)m_pDocument;
357 /////////////////////////////////////////////////////////////////////////////
358 // COpenView message handlers
360 void COpenView::OnPaint()
366 // Draw the logo image
367 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
368 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
369 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
370 // And extend it to the Right boundary
371 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
373 // Draw the resize gripper in the Lower Right corner.
375 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
376 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
377 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
379 // Draw a line to separate the Status Line
380 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
381 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
384 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
385 ScreenToClient(&rcStatus);
386 dc.MoveTo(0, rcStatus.top - 3);
387 dc.LineTo(rc.right, rcStatus.top - 3);
388 dc.SelectObject(oldpen);
393 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
395 if (::GetCapture() == m_hWnd)
397 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
398 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
400 switch (int const id1 = pwndHit->GetDlgCtrlID())
402 case IDC_PATH0_COMBO:
403 case IDC_PATH1_COMBO:
404 case IDC_PATH2_COMBO:
406 CWnd *pwndChild = GetFocus();
407 if (pwndChild && IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
409 id2 = pwndChild->GetDlgCtrlID();
410 pwndChild = pwndChild->GetParent();
411 } while (pwndChild != this);
414 case IDC_PATH0_COMBO:
415 case IDC_PATH1_COMBO:
416 case IDC_PATH2_COMBO:
418 GetDlgItemText(id1, s1);
419 GetDlgItemText(id2, s2);
420 SetDlgItemText(id1, s2);
421 SetDlgItemText(id2, s1);
432 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
434 if (::GetCapture() == m_hWnd)
436 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
437 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
439 switch (pwndHit->GetDlgCtrlID())
441 case IDC_PATH0_COMBO:
442 case IDC_PATH1_COMBO:
443 case IDC_PATH2_COMBO:
444 if (!pwndHit->IsChild(GetFocus()))
446 SetCursor(m_hIconRotate);
451 SetCursor(m_hCursorNo);
458 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
460 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
462 CFrameWnd *const pFrameWnd = GetParentFrame();
463 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
466 pFrameWnd->GetClientRect(&rc);
467 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
468 lpwndpos->cy = m_sizeOrig.cy;
469 if (lpwndpos->flags & SWP_NOOWNERZORDER)
471 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
472 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
473 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
477 else if (pFrameWnd->IsZoomed())
479 lpwndpos->cx = m_totalLog.cx;
480 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
484 if (lpwndpos->cx > rc.Width())
485 lpwndpos->cx = rc.Width();
486 if (lpwndpos->cx < m_sizeOrig.cx)
487 lpwndpos->cx = m_sizeOrig.cx;
488 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
495 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
497 if (lpwndpos->flags & SWP_FRAMECHANGED)
499 m_constraint.UpdateSizes();
500 CFrameWnd *const pFrameWnd = GetParentFrame();
501 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
503 m_constraint.Persist(true, false);
504 WINDOWPLACEMENT wp = {};
505 wp.length = sizeof wp;
506 pFrameWnd->GetWindowPlacement(&wp);
509 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
510 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
511 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
512 pFrameWnd->SetWindowPlacement(&wp);
515 __super::OnWindowPosChanged(lpwndpos);
518 void COpenView::OnDestroy()
520 if (m_pDropHandler != nullptr)
521 RevokeDragDrop(m_hWnd);
523 __super::OnDestroy();
526 LRESULT COpenView::OnNcHitTest(CPoint point)
528 if (GetParentFrame()->IsZoomed())
532 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
533 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
534 if (PtInRect(&rc, point))
537 return __super::OnNcHitTest(point);
541 * @brief Called when "Browse..." button is selected for N path.
543 void COpenView::OnPathButton(UINT nId)
545 const int index = nId - IDC_PATH0_BUTTON;
550 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
553 case paths::IS_EXISTING_DIR:
554 sfolder = m_strPath[index];
556 case paths::IS_EXISTING_FILE:
557 sfolder = paths::GetPathOnly(m_strPath[index]);
559 case paths::DOES_NOT_EXIST:
560 if (!m_strPath[index].empty())
561 sfolder = paths::GetParentPath(m_strPath[index]);
564 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
568 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
570 m_strPath[index] = s;
571 m_strBrowsePath[index] = std::move(s);
573 UpdateButtonStates();
577 void COpenView::OnSwapButton(int id1, int id2)
580 GetDlgItemText(id1, s1);
581 GetDlgItemText(id2, s2);
583 SetDlgItemText(id1, s1);
584 SetDlgItemText(id2, s2);
587 template<int id1, int id2>
588 void COpenView::OnSwapButton()
590 OnSwapButton(id1, id2);
593 void COpenView::OnCompare(UINT nID)
595 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
596 const String filterPrefix = _("[F] ");
597 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
603 for (auto& strPath : m_strPath)
605 if (nFiles >= 1 && strPath.empty())
607 m_files.SetSize(nFiles + 1);
608 m_files[nFiles] = strPath;
609 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
610 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
613 // If left path is a project-file, load it
615 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
618 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
620 theApp.LoadAndOpenProjectFile(m_strPath[0]);
622 else if (!paths::IsDirectory(m_strPath[0]))
624 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
625 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
626 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
627 PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
628 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo, &tmpPrediffingInfo);
633 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
635 if (pathsType == paths::DOES_NOT_EXIST &&
636 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); }))
638 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
642 for (int index = 0; index < nFiles; index++)
644 // If user has edited path by hand, expand environment variables
645 bool bExpand = false;
646 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
649 if (!paths::IsURLorCLSID(m_files[index]))
651 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
653 // Add trailing '\' for directories if its missing
654 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR && !IsArchiveFile(m_files[index]))
655 m_files[index] = paths::AddTrailingSlash(m_files[index]);
656 m_strPath[index] = m_files[index];
661 KillTimer(IDT_CHECKFILES);
662 KillTimer(IDT_RETRY);
664 String filter(strutils::trim_ws(m_strExt));
666 // If prefix found from start..
667 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
669 // Remove prefix + space
670 filter.erase(0, filterPrefix.length());
671 if (!pGlobalFileFilter->SetFilter(filter))
673 // If filtername is not found use default *.* mask
674 pGlobalFileFilter->SetFilter(_T("*.*"));
677 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
681 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
683 m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
684 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
687 SaveComboboxStates();
688 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
689 LoadComboboxStates();
691 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
692 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
693 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
694 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
695 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_NUMBERS, m_bIgnoreNumbers);
696 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
697 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
698 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
700 m_constraint.Persist(true, false);
702 COpenDoc *pDoc = GetDocument();
703 pDoc->m_files = m_files;
704 pDoc->m_bRecurse = m_bRecurse;
705 pDoc->m_strExt = m_strExt;
706 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
707 pDoc->m_strPredifferPipeline = m_strPredifferPipeline;
708 pDoc->m_dwFlags[0] = m_dwFlags[0];
709 pDoc->m_dwFlags[1] = m_dwFlags[1];
710 pDoc->m_dwFlags[2] = m_dwFlags[2];
712 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
713 GetParentFrame()->PostMessage(WM_CLOSE);
715 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
716 PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
717 PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
718 PathContext tmpPathContext(pDoc->m_files);
719 std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
720 bool recurse = pDoc->m_bRecurse;
721 std::unique_ptr<CMainFrame::OpenFolderParams> pOpenFolderParams;
722 if (!pDoc->m_hiddenItems.empty())
724 pOpenFolderParams = std::make_unique<CMainFrame::OpenFolderParams>();
725 pOpenFolderParams->m_hiddenItems = pDoc->m_hiddenItems;
729 GetMainFrame()->DoFileOrFolderOpen(
730 &tmpPathContext, dwFlags.data(),
731 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
733 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
735 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
736 GetMainFrame()->DoFileOrFolderOpen(
737 &tmpPathContext, dwFlags.data(),
738 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
740 else if (nID == ID_OPEN_WITH_UNPACKER)
742 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0],
743 CSelectPluginDlg::PluginType::Unpacker, false, this);
744 if (dlg.DoModal() == IDOK)
746 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
747 GetMainFrame()->DoFileOrFolderOpen(
748 &tmpPathContext, dwFlags.data(),
749 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
754 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo, &tmpPrediffingInfo, pOpenFolderParams.get());
758 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
760 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
764 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
765 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
766 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
769 pCmdUI->Enable(bFile);
773 * @brief Called when dialog is closed with "OK".
775 * Checks that paths are valid and sets filters.
777 void COpenView::OnOK()
783 * @brief Called when dialog is closed via Cancel.
785 * Open-dialog is closed when `Cancel` button is selected or the
786 * `Esc` key is pressed. Save combobox states, since user may have
787 * removed items from them (with `shift-del`) and doesn't want them
789 * This is *not* called when the program is terminated, even if the
790 * dialog is visible at the time.
792 void COpenView::OnCancel()
794 SaveComboboxStates();
795 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
799 * @brief Called when Open-button for project file is selected.
801 void COpenView::OnLoadProject()
805 String fileName = AskProjectFileName(true);
806 if (fileName.empty())
810 if (!theApp.LoadProjectFile(fileName, project))
812 if (project.Items().size() == 0)
815 ProjectFileItem& projItem = *project.Items().begin();
816 bool bRecurse = m_bRecurse;
817 projItem.GetPaths(paths, bRecurse);
818 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::IncludeSubfolders))
819 m_bRecurse = bRecurse;
820 if (paths.GetSize() < 3)
822 m_strPath[0] = paths[0];
823 m_strPath[1] = paths[1];
824 m_strPath[2].clear();
825 m_bReadOnly[0] = projItem.GetLeftReadOnly();
826 m_bReadOnly[1] = projItem.GetRightReadOnly();
827 m_bReadOnly[2] = false;
831 m_strPath[0] = paths[0];
832 m_strPath[1] = paths[1];
833 m_strPath[2] = paths[2];
834 m_bReadOnly[0] = projItem.GetLeftReadOnly();
835 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
836 m_bReadOnly[2] = projItem.GetRightReadOnly();
838 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::FileFilter) && projItem.HasFilter())
839 m_strExt = projItem.GetFilter();
840 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::Plugin))
842 if (projItem.HasUnpacker())
843 m_strUnpackerPipeline = projItem.GetUnpacker();
844 if (projItem.HasPrediffer())
845 m_strPredifferPipeline = projItem.GetPrediffer();
848 if (projItem.HasWindowType())
849 GetDocument()->m_nWindowType = projItem.GetWindowType();
850 if (projItem.HasTableDelimiter())
851 GetDocument()->m_cTableDelimiter = projItem.GetTableDelimiter();
852 if (projItem.HasTableQuote())
853 GetDocument()->m_cTableQuote = projItem.GetTableQuote();
854 if (projItem.HasTableAllowNewLinesInQuotes())
855 GetDocument()->m_bTableAllowNewLinesInQuotes = projItem.GetTableAllowNewLinesInQuotes();
857 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::CompareOptions))
859 if (projItem.HasIgnoreWhite())
860 m_nIgnoreWhite = projItem.GetIgnoreWhite();
861 if (projItem.HasIgnoreBlankLines())
862 m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
863 if (projItem.HasIgnoreCase())
864 m_bIgnoreCase = projItem.GetIgnoreCase();
865 if (projItem.HasIgnoreEol())
866 m_bIgnoreEol = projItem.GetIgnoreEol();
867 if (projItem.HasIgnoreNumbers())
868 m_bIgnoreNumbers = projItem.GetIgnoreNumbers();
869 if (projItem.HasIgnoreCodepage())
870 m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
871 if (projItem.HasFilterCommentsLines())
872 m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
873 if (projItem.HasCompareMethod())
874 m_nCompareMethod = projItem.GetCompareMethod();
877 if ((Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::HiddenItems)) && projItem.HasHiddenItems())
879 GetDocument()->m_hiddenItems = projItem.GetHiddenItems();
882 UpdateButtonStates();
883 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
887 * @brief Called when Save-button for project file is selected.
889 void COpenView::OnSaveProject()
893 String fileName = AskProjectFileName(false);
894 if (fileName.empty())
898 ProjectFileItem projItem;
900 bool bSaveFileFilter = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::FileFilter);
901 bool bSaveIncludeSubfolders = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::IncludeSubfolders);
902 bool bSavePlugin = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::Plugin);
903 bool bSaveCompareOptions = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::CompareOptions);
904 bool bSaveHiddenItems = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::HiddenItems);
906 projItem.SetSaveFilter(bSaveFileFilter);
907 projItem.SetSaveSubfolders(bSaveIncludeSubfolders);
908 projItem.SetSaveUnpacker(bSavePlugin);
909 projItem.SetSavePrediffer(bSavePlugin);
910 projItem.SetSaveIgnoreWhite(bSaveCompareOptions);
911 projItem.SetSaveIgnoreBlankLines(bSaveCompareOptions);
912 projItem.SetSaveIgnoreCase(bSaveCompareOptions);
913 projItem.SetSaveIgnoreEol(bSaveCompareOptions);
914 projItem.SetSaveIgnoreNumbers(bSaveCompareOptions);
915 projItem.SetSaveIgnoreCodepage(bSaveCompareOptions);
916 projItem.SetSaveFilterCommentsLines(bSaveCompareOptions);
917 projItem.SetSaveCompareMethod(bSaveCompareOptions);
918 projItem.SetSaveHiddenItems(bSaveHiddenItems);
920 if (!m_strPath[0].empty())
921 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
922 if (!GetDocument()->m_strDesc[0].empty())
923 projItem.SetLeftDesc(GetDocument()->m_strDesc[0]);
924 if (m_strPath[2].empty())
926 if (!m_strPath[1].empty())
927 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
928 if (!GetDocument()->m_strDesc[1].empty())
929 projItem.SetRightDesc(GetDocument()->m_strDesc[1]);
933 if (!m_strPath[1].empty())
934 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
935 if (!m_strPath[2].empty())
936 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
937 if (!GetDocument()->m_strDesc[1].empty())
938 projItem.SetMiddleDesc(GetDocument()->m_strDesc[1]);
939 if (!GetDocument()->m_strDesc[2].empty())
940 projItem.SetRightDesc(GetDocument()->m_strDesc[2]);
942 if (bSaveFileFilter && !m_strExt.empty())
944 // Remove possbile prefix from the filter name
945 String prefix = _("[F] ");
946 String strExt = m_strExt;
947 size_t ind = strExt.find(prefix, 0);
950 strExt.erase(0, prefix.length());
952 strExt = strutils::trim_ws_begin(strExt);
953 projItem.SetFilter(strExt);
955 if (bSaveIncludeSubfolders)
956 projItem.SetSubfolders(m_bRecurse);
959 if (!m_strUnpackerPipeline.empty())
960 projItem.SetUnpacker(m_strUnpackerPipeline);
961 if (!m_strPredifferPipeline.empty())
962 projItem.SetPrediffer(m_strPredifferPipeline);
964 if (GetDocument()->m_nWindowType != -1)
965 projItem.SetWindowType(GetDocument()->m_nWindowType);
966 if (GetDocument()->m_nWindowType == 2 /* table */)
968 projItem.SetTableDelimiter(GetDocument()->m_cTableDelimiter);
969 projItem.SetTableQuote(GetDocument()->m_cTableQuote);
970 projItem.SetTableAllowNewLinesInQuotes(GetDocument()->m_bTableAllowNewLinesInQuotes);
973 if (bSaveCompareOptions)
975 projItem.SetIgnoreWhite(m_nIgnoreWhite);
976 projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
977 projItem.SetIgnoreCase(m_bIgnoreCase);
978 projItem.SetIgnoreEol(m_bIgnoreEol);
979 projItem.SetIgnoreNumbers(m_bIgnoreNumbers);
980 projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
981 projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
982 projItem.SetCompareMethod(m_nCompareMethod);
985 if (bSaveHiddenItems)
986 projItem.SetHiddenItems(GetDocument()->m_hiddenItems);
988 project.Items().push_back(projItem);
990 if (!theApp.SaveProjectFile(fileName, project))
993 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
996 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
999 GetDlgItem(nID)->GetWindowRect(&rcButton);
1001 VERIFY(menu.LoadMenu(nPopupID));
1002 theApp.TranslateMenu(menu.m_hMenu);
1003 CMenu* pPopup = menu.GetSubMenu(0);
1004 if (pPopup != nullptr)
1006 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
1010 for (int i = 0; i < 3; i++)
1011 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
1012 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
1013 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
1015 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
1016 rcButton.left, rcButton.bottom, GetMainFrame());
1021 template<UINT id, UINT popupid>
1022 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
1024 DropDown(pNMHDR, pResult, id, popupid);
1028 * @brief Allow user to select a file to open/save.
1030 String COpenView::AskProjectFileName(bool bOpen)
1032 // get the default projects path
1033 String strProjectFileName;
1034 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
1036 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
1037 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
1040 if (strProjectFileName.empty())
1043 // get the path part from the filename
1044 strProjectPath = paths::GetParentPath(strProjectFileName);
1045 // store this as the new project path
1046 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
1047 return strProjectFileName;
1051 * @brief Load File- and filter-combobox states.
1053 void COpenView::LoadComboboxStates()
1055 m_ctlPath[0].LoadState(_T("Files\\Left"));
1056 m_ctlPath[1].LoadState(_T("Files\\Right"));
1057 m_ctlPath[2].LoadState(_T("Files\\Option"));
1058 m_ctlExt.LoadState(_T("Files\\Ext"));
1059 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
1060 m_ctlPredifferPipeline.LoadState(_T("Files\\Prediffer"));
1064 * @brief Save File- and filter-combobox states.
1066 void COpenView::SaveComboboxStates()
1068 m_ctlPath[0].SaveState(_T("Files\\Left"));
1069 m_ctlPath[1].SaveState(_T("Files\\Right"));
1070 m_ctlPath[2].SaveState(_T("Files\\Option"));
1071 m_ctlExt.SaveState(_T("Files\\Ext"));
1072 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
1073 m_ctlPredifferPipeline.SaveState(_T("Files\\Prediffer"));
1076 struct UpdateButtonStatesThreadParams
1079 PathContext m_paths;
1082 static UINT UpdateButtonStatesThread(LPVOID lpParam)
1087 CoInitialize(nullptr);
1088 CAssureScriptsForThread scriptsForRescan;
1090 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
1094 if (msg.message != WM_USER + 2)
1097 bool bIsaFolderCompare = true;
1098 bool bIsaFileCompare = true;
1099 bool bInvalid[3] = {false, false, false};
1100 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
1101 int iStatusMsgId = IDS_OPEN_FILESDIRS;
1103 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
1104 PathContext paths = pParams->m_paths;
1105 HWND hWnd = pParams->m_hWnd;
1108 // Check if we have project file as left side path
1109 bool bProject = false;
1111 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
1112 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1117 for (int i = 0; i < paths.GetSize(); ++i)
1119 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
1120 if (pathType[i] == paths::DOES_NOT_EXIST)
1122 if (paths::IsURL(paths[i]))
1123 pathType[i] = paths::IS_EXISTING_FILE;
1130 // Enable buttons as appropriate
1131 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1133 paths::PATH_EXISTENCE pathsType = pathType[0];
1135 if (paths.GetSize() <= 2)
1137 if (bInvalid[0] && bInvalid[1])
1138 iStatusMsgId = IDS_OPEN_BOTHINVALID;
1139 else if (bInvalid[0])
1140 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1141 else if (bInvalid[1])
1143 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
1144 iStatusMsgId = IDS_OPEN_FILESDIRS;
1146 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
1148 else if (!bInvalid[0] && !bInvalid[1])
1150 if (pathType[0] != pathType[1])
1151 iStatusMsgId = IDS_OPEN_MISMATCH;
1153 iStatusMsgId = IDS_OPEN_FILESDIRS;
1158 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
1159 iStatusMsgId = IDS_OPEN_ALLINVALID;
1160 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
1161 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
1162 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
1163 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
1164 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
1165 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
1166 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
1167 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
1168 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
1169 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
1170 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1171 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1172 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1174 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
1175 iStatusMsgId = IDS_OPEN_MISMATCH;
1177 iStatusMsgId = IDS_OPEN_FILESDIRS;
1180 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1181 pathsType = paths::DOES_NOT_EXIST;
1182 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1183 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1184 // Both will be `false` if incompatibilities or something is missing
1185 // Both will end up `true` if file validity isn't being checked
1188 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1197 * @brief Update any resources necessary after a GUI language change
1199 void COpenView::UpdateResources()
1201 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1205 * @brief Enable/disable components based on validity of paths.
1207 void COpenView::UpdateButtonStates()
1209 UpdateData(TRUE); // load member variables from screen
1210 KillTimer(IDT_CHECKFILES);
1213 if (m_pUpdateButtonStatusThread == nullptr)
1215 m_pUpdateButtonStatusThread = AfxBeginThread(
1216 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1217 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1218 m_pUpdateButtonStatusThread->ResumeThread();
1219 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1223 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1224 pParams->m_hWnd = this->m_hWnd;
1225 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1227 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1230 void COpenView::TerminateThreadIfRunning()
1232 if (m_pUpdateButtonStatusThread == nullptr)
1235 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1236 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1237 if (dwResult != WAIT_OBJECT_0)
1239 m_pUpdateButtonStatusThread->SuspendThread();
1240 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1242 delete m_pUpdateButtonStatusThread;
1243 m_pUpdateButtonStatusThread = nullptr;
1247 * @brief Called when user changes selection in left/middle/right path's combo box.
1249 void COpenView::OnSelchangePathCombo(UINT nId)
1251 const int index = nId - IDC_PATH0_COMBO;
1252 int sel = m_ctlPath[index].GetCurSel();
1256 m_ctlPath[index].GetLBText(sel, cstrPath);
1257 m_strPath[index] = cstrPath;
1258 m_ctlPath[index].SetWindowText(cstrPath);
1261 UpdateButtonStates();
1264 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1266 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1268 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1270 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1271 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1276 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1278 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1284 * @brief Called every time paths are edited.
1286 void COpenView::OnEditEvent(UINT nID)
1288 const int N = nID - IDC_PATH0_COMBO;
1289 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1291 int const len = edit->GetWindowTextLength();
1292 if (edit->GetSel() == MAKEWPARAM(len, len))
1295 edit->GetWindowText(text);
1296 // Remove any double quotes
1298 if (text.GetLength() != len)
1300 edit->SetSel(0, len);
1301 edit->ReplaceSel(text);
1305 // (Re)start timer to path validity check delay
1306 // If timer starting fails, update buttonstates immediately
1307 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1308 UpdateButtonStates();
1312 * @brief Handle timer events.
1313 * Checks if paths are valid and sets control states accordingly.
1314 * @param [in] nIDEvent Timer ID that fired.
1316 void COpenView::OnTimer(UINT_PTR nIDEvent)
1318 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1319 UpdateButtonStates();
1321 __super::OnTimer(nIDEvent);
1325 * @brief Called when users selects plugin browse button.
1327 void COpenView::OnSelectPlugin(UINT nID)
1329 paths::PATH_EXISTENCE pathsType;
1333 for (auto& strPath: m_strPath)
1335 if (nFiles == 2 && strPath.empty())
1337 m_files.SetSize(nFiles + 1);
1338 m_files[nFiles] = strPath;
1341 PathContext tmpFiles = m_files;
1342 if (tmpFiles.GetSize() == 2 && tmpFiles[1].empty())
1343 tmpFiles[1] = tmpFiles[0];
1344 pathsType = paths::GetPairComparability(tmpFiles, IsArchiveFile);
1346 if (pathsType == paths::IS_EXISTING_DIR || (pathsType == paths::DOES_NOT_EXIST &&
1347 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); })))
1350 // let the user select a handler
1351 CSelectPluginDlg dlg(nID == IDC_SELECT_UNPACKER ? m_strUnpackerPipeline : m_strPredifferPipeline, m_files[0],
1352 nID == IDC_SELECT_UNPACKER ? CSelectPluginDlg::PluginType::Unpacker : CSelectPluginDlg::PluginType::Prediffer, false, this);
1353 if (dlg.DoModal() == IDOK)
1355 if (nID == IDC_SELECT_UNPACKER)
1356 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1358 m_strPredifferPipeline = dlg.GetPluginPipeline();
1363 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1365 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1366 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1367 const bool bProject = HIWORD(lParam) != 0;
1368 const int iStatusMsgId = LOWORD(lParam);
1370 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1372 for (auto nID : { IDC_FILES_DIRS_GROUP4, IDC_UNPACKER_COMBO, IDC_SELECT_UNPACKER })
1374 EnableDlgItem(nID, bIsaFileCompare);
1377 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1379 for (auto nID : { IDC_FILES_DIRS_GROUP5, IDC_PREDIFFER_COMBO, IDC_SELECT_PREDIFFER })
1381 GetDlgItem(nID)->ShowWindow(bIsaFileCompare ? SW_SHOW : SW_HIDE);
1382 EnableDlgItem(nID, bIsaFileCompare);
1385 for (auto nID : { IDC_FILES_DIRS_GROUP3, IDC_EXT_COMBO, IDC_SELECT_FILTER, IDC_RECURS_CHECK })
1387 GetDlgItem(nID)->ShowWindow((bIsaFolderCompare || !bIsaFileCompare) ? SW_SHOW : SW_HIDE);
1388 EnableDlgItem(nID, bIsaFolderCompare);
1392 SetStatus(iStatusMsgId);
1394 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1396 if (m_retryCount == 0)
1397 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1402 KillTimer(IDT_RETRY);
1409 * @brief Sets the path status text.
1410 * The open dialog shows a status text of selected paths. This function
1411 * is used to set that status text.
1412 * @param [in] msgID Resource ID of status text to set.
1414 void COpenView::SetStatus(UINT msgID)
1416 String msg = theApp.LoadString(msgID);
1417 SetDlgItemText(IDC_OPEN_STATUS, msg);
1421 * @brief Called when "Select..." button for filters is selected.
1423 void COpenView::OnSelectFilter()
1426 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1428 const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1429 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1430 curFilter = strutils::trim_ws(curFilter);
1432 GetMainFrame()->SelectFilter();
1434 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1435 if (pGlobalFileFilter->IsUsingMask())
1437 // If we had filter chosen and now has mask we can overwrite filter
1438 if (!bUseMask || curFilter[0] != '*')
1440 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1445 String filterPrefix = _("[F] ");
1446 filterNameOrMask = filterPrefix + filterNameOrMask;
1447 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1451 void COpenView::OnOptions()
1453 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1457 * @brief Set "Whitespaces" setting.
1458 * @param [in] nID Menu ID of the selected item
1460 void COpenView::OnDiffWhitespace(UINT nID)
1462 assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
1464 m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
1468 * @brief Update "Whitespaces" state.
1469 * @param [in] pCmdUI UI component to update.
1471 void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
1473 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
1477 * @brief Toggle "Ignore blank lines" setting.
1479 void COpenView::OnDiffIgnoreBlankLines()
1481 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1485 * @brief Update "Ignore blank lines" state.
1486 * @param [in] pCmdUI UI component to update.
1488 void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
1490 pCmdUI->SetCheck(m_bIgnoreBlankLines);
1494 * @brief Toggle "Ignore case" setting.
1496 void COpenView::OnDiffIgnoreCase()
1498 m_bIgnoreCase = !m_bIgnoreCase;
1502 * @brief Update "Ignore case" state.
1503 * @param [in] pCmdUI UI component to update.
1505 void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
1507 pCmdUI->SetCheck(m_bIgnoreCase);
1511 * @brief Toggle "Ignore carriage return differences" setting.
1513 void COpenView::OnDiffIgnoreEOL()
1515 m_bIgnoreEol = !m_bIgnoreEol;
1519 * @brief Update "Ignore carriage return differences" state.
1520 * @param [in] pCmdUI UI component to update.
1522 void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
1524 pCmdUI->SetCheck(m_bIgnoreEol);
1528 * @brief Toggle "Ignore numbers" setting.
1530 void COpenView::OnDiffIgnoreNumbers()
1532 m_bIgnoreNumbers = !m_bIgnoreNumbers;
1536 * @brief Update "Ignore numbers" state.
1537 * @param [in] pCmdUI UI component to update.
1539 void COpenView::OnUpdateDiffIgnoreNumbers(CCmdUI* pCmdUI)
1541 pCmdUI->SetCheck(m_bIgnoreNumbers);
1545 * @brief Toggle "Ignore codepage differences" setting.
1547 void COpenView::OnDiffIgnoreCP()
1549 m_bIgnoreCodepage = !m_bIgnoreCodepage;
1553 * @brief Update "Ignore codepage differences" state.
1554 * @param [in] pCmdUI UI component to update.
1556 void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
1558 pCmdUI->SetCheck(m_bIgnoreCodepage);
1562 * @brief Toggle "Ignore comment differences" setting.
1564 void COpenView::OnDiffIgnoreComments()
1566 m_bFilterCommentsLines = !m_bFilterCommentsLines;
1570 * @brief Update "Ignore comment differences" state.
1571 * @param [in] pCmdUI UI component to update.
1573 void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
1575 pCmdUI->SetCheck(m_bFilterCommentsLines);
1579 * @brief Set "Compare method" setting.
1580 * @param [in] nID Menu ID of the selected item
1582 void COpenView::OnCompareMethod(UINT nID)
1584 assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
1586 m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
1590 * @brief Update "Compare method" state.
1591 * @param [in] pCmdUI UI component to update.
1593 void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
1595 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
1599 * @brief Removes whitespaces from left and right paths
1600 * @note Assumes UpdateData(TRUE) is called before this function.
1602 void COpenView::TrimPaths()
1604 for (auto& strPath: m_strPath)
1605 strPath = strutils::trim_ws(strPath);
1609 * @brief Update control states when dialog is activated.
1611 * Update control states when user re-activates dialog. User might have
1612 * switched for other program to e.g. update files/folders and then
1613 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1616 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1618 __super::OnActivate(nState, pWndOther, bMinimized);
1620 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1621 UpdateButtonStates();
1624 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1626 CWnd *pCtl = GetFocus();
1627 if (pCtl != nullptr)
1628 pCtl->PostMessage(msg, wParam, lParam);
1631 template <int MSG, int WPARAM, int LPARAM>
1632 void COpenView::OnEditAction()
1634 OnEditAction(MSG, WPARAM, LPARAM);
1638 * @brief Open help from mainframe when user presses F1.
1640 void COpenView::OnHelp()
1642 theApp.ShowHelp(OpenDlgHelpLocation);
1645 /////////////////////////////////////////////////////////////////////////////
1647 // OnDropFiles code from CDropEdit
1648 // Copyright 1997 Chris Losinger
1650 // shortcut expansion code modified from :
1651 // CShortcut, 1996 Rob Warner
1655 * @brief Drop paths(s) to the dialog.
1656 * One or two paths can be dropped to the dialog. The behaviour is:
1658 * - drop to empty path edit box (check left first)
1659 * - if both boxes have a path, drop to left path
1661 * - overwrite both paths, empty or not
1662 * @param [in] dropInfo Dropped data, including paths.
1664 void COpenView::OnDropFiles(const std::vector<String>& files)
1666 const size_t fileCount = files.size();
1668 // Add dropped paths to the dialog
1672 m_strPath[0] = files[0];
1673 m_strPath[1] = files[1];
1674 m_strPath[2] = files[2];
1676 UpdateButtonStates();
1678 else if (fileCount == 2)
1680 m_strPath[0] = files[0];
1681 m_strPath[1] = files[1];
1683 UpdateButtonStates();
1685 else if (fileCount == 1)
1688 GetCursorPos(&point);
1689 ScreenToClient(&point);
1690 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1691 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1693 switch (int const id = pwndHit->GetDlgCtrlID())
1695 case IDC_PATH0_COMBO:
1696 case IDC_PATH1_COMBO:
1697 case IDC_PATH2_COMBO:
1698 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1701 if (m_strPath[0].empty())
1702 m_strPath[0] = files[0];
1703 else if (m_strPath[1].empty())
1704 m_strPath[1] = files[0];
1705 else if (m_strPath[2].empty())
1706 m_strPath[2] = files[0];
1708 m_strPath[0] = files[0];
1713 UpdateButtonStates();