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"
33 #include "MergeAppCOMClass.h"
35 #include "LanguageSelect.h"
36 #include "Win_VersionHelper.h"
37 #include "OptionsProject.h"
38 #include "Merge7zFormatMergePluginImpl.h"
45 #define BCN_DROPDOWN (BCN_FIRST + 0x0002)
48 // Timer ID and timeout for delaying path validity check
49 const UINT IDT_CHECKFILES = 1;
50 const UINT IDT_RETRY = 2;
51 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
52 const int RETRY_MAX = 3;
56 IMPLEMENT_DYNCREATE(COpenView, CFormView)
58 BEGIN_MESSAGE_MAP(COpenView, CFormView)
59 //{{AFX_MSG_MAP(COpenView)
60 ON_CONTROL_RANGE(BN_CLICKED, IDC_PATH0_BUTTON, IDC_PATH2_BUTTON, OnPathButton)
61 ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
62 ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
63 ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
64 ON_CONTROL_RANGE(CBN_SELCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSelchangePathCombo)
65 ON_CONTROL_RANGE(CBN_EDITCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnEditEvent)
66 ON_CONTROL_RANGE(BN_CLICKED, IDC_SELECT_UNPACKER, IDC_SELECT_PREDIFFER, OnSelectPlugin)
67 ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
68 ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
69 ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
70 ON_NOTIFY_RANGE(CBEN_BEGINEDIT, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSetfocusPathCombo)
71 ON_NOTIFY_RANGE(CBEN_DRAGBEGIN, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnDragBeginPathCombo)
73 ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
74 ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
75 ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, (OnDropDown<IDC_OPTIONS, IDR_POPUP_PROJECT_DIFF_OPTIONS>))
76 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
77 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
78 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
79 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
80 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
81 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
82 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
83 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
84 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnDiffIgnoreNumbers)
85 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_NUMBERS, OnUpdateDiffIgnoreNumbers)
86 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnDiffIgnoreCP)
87 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CODEPAGE, OnUpdateDiffIgnoreCP)
88 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnDiffIgnoreComments)
89 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_COMMENTS, OnUpdateDiffIgnoreComments)
90 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnCompareMethod)
91 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS, ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE, OnUpdateCompareMethod)
93 ON_COMMAND(ID_LOAD_PROJECT, OnLoadProject)
94 ON_COMMAND(ID_SAVE_PROJECT, OnSaveProject)
95 ON_COMMAND(ID_FILE_SAVE, OnSaveProject)
96 ON_NOTIFY(BCN_DROPDOWN, ID_SAVE_PROJECT, (OnDropDown<ID_SAVE_PROJECT, IDR_POPUP_PROJECT>))
97 ON_COMMAND(IDOK, OnOK)
98 ON_NOTIFY(BCN_DROPDOWN, IDOK, (OnDropDown<IDOK, IDR_POPUP_COMPARE>))
99 ON_COMMAND(IDCANCEL, OnCancel)
100 ON_COMMAND(ID_HELP, OnHelp)
101 ON_COMMAND(ID_EDIT_COPY, OnEditAction<WM_COPY>)
102 ON_COMMAND(ID_EDIT_PASTE, OnEditAction<WM_PASTE>)
103 ON_COMMAND(ID_EDIT_CUT, OnEditAction<WM_CUT>)
104 ON_COMMAND(ID_EDIT_UNDO, OnEditAction<WM_UNDO>)
105 ON_COMMAND(ID_EDIT_SELECT_ALL, (OnEditAction<EM_SETSEL, 0, -1>))
106 ON_COMMAND_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnCompare)
107 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_WEBPAGE, OnUpdateCompare)
108 ON_COMMAND_RANGE(ID_UNPACKERS_FIRST, ID_UNPACKERS_LAST, OnCompare)
109 ON_COMMAND_RANGE(ID_OPEN_WITH_UNPACKER, ID_OPEN_WITH_UNPACKER, OnCompare)
110 ON_MESSAGE(WM_USER + 1, OnUpdateStatus)
114 ON_WM_WINDOWPOSCHANGING()
115 ON_WM_WINDOWPOSCHANGED()
121 // COpenView construction/destruction
123 COpenView::COpenView()
124 : CFormView(COpenView::IDD)
125 , m_pUpdateButtonStatusThread(nullptr)
127 , m_pDropHandler(nullptr)
129 , m_bAutoCompleteReady()
130 , m_bReadOnly {false, false, false}
131 , m_hIconRotate(theApp.LoadIcon(IDI_ROTATE2))
132 , m_hCursorNo(LoadCursor(nullptr, IDC_NO))
135 , m_bIgnoreBlankLines(false)
136 , m_bIgnoreCase(false)
137 , m_bIgnoreEol(false)
138 , m_bIgnoreNumbers(false)
139 , m_bIgnoreCodepage(false)
140 , m_bFilterCommentsLines(false)
141 , m_nCompareMethod(0)
143 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
144 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
145 m_bInsideUpdate = TRUE;
148 COpenView::~COpenView()
150 TerminateThreadIfRunning();
153 void COpenView::DoDataExchange(CDataExchange* pDX)
155 __super::DoDataExchange(pDX);
156 //{{AFX_DATA_MAP(COpenView)
157 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
158 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
159 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
160 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
161 DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
162 DDX_Control(pDX, IDC_PREDIFFER_COMBO, m_ctlPredifferPipeline);
163 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
164 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
165 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
166 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
167 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
168 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
169 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
170 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
171 DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
172 DDX_CBStringExact(pDX, IDC_PREDIFFER_COMBO, m_strPredifferPipeline);
176 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
178 // TODO: Modify the Window class or styles here by modifying
179 // the CREATESTRUCT cs
180 cs.style &= ~WS_BORDER;
181 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
182 return __super::PreCreateWindow(cs);
185 void COpenView::OnInitialUpdate()
187 if (!IsVista_OrGreater())
190 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
191 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
192 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
195 m_sizeOrig = GetTotalSize();
197 theApp.TranslateDialog(m_hWnd);
199 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
201 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
202 m_image.Create(1, 1, 24, 0);
205 __super::OnInitialUpdate();
207 // set caption to "swap paths" button
209 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
210 lf.lfCharSet = SYMBOL_CHARSET;
211 lstrcpy(lf.lfFaceName, _T("Wingdings"));
212 m_fontSwapButton.CreateFontIndirect(&lf);
213 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
214 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
216 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
217 SetDlgItemText(ids[i], _T("\xf4"));
220 m_constraint.InitializeCurrentSize(this);
221 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
222 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
223 m_constraint.SetScrollScale(this, 1.0, 1.0);
224 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
225 m_constraint.DisallowHeightGrowth();
226 //m_constraint.SubclassWnd(); // install subclassing
228 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
229 m_constraint.UpdateSizes();
231 COpenDoc *pDoc = GetDocument();
234 GetWindowText(strTitle);
235 pDoc->SetTitle(strTitle);
237 m_files = pDoc->m_files;
238 m_bRecurse = pDoc->m_bRecurse;
239 m_strExt = pDoc->m_strExt;
240 m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
241 m_strPredifferPipeline = pDoc->m_strPredifferPipeline;
242 m_dwFlags[0] = pDoc->m_dwFlags[0];
243 m_dwFlags[1] = pDoc->m_dwFlags[1];
244 m_dwFlags[2] = pDoc->m_dwFlags[2];
246 m_ctlPath[0].SetFileControlStates();
247 m_ctlPath[1].SetFileControlStates(true);
248 m_ctlPath[2].SetFileControlStates(true);
249 m_ctlUnpackerPipeline.SetFileControlStates(true);
250 m_ctlPredifferPipeline.SetFileControlStates(true);
252 for (int file = 0; file < m_files.GetSize(); file++)
254 m_strPath[file] = m_files[file];
255 m_ctlPath[file].SetWindowText(m_files[file].c_str());
256 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
259 m_ctlPath[0].AttachSystemImageList();
260 m_ctlPath[1].AttachSystemImageList();
261 m_ctlPath[2].AttachSystemImageList();
262 LoadComboboxStates();
264 m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
265 m_ctlPredifferPipeline.SetWindowText(m_strPredifferPipeline.c_str());
267 bool bDoUpdateData = true;
268 for (auto& strPath: m_strPath)
270 if (!strPath.empty())
271 bDoUpdateData = false;
273 UpdateData(bDoUpdateData);
275 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
276 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
277 bool bMask = pGlobalFileFilter->IsUsingMask();
281 String filterPrefix = _("[F] ");
282 filterNameOrMask = filterPrefix + filterNameOrMask;
285 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
287 m_ctlExt.SetCurSel(ind);
290 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
292 m_ctlExt.SetCurSel(ind);
294 LogErrorString(_T("Failed to add string to filters combo list!"));
297 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
299 EnableDlgItem(IDOK, true);
300 EnableDlgItem(IDC_UNPACKER_COMBO, true);
301 EnableDlgItem(IDC_SELECT_UNPACKER, true);
304 UpdateButtonStates();
306 bool bOverwriteRecursive = false;
307 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
308 bOverwriteRecursive = true;
309 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
310 bOverwriteRecursive = true;
311 if (!bOverwriteRecursive)
312 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
314 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
315 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
316 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
317 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
318 m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
319 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
320 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
321 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
324 SetStatus(IDS_OPEN_FILESDIRS);
326 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
327 RegisterDragDrop(m_hWnd, m_pDropHandler);
330 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
332 m_bRecurse = GetDocument()->m_bRecurse;
334 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
335 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
336 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
337 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
338 m_bIgnoreNumbers = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_NUMBERS);
339 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
340 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
341 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
346 // COpenView diagnostics
349 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
351 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
352 return (COpenDoc*)m_pDocument;
356 /////////////////////////////////////////////////////////////////////////////
357 // COpenView message handlers
359 void COpenView::OnPaint()
365 // Draw the logo image
366 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
367 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
368 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
369 // And extend it to the Right boundary
370 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
372 // Draw the resize gripper in the Lower Right corner.
374 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
375 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
376 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
378 // Draw a line to separate the Status Line
379 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
380 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
383 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
384 ScreenToClient(&rcStatus);
385 dc.MoveTo(0, rcStatus.top - 3);
386 dc.LineTo(rc.right, rcStatus.top - 3);
387 dc.SelectObject(oldpen);
392 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
394 if (::GetCapture() == m_hWnd)
396 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
397 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
399 switch (int const id1 = pwndHit->GetDlgCtrlID())
401 case IDC_PATH0_COMBO:
402 case IDC_PATH1_COMBO:
403 case IDC_PATH2_COMBO:
405 CWnd *pwndChild = GetFocus();
406 if (pwndChild && IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
408 id2 = pwndChild->GetDlgCtrlID();
409 pwndChild = pwndChild->GetParent();
410 } while (pwndChild != this);
413 case IDC_PATH0_COMBO:
414 case IDC_PATH1_COMBO:
415 case IDC_PATH2_COMBO:
417 GetDlgItemText(id1, s1);
418 GetDlgItemText(id2, s2);
419 SetDlgItemText(id1, s2);
420 SetDlgItemText(id2, s1);
431 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
433 if (::GetCapture() == m_hWnd)
435 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
436 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
438 switch (pwndHit->GetDlgCtrlID())
440 case IDC_PATH0_COMBO:
441 case IDC_PATH1_COMBO:
442 case IDC_PATH2_COMBO:
443 if (!pwndHit->IsChild(GetFocus()))
445 SetCursor(m_hIconRotate);
450 SetCursor(m_hCursorNo);
457 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
459 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
461 CFrameWnd *const pFrameWnd = GetParentFrame();
462 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
465 pFrameWnd->GetClientRect(&rc);
466 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
467 lpwndpos->cy = m_sizeOrig.cy;
468 if (lpwndpos->flags & SWP_NOOWNERZORDER)
470 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
471 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
472 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
476 else if (pFrameWnd->IsZoomed())
478 lpwndpos->cx = m_totalLog.cx;
479 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
483 if (lpwndpos->cx > rc.Width())
484 lpwndpos->cx = rc.Width();
485 if (lpwndpos->cx < m_sizeOrig.cx)
486 lpwndpos->cx = m_sizeOrig.cx;
487 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
494 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
496 if (lpwndpos->flags & SWP_FRAMECHANGED)
498 m_constraint.UpdateSizes();
499 CFrameWnd *const pFrameWnd = GetParentFrame();
500 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
502 m_constraint.Persist(true, false);
503 WINDOWPLACEMENT wp = { sizeof wp };
504 pFrameWnd->GetWindowPlacement(&wp);
507 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
508 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
509 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
510 pFrameWnd->SetWindowPlacement(&wp);
513 __super::OnWindowPosChanged(lpwndpos);
516 void COpenView::OnDestroy()
518 if (m_pDropHandler != nullptr)
519 RevokeDragDrop(m_hWnd);
521 __super::OnDestroy();
524 LRESULT COpenView::OnNcHitTest(CPoint point)
526 if (GetParentFrame()->IsZoomed())
530 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
531 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
532 if (PtInRect(&rc, point))
535 return __super::OnNcHitTest(point);
539 * @brief Called when "Browse..." button is selected for N path.
541 void COpenView::OnPathButton(UINT nId)
543 const int index = nId - IDC_PATH0_BUTTON;
548 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
551 case paths::IS_EXISTING_DIR:
552 sfolder = m_strPath[index];
554 case paths::IS_EXISTING_FILE:
555 sfolder = paths::GetPathOnly(m_strPath[index]);
557 case paths::DOES_NOT_EXIST:
558 if (!m_strPath[index].empty())
559 sfolder = paths::GetParentPath(m_strPath[index]);
562 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
566 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
568 m_strPath[index] = s;
569 m_strBrowsePath[index] = std::move(s);
571 UpdateButtonStates();
575 void COpenView::OnSwapButton(int id1, int id2)
578 GetDlgItemText(id1, s1);
579 GetDlgItemText(id2, s2);
581 SetDlgItemText(id1, s1);
582 SetDlgItemText(id2, s2);
585 template<int id1, int id2>
586 void COpenView::OnSwapButton()
588 OnSwapButton(id1, id2);
591 void COpenView::OnCompare(UINT nID)
593 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
594 const String filterPrefix = _("[F] ");
595 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
601 for (auto& strPath : m_strPath)
603 if (nFiles >= 1 && strPath.empty())
605 m_files.SetSize(nFiles + 1);
606 m_files[nFiles] = strPath;
607 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
608 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
611 // If left path is a project-file, load it
613 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
616 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
618 theApp.LoadAndOpenProjectFile(m_strPath[0]);
620 else if (!paths::IsDirectory(m_strPath[0]))
622 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
623 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
625 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
628 PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
629 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo, &tmpPrediffingInfo);
634 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
635 PrediffingInfo tmpPrediffingInfo(m_strPredifferPipeline);
637 Merge7zFormatMergePluginScope scope(&tmpPackingInfo);
639 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
641 if (pathsType == paths::DOES_NOT_EXIST &&
642 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); }))
644 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
649 for (int index = 0; index < nFiles; index++)
651 // If user has edited path by hand, expand environment variables
652 bool bExpand = false;
653 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
656 if (!paths::IsURLorCLSID(m_files[index]))
658 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
660 // Add trailing '\' for directories if its missing
661 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR && !IsArchiveFile(m_files[index]))
662 m_files[index] = paths::AddTrailingSlash(m_files[index]);
663 m_strPath[index] = m_files[index];
668 KillTimer(IDT_CHECKFILES);
669 KillTimer(IDT_RETRY);
671 String filter(strutils::trim_ws(m_strExt));
673 // If prefix found from start..
674 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
676 // Remove prefix + space
677 filter.erase(0, filterPrefix.length());
678 if (!pGlobalFileFilter->SetFilter(filter))
680 // If filtername is not found use default *.* mask
681 pGlobalFileFilter->SetFilter(_T("*.*"));
684 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
688 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
690 m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
691 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
694 SaveComboboxStates();
695 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
696 LoadComboboxStates();
698 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
699 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
700 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
701 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
702 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_NUMBERS, m_bIgnoreNumbers);
703 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
704 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
705 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
707 m_constraint.Persist(true, false);
709 COpenDoc *pDoc = GetDocument();
710 pDoc->m_files = m_files;
711 pDoc->m_bRecurse = m_bRecurse;
712 pDoc->m_strExt = m_strExt;
713 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
714 pDoc->m_strPredifferPipeline = m_strPredifferPipeline;
715 pDoc->m_dwFlags[0] = m_dwFlags[0];
716 pDoc->m_dwFlags[1] = m_dwFlags[1];
717 pDoc->m_dwFlags[2] = m_dwFlags[2];
719 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
720 GetParentFrame()->PostMessage(WM_CLOSE);
722 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
723 PathContext tmpPathContext(pDoc->m_files);
724 std::array<fileopenflags_t, 3> dwFlags = pDoc->m_dwFlags;
725 bool recurse = pDoc->m_bRecurse;
726 std::unique_ptr<CMainFrame::OpenFolderParams> pOpenFolderParams;
727 if (!pDoc->m_hiddenItems.empty())
729 pOpenFolderParams = std::make_unique<CMainFrame::OpenFolderParams>();
730 pOpenFolderParams->m_hiddenItems = pDoc->m_hiddenItems;
734 GetMainFrame()->DoFileOrFolderOpen(
735 &tmpPathContext, dwFlags.data(),
736 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
738 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
740 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
741 GetMainFrame()->DoFileOrFolderOpen(
742 &tmpPathContext, dwFlags.data(),
743 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
745 else if (nID == ID_OPEN_WITH_UNPACKER)
747 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0],
748 CSelectPluginDlg::PluginType::Unpacker, false, this);
749 if (dlg.DoModal() == IDOK)
751 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
752 GetMainFrame()->DoFileOrFolderOpen(
753 &tmpPathContext, dwFlags.data(),
754 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, &tmpPrediffingInfo, 0, pOpenFolderParams.get());
759 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo, &tmpPrediffingInfo, pOpenFolderParams.get());
763 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
765 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
769 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
770 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
771 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
774 pCmdUI->Enable(bFile);
778 * @brief Called when dialog is closed with "OK".
780 * Checks that paths are valid and sets filters.
782 void COpenView::OnOK()
788 * @brief Called when dialog is closed via Cancel.
790 * Open-dialog is closed when `Cancel` button is selected or the
791 * `Esc` key is pressed. Save combobox states, since user may have
792 * removed items from them (with `shift-del`) and doesn't want them
794 * This is *not* called when the program is terminated, even if the
795 * dialog is visible at the time.
797 void COpenView::OnCancel()
799 SaveComboboxStates();
800 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
804 * @brief Called when Open-button for project file is selected.
806 void COpenView::OnLoadProject()
810 String fileName = AskProjectFileName(true);
811 if (fileName.empty())
815 if (!theApp.LoadProjectFile(fileName, project))
817 if (project.Items().size() == 0)
820 ProjectFileItem& projItem = *project.Items().begin();
821 bool bRecurse = m_bRecurse;
822 projItem.GetPaths(paths, bRecurse);
823 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::IncludeSubfolders))
824 m_bRecurse = bRecurse;
825 if (paths.GetSize() < 3)
827 m_strPath[0] = paths[0];
828 m_strPath[1] = paths[1];
829 m_strPath[2].clear();
830 m_bReadOnly[0] = projItem.GetLeftReadOnly();
831 m_bReadOnly[1] = projItem.GetRightReadOnly();
832 m_bReadOnly[2] = false;
836 m_strPath[0] = paths[0];
837 m_strPath[1] = paths[1];
838 m_strPath[2] = paths[2];
839 m_bReadOnly[0] = projItem.GetLeftReadOnly();
840 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
841 m_bReadOnly[2] = projItem.GetRightReadOnly();
843 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::FileFilter) && projItem.HasFilter())
844 m_strExt = projItem.GetFilter();
845 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::Plugin))
847 if (projItem.HasUnpacker())
848 m_strUnpackerPipeline = projItem.GetUnpacker();
849 if (projItem.HasPrediffer())
850 m_strPredifferPipeline = projItem.GetPrediffer();
853 if (projItem.HasWindowType())
854 GetDocument()->m_nWindowType = projItem.GetWindowType();
855 if (projItem.HasTableDelimiter())
856 GetDocument()->m_cTableDelimiter = projItem.GetTableDelimiter();
857 if (projItem.HasTableQuote())
858 GetDocument()->m_cTableQuote = projItem.GetTableQuote();
859 if (projItem.HasTableAllowNewLinesInQuotes())
860 GetDocument()->m_bTableAllowNewLinesInQuotes = projItem.GetTableAllowNewLinesInQuotes();
862 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::CompareOptions))
864 if (projItem.HasIgnoreWhite())
865 m_nIgnoreWhite = projItem.GetIgnoreWhite();
866 if (projItem.HasIgnoreBlankLines())
867 m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
868 if (projItem.HasIgnoreCase())
869 m_bIgnoreCase = projItem.GetIgnoreCase();
870 if (projItem.HasIgnoreEol())
871 m_bIgnoreEol = projItem.GetIgnoreEol();
872 if (projItem.HasIgnoreNumbers())
873 m_bIgnoreNumbers = projItem.GetIgnoreNumbers();
874 if (projItem.HasIgnoreCodepage())
875 m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
876 if (projItem.HasFilterCommentsLines())
877 m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
878 if (projItem.HasCompareMethod())
879 m_nCompareMethod = projItem.GetCompareMethod();
882 if ((Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::HiddenItems)) && projItem.HasHiddenItems())
884 GetDocument()->m_hiddenItems = projItem.GetHiddenItems();
887 UpdateButtonStates();
888 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
892 * @brief Called when Save-button for project file is selected.
894 void COpenView::OnSaveProject()
898 String fileName = AskProjectFileName(false);
899 if (fileName.empty())
903 ProjectFileItem projItem;
905 bool bSaveFileFilter = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::FileFilter);
906 bool bSaveIncludeSubfolders = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::IncludeSubfolders);
907 bool bSavePlugin = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::Plugin);
908 bool bSaveCompareOptions = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::CompareOptions);
909 bool bSaveHiddenItems = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::HiddenItems);
911 projItem.SetSaveFilter(bSaveFileFilter);
912 projItem.SetSaveSubfolders(bSaveIncludeSubfolders);
913 projItem.SetSaveUnpacker(bSavePlugin);
914 projItem.SetSavePrediffer(bSavePlugin);
915 projItem.SetSaveIgnoreWhite(bSaveCompareOptions);
916 projItem.SetSaveIgnoreBlankLines(bSaveCompareOptions);
917 projItem.SetSaveIgnoreCase(bSaveCompareOptions);
918 projItem.SetSaveIgnoreEol(bSaveCompareOptions);
919 projItem.SetSaveIgnoreNumbers(bSaveCompareOptions);
920 projItem.SetSaveIgnoreCodepage(bSaveCompareOptions);
921 projItem.SetSaveFilterCommentsLines(bSaveCompareOptions);
922 projItem.SetSaveCompareMethod(bSaveCompareOptions);
923 projItem.SetSaveHiddenItems(bSaveHiddenItems);
925 if (!m_strPath[0].empty())
926 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
927 if (!GetDocument()->m_strDesc[0].empty())
928 projItem.SetLeftDesc(GetDocument()->m_strDesc[0]);
929 if (m_strPath[2].empty())
931 if (!m_strPath[1].empty())
932 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
933 if (!GetDocument()->m_strDesc[1].empty())
934 projItem.SetRightDesc(GetDocument()->m_strDesc[1]);
938 if (!m_strPath[1].empty())
939 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
940 if (!m_strPath[2].empty())
941 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
942 if (!GetDocument()->m_strDesc[1].empty())
943 projItem.SetMiddleDesc(GetDocument()->m_strDesc[1]);
944 if (!GetDocument()->m_strDesc[2].empty())
945 projItem.SetRightDesc(GetDocument()->m_strDesc[2]);
947 if (bSaveFileFilter && !m_strExt.empty())
949 // Remove possbile prefix from the filter name
950 String prefix = _("[F] ");
951 String strExt = m_strExt;
952 size_t ind = strExt.find(prefix, 0);
955 strExt.erase(0, prefix.length());
957 strExt = strutils::trim_ws_begin(strExt);
958 projItem.SetFilter(strExt);
960 if (bSaveIncludeSubfolders)
961 projItem.SetSubfolders(m_bRecurse);
964 if (!m_strUnpackerPipeline.empty())
965 projItem.SetUnpacker(m_strUnpackerPipeline);
966 if (!m_strPredifferPipeline.empty())
967 projItem.SetPrediffer(m_strPredifferPipeline);
969 if (GetDocument()->m_nWindowType != -1)
970 projItem.SetWindowType(GetDocument()->m_nWindowType);
971 if (GetDocument()->m_nWindowType == 2 /* table */)
973 projItem.SetTableDelimiter(GetDocument()->m_cTableDelimiter);
974 projItem.SetTableQuote(GetDocument()->m_cTableQuote);
975 projItem.SetTableAllowNewLinesInQuotes(GetDocument()->m_bTableAllowNewLinesInQuotes);
978 if (bSaveCompareOptions)
980 projItem.SetIgnoreWhite(m_nIgnoreWhite);
981 projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
982 projItem.SetIgnoreCase(m_bIgnoreCase);
983 projItem.SetIgnoreEol(m_bIgnoreEol);
984 projItem.SetIgnoreNumbers(m_bIgnoreNumbers);
985 projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
986 projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
987 projItem.SetCompareMethod(m_nCompareMethod);
990 if (bSaveHiddenItems)
991 projItem.SetHiddenItems(GetDocument()->m_hiddenItems);
993 project.Items().push_back(projItem);
995 if (!theApp.SaveProjectFile(fileName, project))
998 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
1001 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
1004 GetDlgItem(nID)->GetWindowRect(&rcButton);
1006 VERIFY(menu.LoadMenu(nPopupID));
1007 theApp.TranslateMenu(menu.m_hMenu);
1008 CMenu* pPopup = menu.GetSubMenu(0);
1009 if (pPopup != nullptr)
1011 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
1015 for (int i = 0; i < 3; i++)
1016 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
1017 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
1018 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
1020 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
1021 rcButton.left, rcButton.bottom, GetMainFrame());
1026 template<UINT id, UINT popupid>
1027 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
1029 DropDown(pNMHDR, pResult, id, popupid);
1033 * @brief Allow user to select a file to open/save.
1035 String COpenView::AskProjectFileName(bool bOpen)
1037 // get the default projects path
1038 String strProjectFileName;
1039 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
1041 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
1042 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
1045 if (strProjectFileName.empty())
1048 // get the path part from the filename
1049 strProjectPath = paths::GetParentPath(strProjectFileName);
1050 // store this as the new project path
1051 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
1052 return strProjectFileName;
1056 * @brief Load File- and filter-combobox states.
1058 void COpenView::LoadComboboxStates()
1060 m_ctlPath[0].LoadState(_T("Files\\Left"));
1061 m_ctlPath[1].LoadState(_T("Files\\Right"));
1062 m_ctlPath[2].LoadState(_T("Files\\Option"));
1063 m_ctlExt.LoadState(_T("Files\\Ext"));
1064 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
1065 m_ctlPredifferPipeline.LoadState(_T("Files\\Prediffer"));
1069 * @brief Save File- and filter-combobox states.
1071 void COpenView::SaveComboboxStates()
1073 m_ctlPath[0].SaveState(_T("Files\\Left"));
1074 m_ctlPath[1].SaveState(_T("Files\\Right"));
1075 m_ctlPath[2].SaveState(_T("Files\\Option"));
1076 m_ctlExt.SaveState(_T("Files\\Ext"));
1077 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
1078 m_ctlPredifferPipeline.SaveState(_T("Files\\Prediffer"));
1081 struct UpdateButtonStatesThreadParams
1084 PathContext m_paths;
1087 static UINT UpdateButtonStatesThread(LPVOID lpParam)
1089 if (FAILED(CoInitialize(nullptr)))
1092 CAssureScriptsForThread scriptsForRescan(new MergeAppCOMClass());
1095 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
1099 if (msg.message != WM_USER + 2)
1102 bool bIsaFolderCompare = true;
1103 bool bIsaFileCompare = true;
1104 bool bInvalid[3] = {false, false, false};
1105 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
1106 int iStatusMsgId = IDS_OPEN_FILESDIRS;
1108 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
1109 PathContext paths = pParams->m_paths;
1110 HWND hWnd = pParams->m_hWnd;
1113 // Check if we have project file as left side path
1114 bool bProject = false;
1116 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
1117 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1122 for (int i = 0; i < paths.GetSize(); ++i)
1124 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
1125 if (pathType[i] == paths::DOES_NOT_EXIST)
1127 if (paths::IsURL(paths[i]))
1128 pathType[i] = paths::IS_EXISTING_FILE;
1135 // Enable buttons as appropriate
1136 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1138 paths::PATH_EXISTENCE pathsType = pathType[0];
1140 if (paths.GetSize() <= 2)
1142 if (bInvalid[0] && bInvalid[1])
1143 iStatusMsgId = IDS_OPEN_BOTHINVALID;
1144 else if (bInvalid[0])
1145 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1146 else if (bInvalid[1])
1148 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
1149 iStatusMsgId = IDS_OPEN_FILESDIRS;
1151 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
1153 else if (!bInvalid[0] && !bInvalid[1])
1155 if (pathType[0] != pathType[1])
1156 iStatusMsgId = IDS_OPEN_MISMATCH;
1158 iStatusMsgId = IDS_OPEN_FILESDIRS;
1163 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
1164 iStatusMsgId = IDS_OPEN_ALLINVALID;
1165 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
1166 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
1167 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
1168 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
1169 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
1170 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
1171 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
1172 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
1173 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
1174 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
1175 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1176 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1177 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1179 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
1180 iStatusMsgId = IDS_OPEN_MISMATCH;
1182 iStatusMsgId = IDS_OPEN_FILESDIRS;
1185 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1186 pathsType = paths::DOES_NOT_EXIST;
1187 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1188 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1189 // Both will be `false` if incompatibilities or something is missing
1190 // Both will end up `true` if file validity isn't being checked
1193 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1202 * @brief Update any resources necessary after a GUI language change
1204 void COpenView::UpdateResources()
1206 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1210 * @brief Enable/disable components based on validity of paths.
1212 void COpenView::UpdateButtonStates()
1214 UpdateData(TRUE); // load member variables from screen
1215 KillTimer(IDT_CHECKFILES);
1218 if (m_pUpdateButtonStatusThread == nullptr)
1220 m_pUpdateButtonStatusThread = AfxBeginThread(
1221 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1222 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1223 m_pUpdateButtonStatusThread->ResumeThread();
1224 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1228 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1229 pParams->m_hWnd = this->m_hWnd;
1230 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1232 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1235 void COpenView::TerminateThreadIfRunning()
1237 if (m_pUpdateButtonStatusThread == nullptr)
1240 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1241 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1242 if (dwResult != WAIT_OBJECT_0)
1244 m_pUpdateButtonStatusThread->SuspendThread();
1245 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1247 delete m_pUpdateButtonStatusThread;
1248 m_pUpdateButtonStatusThread = nullptr;
1252 * @brief Called when user changes selection in left/middle/right path's combo box.
1254 void COpenView::OnSelchangePathCombo(UINT nId)
1256 const int index = nId - IDC_PATH0_COMBO;
1257 int sel = m_ctlPath[index].GetCurSel();
1261 m_ctlPath[index].GetLBText(sel, cstrPath);
1262 m_strPath[index] = cstrPath;
1263 m_ctlPath[index].SetWindowText(cstrPath);
1266 UpdateButtonStates();
1269 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1271 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1273 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1275 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1276 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1281 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1283 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1289 * @brief Called every time paths are edited.
1291 void COpenView::OnEditEvent(UINT nID)
1293 const int N = nID - IDC_PATH0_COMBO;
1294 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1296 int const len = edit->GetWindowTextLength();
1297 if (edit->GetSel() == MAKEWPARAM(len, len))
1300 edit->GetWindowText(text);
1301 // Remove any double quotes
1303 if (text.GetLength() != len)
1305 edit->SetSel(0, len);
1306 edit->ReplaceSel(text);
1310 // (Re)start timer to path validity check delay
1311 // If timer starting fails, update buttonstates immediately
1312 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1313 UpdateButtonStates();
1317 * @brief Handle timer events.
1318 * Checks if paths are valid and sets control states accordingly.
1319 * @param [in] nIDEvent Timer ID that fired.
1321 void COpenView::OnTimer(UINT_PTR nIDEvent)
1323 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1324 UpdateButtonStates();
1326 __super::OnTimer(nIDEvent);
1330 * @brief Called when users selects plugin browse button.
1332 void COpenView::OnSelectPlugin(UINT nID)
1334 paths::PATH_EXISTENCE pathsType;
1338 for (auto& strPath: m_strPath)
1340 if (nFiles == 2 && strPath.empty())
1342 m_files.SetSize(nFiles + 1);
1343 m_files[nFiles] = strPath;
1346 PathContext tmpFiles = m_files;
1347 if (tmpFiles.GetSize() == 2 && tmpFiles[1].empty())
1348 tmpFiles[1] = tmpFiles[0];
1349 pathsType = paths::GetPairComparability(tmpFiles, IsArchiveFile);
1351 if (pathsType == paths::IS_EXISTING_DIR || (pathsType == paths::DOES_NOT_EXIST &&
1352 !std::any_of(m_files.begin(), m_files.end(), [](const auto& path) { return paths::IsURL(path); })))
1355 // let the user select a handler
1356 CSelectPluginDlg dlg(nID == IDC_SELECT_UNPACKER ? m_strUnpackerPipeline : m_strPredifferPipeline, m_files[0],
1357 nID == IDC_SELECT_UNPACKER ? CSelectPluginDlg::PluginType::Unpacker : CSelectPluginDlg::PluginType::Prediffer, false, this);
1358 if (dlg.DoModal() == IDOK)
1360 if (nID == IDC_SELECT_UNPACKER)
1361 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1363 m_strPredifferPipeline = dlg.GetPluginPipeline();
1368 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1370 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1371 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1372 const bool bProject = HIWORD(lParam) != 0;
1373 const int iStatusMsgId = LOWORD(lParam);
1375 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1377 for (auto nID : { IDC_FILES_DIRS_GROUP4, IDC_UNPACKER_COMBO, IDC_SELECT_UNPACKER })
1379 EnableDlgItem(nID, bIsaFileCompare);
1382 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1384 for (auto nID : { IDC_FILES_DIRS_GROUP5, IDC_PREDIFFER_COMBO, IDC_SELECT_PREDIFFER })
1386 GetDlgItem(nID)->ShowWindow(bIsaFileCompare ? SW_SHOW : SW_HIDE);
1387 EnableDlgItem(nID, bIsaFileCompare);
1390 for (auto nID : { IDC_FILES_DIRS_GROUP3, IDC_EXT_COMBO, IDC_SELECT_FILTER, IDC_RECURS_CHECK })
1392 GetDlgItem(nID)->ShowWindow((bIsaFolderCompare || !bIsaFileCompare) ? SW_SHOW : SW_HIDE);
1393 EnableDlgItem(nID, bIsaFolderCompare);
1397 SetStatus(iStatusMsgId);
1399 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1401 if (m_retryCount == 0)
1402 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1407 KillTimer(IDT_RETRY);
1414 * @brief Sets the path status text.
1415 * The open dialog shows a status text of selected paths. This function
1416 * is used to set that status text.
1417 * @param [in] msgID Resource ID of status text to set.
1419 void COpenView::SetStatus(UINT msgID)
1421 String msg = theApp.LoadString(msgID);
1422 SetDlgItemText(IDC_OPEN_STATUS, msg);
1426 * @brief Called when "Select..." button for filters is selected.
1428 void COpenView::OnSelectFilter()
1431 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1433 const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1434 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1435 curFilter = strutils::trim_ws(curFilter);
1437 GetMainFrame()->SelectFilter();
1439 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1440 if (pGlobalFileFilter->IsUsingMask())
1442 // If we had filter chosen and now has mask we can overwrite filter
1443 if (!bUseMask || curFilter[0] != '*')
1445 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1450 String filterPrefix = _("[F] ");
1451 filterNameOrMask = filterPrefix + filterNameOrMask;
1452 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1456 void COpenView::OnOptions()
1458 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1462 * @brief Set "Whitespaces" setting.
1463 * @param [in] nID Menu ID of the selected item
1465 void COpenView::OnDiffWhitespace(UINT nID)
1467 assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
1469 m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
1473 * @brief Update "Whitespaces" state.
1474 * @param [in] pCmdUI UI component to update.
1476 void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
1478 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
1482 * @brief Toggle "Ignore blank lines" setting.
1484 void COpenView::OnDiffIgnoreBlankLines()
1486 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1490 * @brief Update "Ignore blank lines" state.
1491 * @param [in] pCmdUI UI component to update.
1493 void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
1495 pCmdUI->SetCheck(m_bIgnoreBlankLines);
1499 * @brief Toggle "Ignore case" setting.
1501 void COpenView::OnDiffIgnoreCase()
1503 m_bIgnoreCase = !m_bIgnoreCase;
1507 * @brief Update "Ignore case" state.
1508 * @param [in] pCmdUI UI component to update.
1510 void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
1512 pCmdUI->SetCheck(m_bIgnoreCase);
1516 * @brief Toggle "Ignore carriage return differences" setting.
1518 void COpenView::OnDiffIgnoreEOL()
1520 m_bIgnoreEol = !m_bIgnoreEol;
1524 * @brief Update "Ignore carriage return differences" state.
1525 * @param [in] pCmdUI UI component to update.
1527 void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
1529 pCmdUI->SetCheck(m_bIgnoreEol);
1533 * @brief Toggle "Ignore numbers" setting.
1535 void COpenView::OnDiffIgnoreNumbers()
1537 m_bIgnoreNumbers = !m_bIgnoreNumbers;
1541 * @brief Update "Ignore numbers" state.
1542 * @param [in] pCmdUI UI component to update.
1544 void COpenView::OnUpdateDiffIgnoreNumbers(CCmdUI* pCmdUI)
1546 pCmdUI->SetCheck(m_bIgnoreNumbers);
1550 * @brief Toggle "Ignore codepage differences" setting.
1552 void COpenView::OnDiffIgnoreCP()
1554 m_bIgnoreCodepage = !m_bIgnoreCodepage;
1558 * @brief Update "Ignore codepage differences" state.
1559 * @param [in] pCmdUI UI component to update.
1561 void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
1563 pCmdUI->SetCheck(m_bIgnoreCodepage);
1567 * @brief Toggle "Ignore comment differences" setting.
1569 void COpenView::OnDiffIgnoreComments()
1571 m_bFilterCommentsLines = !m_bFilterCommentsLines;
1575 * @brief Update "Ignore comment differences" state.
1576 * @param [in] pCmdUI UI component to update.
1578 void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
1580 pCmdUI->SetCheck(m_bFilterCommentsLines);
1584 * @brief Set "Compare method" setting.
1585 * @param [in] nID Menu ID of the selected item
1587 void COpenView::OnCompareMethod(UINT nID)
1589 assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
1591 m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
1595 * @brief Update "Compare method" state.
1596 * @param [in] pCmdUI UI component to update.
1598 void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
1600 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
1604 * @brief Removes whitespaces from left and right paths
1605 * @note Assumes UpdateData(TRUE) is called before this function.
1607 void COpenView::TrimPaths()
1609 for (auto& strPath: m_strPath)
1610 strPath = strutils::trim_ws(strPath);
1614 * @brief Update control states when dialog is activated.
1616 * Update control states when user re-activates dialog. User might have
1617 * switched for other program to e.g. update files/folders and then
1618 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1621 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1623 __super::OnActivate(nState, pWndOther, bMinimized);
1625 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1626 UpdateButtonStates();
1629 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1631 CWnd *pCtl = GetFocus();
1632 if (pCtl != nullptr)
1633 pCtl->PostMessage(msg, wParam, lParam);
1637 * @brief Open help from mainframe when user presses F1.
1639 void COpenView::OnHelp()
1641 theApp.ShowHelp(OpenDlgHelpLocation);
1644 /////////////////////////////////////////////////////////////////////////////
1646 // OnDropFiles code from CDropEdit
1647 // Copyright 1997 Chris Losinger
1649 // shortcut expansion code modified from :
1650 // CShortcut, 1996 Rob Warner
1654 * @brief Drop paths(s) to the dialog.
1655 * One or two paths can be dropped to the dialog. The behaviour is:
1657 * - drop to empty path edit box (check left first)
1658 * - if both boxes have a path, drop to left path
1660 * - overwrite both paths, empty or not
1661 * @param [in] dropInfo Dropped data, including paths.
1663 void COpenView::OnDropFiles(const std::vector<String>& files)
1665 const size_t fileCount = files.size();
1667 // Add dropped paths to the dialog
1671 m_strPath[0] = files[0];
1672 m_strPath[1] = files[1];
1673 m_strPath[2] = files[2];
1675 UpdateButtonStates();
1677 else if (fileCount == 2)
1679 m_strPath[0] = files[0];
1680 m_strPath[1] = files[1];
1682 UpdateButtonStates();
1684 else if (fileCount == 1)
1687 GetCursorPos(&point);
1688 ScreenToClient(&point);
1689 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1690 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1692 switch (int const id = pwndHit->GetDlgCtrlID())
1694 case IDC_PATH0_COMBO:
1695 case IDC_PATH1_COMBO:
1696 case IDC_PATH2_COMBO:
1697 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1700 if (m_strPath[0].empty())
1701 m_strPath[0] = files[0];
1702 else if (m_strPath[1].empty())
1703 m_strPath[1] = files[0];
1704 else if (m_strPath[2].empty())
1705 m_strPath[2] = files[0];
1707 m_strPath[0] = files[0];
1712 UpdateButtonStates();