1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
10 * @brief Implementation of the COpenView class
17 #include "UnicodeString.h"
20 #include "ProjectFile.h"
22 #include "SelectPluginDlg.h"
23 #include "OptionsDef.h"
25 #include "OptionsMgr.h"
26 #include "FileOrFolderSelect.h"
28 #include "Constants.h"
30 #include "DropHandler.h"
31 #include "FileFilterHelper.h"
34 #include "LanguageSelect.h"
35 #include "Win_VersionHelper.h"
36 #include "OptionsProject.h"
43 #define BCN_DROPDOWN (BCN_FIRST + 0x0002)
46 // Timer ID and timeout for delaying path validity check
47 const UINT IDT_CHECKFILES = 1;
48 const UINT IDT_RETRY = 2;
49 const UINT CHECKFILES_TIMEOUT = 1000; // milliseconds
50 const int RETRY_MAX = 3;
51 static const TCHAR EMPTY_EXTENSION[] = _T(".*");
53 /** @brief Location for Open-dialog specific help to open. */
54 static TCHAR OpenDlgHelpLocation[] = _T("::/htmlhelp/Open_paths.html");
58 IMPLEMENT_DYNCREATE(COpenView, CFormView)
60 BEGIN_MESSAGE_MAP(COpenView, CFormView)
61 //{{AFX_MSG_MAP(COpenView)
62 ON_CONTROL_RANGE(BN_CLICKED, IDC_PATH0_BUTTON, IDC_PATH2_BUTTON, OnPathButton)
63 ON_BN_CLICKED(IDC_SWAP01_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH1_COMBO>))
64 ON_BN_CLICKED(IDC_SWAP12_BUTTON, (OnSwapButton<IDC_PATH1_COMBO, IDC_PATH2_COMBO>))
65 ON_BN_CLICKED(IDC_SWAP02_BUTTON, (OnSwapButton<IDC_PATH0_COMBO, IDC_PATH2_COMBO>))
66 ON_CONTROL_RANGE(CBN_SELCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSelchangePathCombo)
67 ON_CONTROL_RANGE(CBN_EDITCHANGE, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnEditEvent)
68 ON_BN_CLICKED(IDC_SELECT_UNPACKER, OnSelectUnpacker)
69 ON_CBN_SELENDCANCEL(IDC_PATH0_COMBO, UpdateButtonStates)
70 ON_CBN_SELENDCANCEL(IDC_PATH1_COMBO, UpdateButtonStates)
71 ON_CBN_SELENDCANCEL(IDC_PATH2_COMBO, UpdateButtonStates)
72 ON_NOTIFY_RANGE(CBEN_BEGINEDIT, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnSetfocusPathCombo)
73 ON_NOTIFY_RANGE(CBEN_DRAGBEGIN, IDC_PATH0_COMBO, IDC_PATH2_COMBO, OnDragBeginPathCombo)
75 ON_BN_CLICKED(IDC_SELECT_FILTER, OnSelectFilter)
76 ON_BN_CLICKED(IDC_OPTIONS, OnOptions)
77 ON_NOTIFY(BCN_DROPDOWN, IDC_OPTIONS, (OnDropDown<IDC_OPTIONS, IDR_POPUP_PROJECT_DIFF_OPTIONS>))
78 ON_COMMAND_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnDiffWhitespace)
79 ON_UPDATE_COMMAND_UI_RANGE(ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE, ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
80 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnDiffIgnoreBlankLines)
81 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_BLANKLINES, OnUpdateDiffIgnoreBlankLines)
82 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnDiffIgnoreCase)
83 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_CASE, OnUpdateDiffIgnoreCase)
84 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnDiffIgnoreEOL)
85 ON_UPDATE_COMMAND_UI(ID_PROJECT_DIFF_OPTIONS_IGNORE_EOL, OnUpdateDiffIgnoreEOL)
86 ON_COMMAND(ID_PROJECT_DIFF_OPTIONS_IGNORE_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_IMAGE, OnCompare)
107 ON_UPDATE_COMMAND_UI_RANGE(ID_MERGE_COMPARE_TEXT, ID_MERGE_COMPARE_IMAGE, 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_bIgnoreCodepage(false)
139 , m_bFilterCommentsLines(false)
140 , m_nCompareMethod(0)
142 // CWnd::EnableScrollBarCtrl() called inside CScrollView::UpdateBars() is quite slow.
143 // Therefore, set m_bInsideUpdate = TRUE so that CScrollView::UpdateBars() does almost nothing.
144 m_bInsideUpdate = TRUE;
147 COpenView::~COpenView()
149 TerminateThreadIfRunning();
152 void COpenView::DoDataExchange(CDataExchange* pDX)
154 CFormView::DoDataExchange(pDX);
155 //{{AFX_DATA_MAP(COpenView)
156 DDX_Control(pDX, IDC_EXT_COMBO, m_ctlExt);
157 DDX_Control(pDX, IDC_PATH0_COMBO, m_ctlPath[0]);
158 DDX_Control(pDX, IDC_PATH1_COMBO, m_ctlPath[1]);
159 DDX_Control(pDX, IDC_PATH2_COMBO, m_ctlPath[2]);
160 DDX_Control(pDX, IDC_UNPACKER_COMBO, m_ctlUnpackerPipeline);
161 DDX_CBStringExact(pDX, IDC_PATH0_COMBO, m_strPath[0]);
162 DDX_CBStringExact(pDX, IDC_PATH1_COMBO, m_strPath[1]);
163 DDX_CBStringExact(pDX, IDC_PATH2_COMBO, m_strPath[2]);
164 DDX_Check(pDX, IDC_PATH0_READONLY, m_bReadOnly[0]);
165 DDX_Check(pDX, IDC_PATH1_READONLY, m_bReadOnly[1]);
166 DDX_Check(pDX, IDC_PATH2_READONLY, m_bReadOnly[2]);
167 DDX_Check(pDX, IDC_RECURS_CHECK, m_bRecurse);
168 DDX_CBStringExact(pDX, IDC_EXT_COMBO, m_strExt);
169 DDX_CBStringExact(pDX, IDC_UNPACKER_COMBO, m_strUnpackerPipeline);
173 BOOL COpenView::PreCreateWindow(CREATESTRUCT& cs)
175 // TODO: Modify the Window class or styles here by modifying
176 // the CREATESTRUCT cs
177 cs.style &= ~WS_BORDER;
178 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
179 return CFormView::PreCreateWindow(cs);
182 void COpenView::OnInitialUpdate()
184 if (!IsVista_OrGreater())
187 SendDlgItemMessage(IDC_OPTIONS, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
188 SendDlgItemMessage(ID_SAVE_PROJECT, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
189 SendDlgItemMessage(IDOK, BM_SETSTYLE, BS_PUSHBUTTON, TRUE);
192 m_sizeOrig = GetTotalSize();
194 theApp.TranslateDialog(m_hWnd);
196 if (!LoadImageFromResource(m_image, MAKEINTRESOURCE(IDR_LOGO), _T("IMAGE")))
198 // FIXME: LoadImageFromResource() seems to fail when running on Wine 5.0.
199 m_image.Create(1, 1, 24, 0);
202 CFormView::OnInitialUpdate();
204 // set caption to "swap paths" button
206 GetDlgItem(IDC_SWAP01_BUTTON)->GetFont()->GetObject(sizeof(lf), &lf);
207 lf.lfCharSet = SYMBOL_CHARSET;
208 lstrcpy(lf.lfFaceName, _T("Wingdings"));
209 m_fontSwapButton.CreateFontIndirect(&lf);
210 const int ids[] = {IDC_SWAP01_BUTTON, IDC_SWAP12_BUTTON, IDC_SWAP02_BUTTON};
211 for (int i = 0; i < sizeof(ids)/sizeof(ids[0]); ++i)
213 GetDlgItem(ids[i])->SetFont(&m_fontSwapButton);
214 SetDlgItemText(ids[i], _T("\xf4"));
217 m_constraint.InitializeCurrentSize(this);
218 m_constraint.InitializeSpecificSize(this, m_sizeOrig.cx, m_sizeOrig.cy);
219 m_constraint.SetMaxSizePixels(-1, m_sizeOrig.cy);
220 m_constraint.SetScrollScale(this, 1.0, 1.0);
221 m_constraint.SetSizeGrip(prdlg::CMoveConstraint::SG_NONE);
222 m_constraint.DisallowHeightGrowth();
223 //m_constraint.SubclassWnd(); // install subclassing
225 m_constraint.LoadPosition(_T("ResizeableDialogs"), _T("OpenView"), false); // persist size via registry
226 m_constraint.UpdateSizes();
228 COpenDoc *pDoc = GetDocument();
231 GetWindowText(strTitle);
232 pDoc->SetTitle(strTitle);
234 m_files = pDoc->m_files;
235 m_bRecurse = pDoc->m_bRecurse;
236 m_strExt = pDoc->m_strExt;
237 m_strUnpackerPipeline = pDoc->m_strUnpackerPipeline;
238 m_dwFlags[0] = pDoc->m_dwFlags[0];
239 m_dwFlags[1] = pDoc->m_dwFlags[1];
240 m_dwFlags[2] = pDoc->m_dwFlags[2];
242 m_ctlPath[0].SetFileControlStates();
243 m_ctlPath[1].SetFileControlStates(true);
244 m_ctlPath[2].SetFileControlStates(true);
245 m_ctlUnpackerPipeline.SetFileControlStates(true);
247 for (int file = 0; file < m_files.GetSize(); file++)
249 m_strPath[file] = m_files[file];
250 m_ctlPath[file].SetWindowText(m_files[file].c_str());
251 m_bReadOnly[file] = (m_dwFlags[file] & FFILEOPEN_READONLY) != 0;
254 m_ctlPath[0].AttachSystemImageList();
255 m_ctlPath[1].AttachSystemImageList();
256 m_ctlPath[2].AttachSystemImageList();
257 LoadComboboxStates();
259 m_ctlUnpackerPipeline.SetWindowText(m_strUnpackerPipeline.c_str());
261 bool bDoUpdateData = true;
262 for (auto& strPath: m_strPath)
264 if (!strPath.empty())
265 bDoUpdateData = false;
267 UpdateData(bDoUpdateData);
269 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
270 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
271 bool bMask = pGlobalFileFilter->IsUsingMask();
275 String filterPrefix = _("[F] ");
276 filterNameOrMask = filterPrefix + filterNameOrMask;
279 int ind = m_ctlExt.FindStringExact(0, filterNameOrMask.c_str());
281 m_ctlExt.SetCurSel(ind);
284 ind = m_ctlExt.InsertString(0, filterNameOrMask.c_str());
286 m_ctlExt.SetCurSel(ind);
288 LogErrorString(_T("Failed to add string to filters combo list!"));
291 if (!GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
293 EnableDlgItem(IDOK, true);
294 EnableDlgItem(IDC_UNPACKER_COMBO, true);
295 EnableDlgItem(IDC_SELECT_UNPACKER, true);
298 UpdateButtonStates();
300 bool bOverwriteRecursive = false;
301 if (m_dwFlags[0] & FFILEOPEN_PROJECT || m_dwFlags[1] & FFILEOPEN_PROJECT)
302 bOverwriteRecursive = true;
303 if (m_dwFlags[0] & FFILEOPEN_CMDLINE || m_dwFlags[1] & FFILEOPEN_CMDLINE)
304 bOverwriteRecursive = true;
305 if (!bOverwriteRecursive)
306 m_bRecurse = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
308 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
309 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
310 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
311 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
312 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
313 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
314 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
317 SetStatus(IDS_OPEN_FILESDIRS);
319 m_pDropHandler = new DropHandler(std::bind(&COpenView::OnDropFiles, this, std::placeholders::_1));
320 RegisterDragDrop(m_hWnd, m_pDropHandler);
323 void COpenView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
325 m_bRecurse = GetDocument()->m_bRecurse;
327 m_nIgnoreWhite = GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE);
328 m_bIgnoreBlankLines = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_BLANKLINES);
329 m_bIgnoreCase = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE);
330 m_bIgnoreEol = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL);
331 m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
332 m_bFilterCommentsLines = GetOptionsMgr()->GetBool(OPT_CMP_FILTER_COMMENTLINES);
333 m_nCompareMethod = GetOptionsMgr()->GetInt(OPT_CMP_METHOD);
338 // COpenView diagnostics
341 COpenDoc* COpenView::GetDocument() const // non-debug version is inline
343 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(COpenDoc)));
344 return (COpenDoc*)m_pDocument;
348 /////////////////////////////////////////////////////////////////////////////
349 // COpenView message handlers
351 void COpenView::OnPaint()
357 // Draw the logo image
358 CSize size{ m_image.GetWidth(), m_image.GetHeight() };
359 CRect rcImage(0, 0, size.cx * GetSystemMetrics(SM_CXSMICON) / 16, size.cy * GetSystemMetrics(SM_CYSMICON) / 16);
360 m_image.Draw(dc.m_hDC, rcImage, Gdiplus::InterpolationModeBicubic);
361 // And extend it to the Right boundary
362 dc.PatBlt(rcImage.Width(), 0, rc.Width() - rcImage.Width(), rcImage.Height(), PATCOPY);
364 // Draw the resize gripper in the Lower Right corner.
366 rcGrip.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
367 rcGrip.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
368 dc.DrawFrameControl(&rcGrip, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
370 // Draw a line to separate the Status Line
371 CPen newPen(PS_SOLID, 1, RGB(208, 208, 208)); // a very light gray
372 CPen* oldpen = (CPen*)dc.SelectObject(&newPen);
375 GetDlgItem(IDC_OPEN_STATUS)->GetWindowRect(&rcStatus);
376 ScreenToClient(&rcStatus);
377 dc.MoveTo(0, rcStatus.top - 3);
378 dc.LineTo(rc.right, rcStatus.top - 3);
379 dc.SelectObject(oldpen);
381 CFormView::OnPaint();
384 void COpenView::OnLButtonUp(UINT nFlags, CPoint point)
386 if (::GetCapture() == m_hWnd)
388 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
389 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
391 switch (int const id1 = pwndHit->GetDlgCtrlID())
393 case IDC_PATH0_COMBO:
394 case IDC_PATH1_COMBO:
395 case IDC_PATH2_COMBO:
397 CWnd *pwndChild = GetFocus();
398 if (IsChild(pwndChild) && !pwndHit->IsChild(pwndChild)) do
400 id2 = pwndChild->GetDlgCtrlID();
401 pwndChild = pwndChild->GetParent();
402 } while (pwndChild != this);
405 case IDC_PATH0_COMBO:
406 case IDC_PATH1_COMBO:
407 case IDC_PATH2_COMBO:
409 GetDlgItemText(id1, s1);
410 GetDlgItemText(id2, s2);
411 SetDlgItemText(id1, s2);
412 SetDlgItemText(id2, s1);
423 void COpenView::OnMouseMove(UINT nFlags, CPoint point)
425 if (::GetCapture() == m_hWnd)
427 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
428 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
430 switch (pwndHit->GetDlgCtrlID())
432 case IDC_PATH0_COMBO:
433 case IDC_PATH1_COMBO:
434 case IDC_PATH2_COMBO:
435 if (!pwndHit->IsChild(GetFocus()))
437 SetCursor(m_hIconRotate);
442 SetCursor(m_hCursorNo);
449 void COpenView::OnWindowPosChanging(WINDOWPOS* lpwndpos)
451 if ((lpwndpos->flags & (SWP_NOMOVE | SWP_NOSIZE)) == 0)
453 CFrameWnd *const pFrameWnd = GetParentFrame();
454 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
457 pFrameWnd->GetClientRect(&rc);
458 lpwndpos->flags |= SWP_FRAMECHANGED | SWP_SHOWWINDOW;
459 lpwndpos->cy = m_sizeOrig.cy;
460 if (lpwndpos->flags & SWP_NOOWNERZORDER)
462 lpwndpos->x = rc.right - (lpwndpos->x + lpwndpos->cx);
463 lpwndpos->cx = rc.right - 2 * lpwndpos->x;
464 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
468 else if (pFrameWnd->IsZoomed())
470 lpwndpos->cx = m_totalLog.cx;
471 lpwndpos->y = (rc.bottom - lpwndpos->cy) / 2;
475 if (lpwndpos->cx > rc.Width())
476 lpwndpos->cx = rc.Width();
477 if (lpwndpos->cx < m_sizeOrig.cx)
478 lpwndpos->cx = m_sizeOrig.cx;
479 lpwndpos->x = (rc.right - lpwndpos->cx) / 2;
486 void COpenView::OnWindowPosChanged(WINDOWPOS* lpwndpos)
488 if (lpwndpos->flags & SWP_FRAMECHANGED)
490 m_constraint.UpdateSizes();
491 CFrameWnd *const pFrameWnd = GetParentFrame();
492 if (pFrameWnd == GetTopLevelFrame()->GetActiveFrame())
494 m_constraint.Persist(true, false);
495 WINDOWPLACEMENT wp = {};
496 wp.length = sizeof wp;
497 pFrameWnd->GetWindowPlacement(&wp);
500 pFrameWnd->CalcWindowRect(&rc, CWnd::adjustOutside);
501 wp.rcNormalPosition.right = wp.rcNormalPosition.left + rc.Width();
502 wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + rc.Height();
503 pFrameWnd->SetWindowPlacement(&wp);
506 CFormView::OnWindowPosChanged(lpwndpos);
509 void COpenView::OnDestroy()
511 if (m_pDropHandler != nullptr)
512 RevokeDragDrop(m_hWnd);
514 CFormView::OnDestroy();
517 LRESULT COpenView::OnNcHitTest(CPoint point)
519 if (GetParentFrame()->IsZoomed())
523 rc.left = rc.right - GetSystemMetrics(SM_CXVSCROLL);
524 rc.top = rc.bottom - GetSystemMetrics(SM_CYHSCROLL);
525 if (PtInRect(&rc, point))
528 return CFormView::OnNcHitTest(point);
532 * @brief Called when "Browse..." button is selected for N path.
534 void COpenView::OnPathButton(UINT nId)
536 const int index = nId - IDC_PATH0_BUTTON;
541 paths::PATH_EXISTENCE existence = paths::DoesPathExist(m_strPath[index]);
544 case paths::IS_EXISTING_DIR:
545 sfolder = m_strPath[index];
547 case paths::IS_EXISTING_FILE:
548 sfolder = paths::GetPathOnly(m_strPath[index]);
550 case paths::DOES_NOT_EXIST:
551 if (!m_strPath[index].empty())
552 sfolder = paths::GetParentPath(m_strPath[index]);
555 _RPTF0(_CRT_ERROR, "Invalid return value from paths::DoesPathExist()");
559 if (SelectFileOrFolder(GetSafeHwnd(), s, sfolder.c_str()))
561 m_strPath[index] = s;
562 m_strBrowsePath[index] = s;
564 UpdateButtonStates();
568 void COpenView::OnSwapButton(int id1, int id2)
571 GetDlgItemText(id1, s1);
572 GetDlgItemText(id2, s2);
574 SetDlgItemText(id1, s1);
575 SetDlgItemText(id2, s2);
578 template<int id1, int id2>
579 void COpenView::OnSwapButton()
581 OnSwapButton(id1, id2);
584 void COpenView::OnCompare(UINT nID)
586 int pathsType; // enum from paths::PATH_EXISTENCE in paths.h
587 const String filterPrefix = _("[F] ");
588 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
594 for (auto& strPath : m_strPath)
596 if (nFiles >= 1 && strPath.empty())
598 m_files.SetSize(nFiles + 1);
599 m_files[nFiles] = strPath;
600 m_dwFlags[nFiles] &= ~FFILEOPEN_READONLY;
601 m_dwFlags[nFiles] |= m_bReadOnly[nFiles] ? FFILEOPEN_READONLY : 0;
604 // If left path is a project-file, load it
606 paths::SplitFilename(m_strPath[0], nullptr, nullptr, &ext);
609 if (strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
611 theApp.LoadAndOpenProjectFile(m_strPath[0]);
613 else if (!paths::IsDirectory(m_strPath[0]))
615 PackingInfo tmpPackingInfo(m_strUnpackerPipeline);
616 if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
617 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
618 GetMainFrame()->DoSelfCompare(nID, m_strPath[0], nullptr, &tmpPackingInfo);
623 pathsType = paths::GetPairComparability(m_files, IsArchiveFile);
625 if (pathsType == paths::DOES_NOT_EXIST)
627 LangMessageBox(IDS_ERROR_INCOMPARABLE, MB_ICONSTOP);
631 for (int index = 0; index < nFiles; index++)
633 // If user has edited path by hand, expand environment variables
634 bool bExpand = false;
635 if (strutils::compare_nocase(m_strBrowsePath[index], m_files[index]) != 0)
638 if (!paths::IsURLorCLSID(m_files[index]))
640 m_files[index] = paths::GetLongPath(m_files[index], bExpand);
642 // Add trailing '\' for directories if its missing
643 if (paths::DoesPathExist(m_files[index]) == paths::IS_EXISTING_DIR)
644 m_files[index] = paths::AddTrailingSlash(m_files[index]);
645 m_strPath[index] = m_files[index];
650 KillTimer(IDT_CHECKFILES);
651 KillTimer(IDT_RETRY);
653 String filter(strutils::trim_ws(m_strExt));
655 // If prefix found from start..
656 if (filter.substr(0, filterPrefix.length()) == filterPrefix)
658 // Remove prefix + space
659 filter.erase(0, filterPrefix.length());
660 if (!pGlobalFileFilter->SetFilter(filter))
662 // If filtername is not found use default *.* mask
663 pGlobalFileFilter->SetFilter(_T("*.*"));
666 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
670 bool bFilterSet = pGlobalFileFilter->SetFilter(filter);
672 m_strExt = pGlobalFileFilter->GetFilterNameOrMask();
673 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
676 SaveComboboxStates();
677 GetOptionsMgr()->SaveOption(OPT_CMP_INCLUDE_SUBDIRS, m_bRecurse);
678 LoadComboboxStates();
680 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, m_nIgnoreWhite);
681 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_BLANKLINES, m_bIgnoreBlankLines);
682 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, m_bIgnoreCase);
683 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, m_bIgnoreEol);
684 GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CODEPAGE, m_bIgnoreCodepage);
685 GetOptionsMgr()->SaveOption(OPT_CMP_FILTER_COMMENTLINES, m_bFilterCommentsLines);
686 GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, m_nCompareMethod);
688 m_constraint.Persist(true, false);
690 COpenDoc *pDoc = GetDocument();
691 pDoc->m_files = m_files;
692 pDoc->m_bRecurse = m_bRecurse;
693 pDoc->m_strExt = m_strExt;
694 pDoc->m_strUnpackerPipeline = m_strUnpackerPipeline;
695 pDoc->m_dwFlags[0] = m_dwFlags[0];
696 pDoc->m_dwFlags[1] = m_dwFlags[1];
697 pDoc->m_dwFlags[2] = m_dwFlags[2];
699 if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_OK))
700 GetParentFrame()->PostMessage(WM_CLOSE);
702 // Copy the values in pDoc as it will be invalid when COpenFrame is closed.
703 PackingInfo tmpPackingInfo(pDoc->m_strUnpackerPipeline);
704 PathContext tmpPathContext(pDoc->m_files);
705 std::array<DWORD, 3> dwFlags = pDoc->m_dwFlags;
706 bool recurse = pDoc->m_bRecurse;
709 GetMainFrame()->DoFileOrFolderOpen(
710 &tmpPathContext, dwFlags.data(),
711 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
713 else if (ID_UNPACKERS_FIRST <= nID && nID <= ID_UNPACKERS_LAST)
715 tmpPackingInfo.SetPluginPipeline(CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::UnpackerEventNames, ID_UNPACKERS_FIRST));
716 GetMainFrame()->DoFileOrFolderOpen(
717 &tmpPathContext, dwFlags.data(),
718 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
720 else if (nID == ID_OPEN_WITH_UNPACKER)
722 CSelectPluginDlg dlg(pDoc->m_strUnpackerPipeline, tmpPathContext[0],
723 CSelectPluginDlg::PluginType::Unpacker, false, this);
724 if (dlg.DoModal() == IDOK)
726 tmpPackingInfo.SetPluginPipeline(dlg.GetPluginPipeline());
727 GetMainFrame()->DoFileOrFolderOpen(
728 &tmpPathContext, dwFlags.data(),
729 nullptr, _T(""), recurse, nullptr, &tmpPackingInfo, nullptr);
734 GetMainFrame()->DoFileOpen(nID, &tmpPathContext, dwFlags.data(), nullptr, _T(""), &tmpPackingInfo);
738 void COpenView::OnUpdateCompare(CCmdUI *pCmdUI)
740 bool bFile = GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled();
744 PathContext paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
745 bFile = std::all_of(paths.begin(), paths.end(), [](const String& path) {
746 return paths::DoesPathExist(path) == paths::IS_EXISTING_FILE;
749 pCmdUI->Enable(bFile);
753 * @brief Called when dialog is closed with "OK".
755 * Checks that paths are valid and sets filters.
757 void COpenView::OnOK()
763 * @brief Called when dialog is closed via Cancel.
765 * Open-dialog is closed when `Cancel` button is selected or the
766 * `Esc` key is pressed. Save combobox states, since user may have
767 * removed items from them (with `shift-del`) and doesn't want them
769 * This is *not* called when the program is terminated, even if the
770 * dialog is visible at the time.
772 void COpenView::OnCancel()
774 SaveComboboxStates();
775 AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
779 * @brief Callled when Open-button for project file is selected.
781 void COpenView::OnLoadProject()
785 String fileName = AskProjectFileName(true);
786 if (fileName.empty())
790 if (!theApp.LoadProjectFile(fileName, project))
792 if (project.Items().size() == 0)
795 ProjectFileItem& projItem = *project.Items().begin();
796 bool bRecurse = m_bRecurse;
797 projItem.GetPaths(paths, bRecurse);
798 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::IncludeSubfolders))
799 m_bRecurse = bRecurse;
800 if (paths.GetSize() < 3)
802 m_strPath[0] = paths[0];
803 m_strPath[1] = paths[1];
804 m_strPath[2] = _T("");
805 m_bReadOnly[0] = projItem.GetLeftReadOnly();
806 m_bReadOnly[1] = projItem.GetRightReadOnly();
807 m_bReadOnly[2] = false;
811 m_strPath[0] = paths[0];
812 m_strPath[1] = paths[1];
813 m_strPath[2] = paths[2];
814 m_bReadOnly[0] = projItem.GetLeftReadOnly();
815 m_bReadOnly[1] = projItem.GetMiddleReadOnly();
816 m_bReadOnly[2] = projItem.GetRightReadOnly();
818 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::FileFilter) && projItem.HasFilter())
819 m_strExt = projItem.GetFilter();
820 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::UnpackerPlugin) && projItem.HasUnpacker())
821 m_strUnpackerPipeline = projItem.GetUnpacker();
823 if (Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Load, Options::Project::Item::CompareOptions))
825 if (projItem.HasIgnoreWhite())
826 m_nIgnoreWhite = projItem.GetIgnoreWhite();
827 if (projItem.HasIgnoreBlankLines())
828 m_bIgnoreBlankLines = projItem.GetIgnoreBlankLines();
829 if (projItem.HasIgnoreCase())
830 m_bIgnoreCase = projItem.GetIgnoreCase();
831 if (projItem.HasIgnoreEol())
832 m_bIgnoreEol = projItem.GetIgnoreEol();
833 if (projItem.HasIgnoreCodepage())
834 m_bIgnoreCodepage = projItem.GetIgnoreCodepage();
835 if (projItem.HasFilterCommentsLines())
836 m_bFilterCommentsLines = projItem.GetFilterCommentsLines();
837 if (projItem.HasCompareMethod())
838 m_nCompareMethod = projItem.GetCompareMethod();
842 UpdateButtonStates();
843 LangMessageBox(IDS_PROJFILE_LOAD_SUCCESS, MB_ICONINFORMATION);
847 * @brief Called when Save-button for project file is selected.
849 void COpenView::OnSaveProject()
853 String fileName = AskProjectFileName(false);
854 if (fileName.empty())
858 ProjectFileItem projItem;
860 bool bSaveFileFilter = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::FileFilter);
861 bool bSaveIncludeSubfolders = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::IncludeSubfolders);
862 bool bSaveUnpackerPlugin = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::UnpackerPlugin);
863 bool bSaveCompareOptions = Options::Project::Get(GetOptionsMgr(), Options::Project::Operation::Save, Options::Project::Item::CompareOptions);
865 projItem.SetSaveFilter(bSaveFileFilter);
866 projItem.SetSaveSubfolders(bSaveIncludeSubfolders);
867 projItem.SetSaveUnpacker(bSaveUnpackerPlugin);
868 projItem.SetSaveIgnoreWhite(bSaveCompareOptions);
869 projItem.SetSaveIgnoreBlankLines(bSaveCompareOptions);
870 projItem.SetSaveIgnoreCase(bSaveCompareOptions);
871 projItem.SetSaveIgnoreEol(bSaveCompareOptions);
872 projItem.SetSaveIgnoreCodepage(bSaveCompareOptions);
873 projItem.SetSaveFilterCommentsLines(bSaveCompareOptions);
874 projItem.SetSaveCompareMethod(bSaveCompareOptions);
876 if (!m_strPath[0].empty())
877 projItem.SetLeft(m_strPath[0], &m_bReadOnly[0]);
878 if (m_strPath[2].empty())
880 if (!m_strPath[1].empty())
881 projItem.SetRight(m_strPath[1], &m_bReadOnly[1]);
885 if (!m_strPath[1].empty())
886 projItem.SetMiddle(m_strPath[1], &m_bReadOnly[1]);
887 if (!m_strPath[2].empty())
888 projItem.SetRight(m_strPath[2], &m_bReadOnly[2]);
890 if (bSaveFileFilter && !m_strExt.empty())
892 // Remove possbile prefix from the filter name
893 String prefix = _("[F] ");
894 String strExt = m_strExt;
895 size_t ind = strExt.find(prefix, 0);
898 strExt.erase(0, prefix.length());
900 strExt = strutils::trim_ws_begin(strExt);
901 projItem.SetFilter(strExt);
903 if (bSaveIncludeSubfolders)
904 projItem.SetSubfolders(m_bRecurse);
905 if (bSaveUnpackerPlugin && !m_strUnpackerPipeline.empty())
906 projItem.SetUnpacker(m_strUnpackerPipeline);
908 if (bSaveCompareOptions)
910 projItem.SetIgnoreWhite(m_nIgnoreWhite);
911 projItem.SetIgnoreBlankLines(m_bIgnoreBlankLines);
912 projItem.SetIgnoreCase(m_bIgnoreCase);
913 projItem.SetIgnoreEol(m_bIgnoreEol);
914 projItem.SetIgnoreCodepage(m_bIgnoreCodepage);
915 projItem.SetFilterCommentsLines(m_bFilterCommentsLines);
916 projItem.SetCompareMethod(m_nCompareMethod);
919 project.Items().push_back(projItem);
921 if (!theApp.SaveProjectFile(fileName, project))
924 LangMessageBox(IDS_PROJFILE_SAVE_SUCCESS, MB_ICONINFORMATION);
927 void COpenView::DropDown(NMHDR* pNMHDR, LRESULT* pResult, UINT nID, UINT nPopupID)
929 CRect rcButton, rcView;
930 GetDlgItem(nID)->GetWindowRect(&rcButton);
932 VERIFY(menu.LoadMenu(nPopupID));
933 theApp.TranslateMenu(menu.m_hMenu);
934 CMenu* pPopup = menu.GetSubMenu(0);
935 if (pPopup != nullptr)
937 if (nID == IDOK && GetDlgItem(IDC_UNPACKER_COMBO)->IsWindowEnabled())
941 for (int i = 0; i < 3; i++)
942 tmpPath[i] = m_strPath[i].empty() ? _T("|.|") : m_strPath[i];
943 String filteredFilenames = strutils::join(std::begin(tmpPath), std::end(tmpPath), _T("|"));
944 CMainFrame::AppendPluginMenus(pPopup, filteredFilenames, FileTransform::UnpackerEventNames, true, ID_UNPACKERS_FIRST);
946 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
947 rcButton.left, rcButton.bottom, GetMainFrame());
952 template<UINT id, UINT popupid>
953 void COpenView::OnDropDown(NMHDR *pNMHDR, LRESULT *pResult)
955 DropDown(pNMHDR, pResult, id, popupid);
959 * @brief Allow user to select a file to open/save.
961 String COpenView::AskProjectFileName(bool bOpen)
963 // get the default projects path
964 String strProjectFileName;
965 String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
967 if (!::SelectFile(GetSafeHwnd(), strProjectFileName, bOpen, strProjectPath.c_str(),
968 _T(""), _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), _T(".WinMerge")))
971 if (strProjectFileName.empty())
974 // get the path part from the filename
975 strProjectPath = paths::GetParentPath(strProjectFileName);
976 // store this as the new project path
977 GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
978 return strProjectFileName;
982 * @brief Load File- and filter-combobox states.
984 void COpenView::LoadComboboxStates()
986 m_ctlPath[0].LoadState(_T("Files\\Left"));
987 m_ctlPath[1].LoadState(_T("Files\\Right"));
988 m_ctlPath[2].LoadState(_T("Files\\Option"));
989 m_ctlExt.LoadState(_T("Files\\Ext"));
990 m_ctlUnpackerPipeline.LoadState(_T("Files\\Unpacker"));
994 * @brief Save File- and filter-combobox states.
996 void COpenView::SaveComboboxStates()
998 m_ctlPath[0].SaveState(_T("Files\\Left"));
999 m_ctlPath[1].SaveState(_T("Files\\Right"));
1000 m_ctlPath[2].SaveState(_T("Files\\Option"));
1001 m_ctlExt.SaveState(_T("Files\\Ext"));
1002 m_ctlUnpackerPipeline.SaveState(_T("Files\\Unpacker"));
1005 struct UpdateButtonStatesThreadParams
1008 PathContext m_paths;
1011 static UINT UpdateButtonStatesThread(LPVOID lpParam)
1016 CoInitialize(nullptr);
1017 CAssureScriptsForThread scriptsForRescan;
1019 while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
1023 if (msg.message != WM_USER + 2)
1026 bool bIsaFolderCompare = true;
1027 bool bIsaFileCompare = true;
1028 bool bInvalid[3] = {false, false, false};
1029 paths::PATH_EXISTENCE pathType[3] = {paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST, paths::DOES_NOT_EXIST};
1030 int iStatusMsgId = IDS_OPEN_FILESDIRS;
1032 UpdateButtonStatesThreadParams *pParams = reinterpret_cast<UpdateButtonStatesThreadParams *>(msg.wParam);
1033 PathContext paths = pParams->m_paths;
1034 HWND hWnd = pParams->m_hWnd;
1037 // Check if we have project file as left side path
1038 bool bProject = false;
1040 paths::SplitFilename(paths[0], nullptr, nullptr, &ext);
1041 if (paths[1].empty() && strutils::compare_nocase(ext, ProjectFile::PROJECTFILE_EXT) == 0)
1046 for (int i = 0; i < paths.GetSize(); ++i)
1048 pathType[i] = paths::DoesPathExist(paths[i], IsArchiveFile);
1049 if (pathType[i] == paths::DOES_NOT_EXIST)
1054 // Enable buttons as appropriate
1055 if (GetOptionsMgr()->GetBool(OPT_VERIFY_OPEN_PATHS))
1057 paths::PATH_EXISTENCE pathsType = pathType[0];
1059 if (paths.GetSize() <= 2)
1061 if (bInvalid[0] && bInvalid[1])
1062 iStatusMsgId = IDS_OPEN_BOTHINVALID;
1063 else if (bInvalid[0])
1064 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1065 else if (bInvalid[1])
1067 if (pathType[0] == paths::IS_EXISTING_FILE && (paths.GetSize() == 1 || paths[1].empty()))
1068 iStatusMsgId = IDS_OPEN_FILESDIRS;
1070 iStatusMsgId = IDS_OPEN_RIGHTINVALID2;
1072 else if (!bInvalid[0] && !bInvalid[1])
1074 if (pathType[0] != pathType[1])
1075 iStatusMsgId = IDS_OPEN_MISMATCH;
1077 iStatusMsgId = IDS_OPEN_FILESDIRS;
1082 if (bInvalid[0] && bInvalid[1] && bInvalid[2])
1083 iStatusMsgId = IDS_OPEN_ALLINVALID;
1084 else if (!bInvalid[0] && bInvalid[1] && bInvalid[2])
1085 iStatusMsgId = IDS_OPEN_MIDDLERIGHTINVALID;
1086 else if (bInvalid[0] && !bInvalid[1] && bInvalid[2])
1087 iStatusMsgId = IDS_OPEN_LEFTRIGHTINVALID;
1088 else if (!bInvalid[0] && !bInvalid[1] && bInvalid[2])
1089 iStatusMsgId = IDS_OPEN_RIGHTINVALID3;
1090 else if (bInvalid[0] && bInvalid[1] && !bInvalid[2])
1091 iStatusMsgId = IDS_OPEN_LEFTMIDDLEINVALID;
1092 else if (!bInvalid[0] && bInvalid[1] && !bInvalid[2])
1093 iStatusMsgId = IDS_OPEN_MIDDLEINVALID;
1094 else if (bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1095 iStatusMsgId = IDS_OPEN_LEFTINVALID;
1096 else if (!bInvalid[0] && !bInvalid[1] && !bInvalid[2])
1098 if (pathType[0] != pathType[1] || pathType[0] != pathType[2])
1099 iStatusMsgId = IDS_OPEN_MISMATCH;
1101 iStatusMsgId = IDS_OPEN_FILESDIRS;
1104 if (iStatusMsgId != IDS_OPEN_FILESDIRS)
1105 pathsType = paths::DOES_NOT_EXIST;
1106 bIsaFileCompare = (pathsType == paths::IS_EXISTING_FILE);
1107 bIsaFolderCompare = (pathsType == paths::IS_EXISTING_DIR);
1108 // Both will be `false` if incompatibilities or something is missing
1109 // Both will end up `true` if file validity isn't being checked
1112 PostMessage(hWnd, WM_USER + 1, MAKEWPARAM(bIsaFolderCompare, bIsaFileCompare), MAKELPARAM(iStatusMsgId, bProject));
1121 * @brief Update any resources necessary after a GUI language change
1123 void COpenView::UpdateResources()
1125 theApp.m_pLangDlg->RetranslateDialog(m_hWnd, MAKEINTRESOURCE(IDD_OPEN));
1129 * @brief Enable/disable components based on validity of paths.
1131 void COpenView::UpdateButtonStates()
1133 UpdateData(TRUE); // load member variables from screen
1134 KillTimer(IDT_CHECKFILES);
1137 if (m_pUpdateButtonStatusThread == nullptr)
1139 m_pUpdateButtonStatusThread = AfxBeginThread(
1140 UpdateButtonStatesThread, nullptr, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
1141 m_pUpdateButtonStatusThread->m_bAutoDelete = FALSE;
1142 m_pUpdateButtonStatusThread->ResumeThread();
1143 while (PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_NULL, 0, 0) == FALSE)
1147 UpdateButtonStatesThreadParams *pParams = new UpdateButtonStatesThreadParams;
1148 pParams->m_hWnd = this->m_hWnd;
1149 pParams->m_paths = PathContext(std::vector<String>(&m_strPath[0], &m_strPath[m_strPath[2].empty() ? 2 : 3]));
1151 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_USER + 2, (WPARAM)pParams, 0);
1154 void COpenView::TerminateThreadIfRunning()
1156 if (m_pUpdateButtonStatusThread == nullptr)
1159 PostThreadMessage(m_pUpdateButtonStatusThread->m_nThreadID, WM_QUIT, 0, 0);
1160 DWORD dwResult = WaitForSingleObject(m_pUpdateButtonStatusThread->m_hThread, 100);
1161 if (dwResult != WAIT_OBJECT_0)
1163 m_pUpdateButtonStatusThread->SuspendThread();
1164 TerminateThread(m_pUpdateButtonStatusThread->m_hThread, 0);
1166 delete m_pUpdateButtonStatusThread;
1167 m_pUpdateButtonStatusThread = nullptr;
1171 * @brief Called when user changes selection in left/middle/right path's combo box.
1173 void COpenView::OnSelchangePathCombo(UINT nId)
1175 const int index = nId - IDC_PATH0_COMBO;
1176 int sel = m_ctlPath[index].GetCurSel();
1180 m_ctlPath[index].GetLBText(sel, cstrPath);
1181 m_strPath[index] = cstrPath;
1182 m_ctlPath[index].SetWindowText(cstrPath);
1185 UpdateButtonStates();
1188 void COpenView::OnSetfocusPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1190 if (!m_bAutoCompleteReady[id - IDC_PATH0_COMBO])
1192 int nSource = GetOptionsMgr()->GetInt(OPT_AUTO_COMPLETE_SOURCE);
1194 m_ctlPath[id - IDC_PATH0_COMBO].SetAutoComplete(nSource);
1195 m_bAutoCompleteReady[id - IDC_PATH0_COMBO] = true;
1200 void COpenView::OnDragBeginPathCombo(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
1202 m_ctlPath[id - IDC_PATH0_COMBO].SetFocus();
1208 * @brief Called every time paths are edited.
1210 void COpenView::OnEditEvent(UINT nID)
1212 const int N = nID - IDC_PATH0_COMBO;
1213 if (CEdit *const edit = m_ctlPath[N].GetEditCtrl())
1215 int const len = edit->GetWindowTextLength();
1216 if (edit->GetSel() == MAKEWPARAM(len, len))
1219 edit->GetWindowText(text);
1220 // Remove any double quotes
1222 if (text.GetLength() != len)
1224 edit->SetSel(0, len);
1225 edit->ReplaceSel(text);
1229 // (Re)start timer to path validity check delay
1230 // If timer starting fails, update buttonstates immediately
1231 if (!SetTimer(IDT_CHECKFILES, CHECKFILES_TIMEOUT, nullptr))
1232 UpdateButtonStates();
1236 * @brief Handle timer events.
1237 * Checks if paths are valid and sets control states accordingly.
1238 * @param [in] nIDEvent Timer ID that fired.
1240 void COpenView::OnTimer(UINT_PTR nIDEvent)
1242 if (nIDEvent == IDT_CHECKFILES || nIDEvent == IDT_RETRY)
1243 UpdateButtonStates();
1245 CFormView::OnTimer(nIDEvent);
1249 * @brief Called when users selects plugin browse button.
1251 void COpenView::OnSelectUnpacker()
1253 paths::PATH_EXISTENCE pathsType;
1257 for (auto& strPath: m_strPath)
1259 if (nFiles == 2 && strPath.empty())
1261 m_files.SetSize(nFiles + 1);
1262 m_files[nFiles] = strPath;
1265 pathsType = paths::GetPairComparability(m_files);
1267 if (pathsType != paths::IS_EXISTING_FILE)
1270 // let the user select a handler
1271 CSelectPluginDlg dlg(m_strUnpackerPipeline, m_files[0],
1272 CSelectPluginDlg::PluginType::Unpacker, false, this);
1273 if (dlg.DoModal() == IDOK)
1275 m_strUnpackerPipeline = dlg.GetPluginPipeline();
1280 LRESULT COpenView::OnUpdateStatus(WPARAM wParam, LPARAM lParam)
1282 const bool bIsaFolderCompare = LOWORD(wParam) != 0;
1283 const bool bIsaFileCompare = HIWORD(wParam) != 0;
1284 const bool bProject = HIWORD(lParam) != 0;
1285 const int iStatusMsgId = LOWORD(lParam);
1287 EnableDlgItem(IDOK, bIsaFolderCompare || bIsaFileCompare || bProject);
1289 EnableDlgItem(IDC_FILES_DIRS_GROUP4, bIsaFileCompare);
1290 EnableDlgItem(IDC_UNPACKER_COMBO, bIsaFileCompare);
1291 EnableDlgItem(IDC_SELECT_UNPACKER, bIsaFileCompare);
1293 EnableDlgItem(IDC_FILES_DIRS_GROUP3, bIsaFolderCompare);
1294 EnableDlgItem(IDC_EXT_COMBO, bIsaFolderCompare);
1295 EnableDlgItem(IDC_SELECT_FILTER, bIsaFolderCompare);
1296 EnableDlgItem(IDC_RECURS_CHECK, bIsaFolderCompare);
1298 SetStatus(iStatusMsgId);
1300 if (iStatusMsgId != IDS_OPEN_FILESDIRS && m_retryCount <= RETRY_MAX)
1302 if (m_retryCount == 0)
1303 SetTimer(IDT_RETRY, CHECKFILES_TIMEOUT, nullptr);
1308 KillTimer(IDT_RETRY);
1315 * @brief Sets the path status text.
1316 * The open dialog shows a status text of selected paths. This function
1317 * is used to set that status text.
1318 * @param [in] msgID Resource ID of status text to set.
1320 void COpenView::SetStatus(UINT msgID)
1322 String msg = theApp.LoadString(msgID);
1323 SetDlgItemText(IDC_OPEN_STATUS, msg);
1327 * @brief Called when "Select..." button for filters is selected.
1329 void COpenView::OnSelectFilter()
1331 String filterPrefix = _("[F] ");
1333 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
1335 const bool bUseMask = pGlobalFileFilter->IsUsingMask();
1336 GetDlgItemText(IDC_EXT_COMBO, curFilter);
1337 curFilter = strutils::trim_ws(curFilter);
1339 GetMainFrame()->SelectFilter();
1341 String filterNameOrMask = pGlobalFileFilter->GetFilterNameOrMask();
1342 if (pGlobalFileFilter->IsUsingMask())
1344 // If we had filter chosen and now has mask we can overwrite filter
1345 if (!bUseMask || curFilter[0] != '*')
1347 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1352 filterNameOrMask = filterPrefix + filterNameOrMask;
1353 SetDlgItemText(IDC_EXT_COMBO, filterNameOrMask);
1357 void COpenView::OnOptions()
1359 GetMainFrame()->PostMessage(WM_COMMAND, ID_OPTIONS);
1363 * @brief Set "Whitespaces" setting.
1364 * @param [in] nID Menu ID of the selected item
1366 void COpenView::OnDiffWhitespace(UINT nID)
1368 assert(nID >= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE && nID <= ID_PROJECT_DIFF_OPTIONS_WHITESPACE_IGNOREALL);
1370 m_nIgnoreWhite = nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE;
1374 * @brief Update "Whitespaces" state.
1375 * @param [in] pCmdUI UI component to update.
1377 void COpenView::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
1379 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_WHITESPACE_COMPARE) == static_cast<UINT>(m_nIgnoreWhite));
1383 * @brief Toggle "Ignore blank lines" setting.
1385 void COpenView::OnDiffIgnoreBlankLines()
1387 m_bIgnoreBlankLines = !m_bIgnoreBlankLines;
1391 * @brief Update "Ignore blank lines" state.
1392 * @param [in] pCmdUI UI component to update.
1394 void COpenView::OnUpdateDiffIgnoreBlankLines(CCmdUI* pCmdUI)
1396 pCmdUI->SetCheck(m_bIgnoreBlankLines);
1400 * @brief Toggle "Ignore case" setting.
1402 void COpenView::OnDiffIgnoreCase()
1404 m_bIgnoreCase = !m_bIgnoreCase;
1408 * @brief Update "Ignore case" state.
1409 * @param [in] pCmdUI UI component to update.
1411 void COpenView::OnUpdateDiffIgnoreCase(CCmdUI* pCmdUI)
1413 pCmdUI->SetCheck(m_bIgnoreCase);
1417 * @brief Toggle "Ignore carriage return differences" setting.
1419 void COpenView::OnDiffIgnoreEOL()
1421 m_bIgnoreEol = !m_bIgnoreEol;
1425 * @brief Update "Ignore carriage return differences" state.
1426 * @param [in] pCmdUI UI component to update.
1428 void COpenView::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
1430 pCmdUI->SetCheck(m_bIgnoreEol);
1434 * @brief Toggle "Ignore codepage differences" setting.
1436 void COpenView::OnDiffIgnoreCP()
1438 m_bIgnoreCodepage = !m_bIgnoreCodepage;
1442 * @brief Update "Ignore codepage differences" state.
1443 * @param [in] pCmdUI UI component to update.
1445 void COpenView::OnUpdateDiffIgnoreCP(CCmdUI* pCmdUI)
1447 pCmdUI->SetCheck(m_bIgnoreCodepage);
1451 * @brief Toggle "Ignore comment differences" setting.
1453 void COpenView::OnDiffIgnoreComments()
1455 m_bFilterCommentsLines = !m_bFilterCommentsLines;
1459 * @brief Update "Ignore comment differences" state.
1460 * @param [in] pCmdUI UI component to update.
1462 void COpenView::OnUpdateDiffIgnoreComments(CCmdUI* pCmdUI)
1464 pCmdUI->SetCheck(m_bFilterCommentsLines);
1468 * @brief Set "Compare method" setting.
1469 * @param [in] nID Menu ID of the selected item
1471 void COpenView::OnCompareMethod(UINT nID)
1473 assert(nID >= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS && nID <= ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_SIZE);
1475 m_nCompareMethod = nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS;
1479 * @brief Update "Compare method" state.
1480 * @param [in] pCmdUI UI component to update.
1482 void COpenView::OnUpdateCompareMethod(CCmdUI* pCmdUI)
1484 pCmdUI->SetRadio((pCmdUI->m_nID - ID_PROJECT_DIFF_OPTIONS_COMPMETHOD_FULL_CONTENTS) == static_cast<UINT>(m_nCompareMethod));
1488 * @brief Removes whitespaces from left and right paths
1489 * @note Assumes UpdateData(TRUE) is called before this function.
1491 void COpenView::TrimPaths()
1493 for (auto& strPath: m_strPath)
1494 strPath = strutils::trim_ws(strPath);
1498 * @brief Update control states when dialog is activated.
1500 * Update control states when user re-activates dialog. User might have
1501 * switched for other program to e.g. update files/folders and then
1502 * swiches back to WinMerge. Its nice to see WinMerge detects updated
1505 void COpenView::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
1507 CFormView::OnActivate(nState, pWndOther, bMinimized);
1509 if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
1510 UpdateButtonStates();
1513 void COpenView::OnEditAction(int msg, WPARAM wParam, LPARAM lParam)
1515 CWnd *pCtl = GetFocus();
1516 if (pCtl != nullptr)
1517 pCtl->PostMessage(msg, wParam, lParam);
1520 template <int MSG, int WPARAM, int LPARAM>
1521 void COpenView::OnEditAction()
1523 OnEditAction(MSG, WPARAM, LPARAM);
1527 * @brief Open help from mainframe when user presses F1.
1529 void COpenView::OnHelp()
1531 theApp.ShowHelp(OpenDlgHelpLocation);
1534 /////////////////////////////////////////////////////////////////////////////
1536 // OnDropFiles code from CDropEdit
1537 // Copyright 1997 Chris Losinger
1539 // shortcut expansion code modified from :
1540 // CShortcut, 1996 Rob Warner
1544 * @brief Drop paths(s) to the dialog.
1545 * One or two paths can be dropped to the dialog. The behaviour is:
1547 * - drop to empty path edit box (check left first)
1548 * - if both boxes have a path, drop to left path
1550 * - overwrite both paths, empty or not
1551 * @param [in] dropInfo Dropped data, including paths.
1553 void COpenView::OnDropFiles(const std::vector<String>& files)
1555 const size_t fileCount = files.size();
1557 // Add dropped paths to the dialog
1561 m_strPath[0] = files[0];
1562 m_strPath[1] = files[1];
1563 m_strPath[2] = files[2];
1565 UpdateButtonStates();
1567 else if (fileCount == 2)
1569 m_strPath[0] = files[0];
1570 m_strPath[1] = files[1];
1572 UpdateButtonStates();
1574 else if (fileCount == 1)
1577 GetCursorPos(&point);
1578 ScreenToClient(&point);
1579 if (CWnd *const pwndHit = ChildWindowFromPoint(point,
1580 CWP_SKIPINVISIBLE | CWP_SKIPDISABLED | CWP_SKIPTRANSPARENT))
1582 switch (int const id = pwndHit->GetDlgCtrlID())
1584 case IDC_PATH0_COMBO:
1585 case IDC_PATH1_COMBO:
1586 case IDC_PATH2_COMBO:
1587 m_strPath[id - IDC_PATH0_COMBO] = files[0];
1590 if (m_strPath[0].empty())
1591 m_strPath[0] = files[0];
1592 else if (m_strPath[1].empty())
1593 m_strPath[1] = files[0];
1594 else if (m_strPath[2].empty())
1595 m_strPath[2] = files[0];
1597 m_strPath[0] = files[0];
1602 UpdateButtonStates();