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 file for CDirDoc
16 #include <Poco/StringTokenizer.h>
17 #include <boost/range/mfc.hpp>
19 #include "IMergeDoc.h"
20 #include "CompareOptions.h"
21 #include "UnicodeString.h"
22 #include "CompareStats.h"
23 #include "FilterList.h"
24 #include "SubstitutionList.h"
30 #include "OptionsDef.h"
31 #include "OptionsMgr.h"
32 #include "OptionsDiffOptions.h"
33 #include "LineFiltersList.h"
34 #include "SubstitutionFiltersList.h"
35 #include "FileFilterHelper.h"
37 #include "DirActions.h"
39 #include "MessageBoxDialog.h"
40 #include "DirCmpReport.h"
41 #include "DiffWrapper.h"
42 #include "FolderCmp.h"
43 #include "DirViewColItems.h"
44 #include <Poco/Semaphore.h>
50 using Poco::StringTokenizer;
54 int CDirDoc::m_nDirsTemp = 2;
56 /////////////////////////////////////////////////////////////////////////////
59 IMPLEMENT_DYNCREATE(CDirDoc, CDocument)
67 , m_pCompareStats(nullptr)
68 , m_bMarkedRescan(false)
69 , m_pTempPathContext(nullptr)
70 , m_bGeneratingReport(false)
73 m_nDirs = m_nDirsTemp;
83 * Clears document list and deleted possible archive-temp files.
87 // Inform all of our merge docs that we're closing
88 for (auto pMergeDoc : m_MergeDocs)
89 pMergeDoc->DirDocClosing(this);
90 // Delete all temporary folders belonging to this document
91 while (m_pTempPathContext != nullptr)
93 m_pTempPathContext = m_pTempPathContext->DeleteHead();
98 * @brief Called when new dirdoc is created.
100 BOOL CDirDoc::OnNewDocument()
102 if (!CDocument::OnNewDocument())
109 BEGIN_MESSAGE_MAP(CDirDoc, CDocument)
110 //{{AFX_MSG_MAP(CDirDoc)
111 // NOTE - the ClassWizard will add and remove mapping macros here.
113 ON_BN_CLICKED(IDC_COMPARISON_STOP, OnBnClickedComparisonStop)
114 ON_BN_CLICKED(IDC_COMPARISON_PAUSE, OnBnClickedComparisonPause)
115 ON_BN_CLICKED(IDC_COMPARISON_CONTINUE, OnBnClickedComparisonContinue)
120 /////////////////////////////////////////////////////////////////////////////
121 // CDirDoc serialization
123 void CDirDoc::Serialize(CArchive& ar)
127 // TODO: add storing code here
131 // TODO: add loading code here
135 /////////////////////////////////////////////////////////////////////////////
139 * @brief Initialise directory compare for given paths.
141 * Initialises directory compare with paths given and recursive choice.
142 * Previous compare context is first free'd.
143 * @param [in] paths Paths to compare
144 * @param [in] bRecursive If `true` subdirectories are included to compare.
146 void CDirDoc::InitCompare(const PathContext & paths, bool bRecursive, CTempPathContext *pTempPathContext)
148 // Abort previous comparing
149 while (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
151 m_diffThread.Abort();
155 m_pDirView->DeleteAllDisplayItems();
156 // Anything that can go wrong here will yield an exception.
157 // Default implementation of operator new() never returns `nullptr`.
159 if (m_pCompareStats == nullptr)
160 m_pCompareStats.reset(new CompareStats(m_nDirs));
162 m_pCtxt.reset(new CDiffContext(paths,
163 GetOptionsMgr()->GetInt(OPT_CMP_METHOD)));
164 m_pCtxt->m_bRecursive = bRecursive;
166 if (pTempPathContext != nullptr)
169 for (nIndex = 0; nIndex < m_nDirs; nIndex++)
170 ApplyDisplayRoot(nIndex, pTempPathContext->m_strDisplayRoot[nIndex]);
171 pTempPathContext->m_pParent = m_pTempPathContext;
172 m_pTempPathContext = pTempPathContext;
173 for (nIndex = 0; nIndex < m_nDirs; nIndex++)
174 m_pTempPathContext->m_strRoot[nIndex] = m_pCtxt->GetNormalizedPath(nIndex);
180 * @brief Load line filters to the compare context.
181 * Loads linefilters, converts them to UTF-8 and sets them for compare context.
183 void CDirDoc::LoadLineFilterList(CDiffContext *pCtxt)
185 ASSERT(pCtxt != nullptr);
187 bool bFilters = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
188 String filters = theApp.m_pLineFilters->GetAsString();
189 if (!bFilters || filters.empty())
191 pCtxt->m_pFilterList.reset();
195 if (pCtxt->m_pFilterList)
196 pCtxt->m_pFilterList->RemoveAllFilters();
198 pCtxt->m_pFilterList.reset(new FilterList());
200 std::string regexp_str = ucr::toUTF8(filters);
202 // Add every "line" of regexps to regexp list
203 StringTokenizer tokens(regexp_str, "\r\n");
204 for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
205 pCtxt->m_pFilterList->AddRegExp(*it);
208 void CDirDoc::LoadSubstitutionFiltersList(CDiffContext* pCtxt)
210 ASSERT(pCtxt != nullptr);
212 bool SubstitutionFiltersEnabled = theApp.m_pSubstitutionFiltersList->GetEnabled();
213 if (!SubstitutionFiltersEnabled || theApp.m_pSubstitutionFiltersList->GetCount() == 0)
215 pCtxt->m_pSubstitutionList.reset();
219 pCtxt->m_pSubstitutionList = theApp.m_pSubstitutionFiltersList->MakeSubstitutionList();
222 void CDirDoc::DiffThreadCallback(int& state)
224 PostMessage(m_pDirView->GetSafeHwnd(), MSG_UI_UPDATE, state, false);
227 void CDirDoc::InitDiffContext(CDiffContext *pCtxt)
229 LoadLineFilterList(pCtxt);
230 LoadSubstitutionFiltersList(pCtxt);
232 DIFFOPTIONS options = {0};
233 Options::DiffOptions::Load(GetOptionsMgr(), options);
235 pCtxt->CreateCompareOptions(GetOptionsMgr()->GetInt(OPT_CMP_METHOD), options);
237 pCtxt->m_iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
238 if ((pCtxt->m_iGuessEncodingType >> 16) == 0)
239 pCtxt->m_iGuessEncodingType |= 50001 << 16;
240 pCtxt->m_bIgnoreSmallTimeDiff = GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME);
241 pCtxt->m_bStopAfterFirstDiff = GetOptionsMgr()->GetBool(OPT_CMP_STOP_AFTER_FIRST);
242 pCtxt->m_nQuickCompareLimit = GetOptionsMgr()->GetInt(OPT_CMP_QUICK_LIMIT);
243 pCtxt->m_nBinaryCompareLimit = GetOptionsMgr()->GetInt(OPT_CMP_BINARY_LIMIT);
244 pCtxt->m_bPluginsEnabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
245 pCtxt->m_bWalkUniques = GetOptionsMgr()->GetBool(OPT_CMP_WALK_UNIQUE_DIRS);
246 pCtxt->m_bIgnoreReparsePoints = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_REPARSE_POINTS);
247 pCtxt->m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
248 pCtxt->m_bEnableImageCompare = GetOptionsMgr()->GetBool(OPT_CMP_ENABLE_IMGCMP_IN_DIRCMP);
249 pCtxt->m_dColorDistanceThreshold = GetOptionsMgr()->GetInt(OPT_CMP_IMG_THRESHOLD) / 1000.0;
251 pCtxt->m_pPropertySystem.reset(new PropertySystem(m_pDirView->GetDirViewColItems()->GetAdditionalPropertyNames()));
253 m_imgfileFilter.UseMask(true);
254 m_imgfileFilter.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
255 pCtxt->m_pImgfileFilter = &m_imgfileFilter;
257 pCtxt->m_pCompareStats = m_pCompareStats.get();
259 // Make sure filters are up-to-date
260 auto* pGlobalFileFilter = theApp.GetGlobalFileFilter();
261 pGlobalFileFilter->ReloadUpdatedFilters();
262 m_fileHelper.CloneFrom(pGlobalFileFilter);
263 pCtxt->m_piFilterGlobal = &m_fileHelper;
265 // All plugin management is done by our plugin manager
266 pCtxt->m_piPluginInfos = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED) ? &m_pluginman : nullptr;
270 * @brief Perform directory comparison again from scratch
272 void CDirDoc::Rescan()
274 if (m_pCtxt == nullptr)
277 CDirFrame *pf = m_pDirView->GetParentFrame();
279 // If we're already doing a rescan, bail out
280 UINT threadState = m_diffThread.GetThreadState();
281 if (threadState == CDiffThread::THREAD_COMPARING)
284 if (!m_bGeneratingReport)
285 m_pCompareStats->Reset();
286 m_pDirView->StartCompare(m_pCompareStats.get());
288 if (m_pCmpProgressBar == nullptr)
289 m_pCmpProgressBar.reset(new DirCompProgressBar());
291 if (!::IsWindow(m_pCmpProgressBar->GetSafeHwnd()))
292 m_pCmpProgressBar->Create(m_pDirView->GetParentFrame());
294 m_pCmpProgressBar->SetCompareStat(m_pCompareStats.get());
295 m_pCmpProgressBar->StartUpdating();
297 m_pDirView->GetParentFrame()->ShowControlBar(m_pCmpProgressBar.get(), TRUE, FALSE);
299 if (!m_bGeneratingReport)
300 m_pDirView->DeleteAllDisplayItems();
301 // Don't clear if only scanning selected items
302 if (!m_bMarkedRescan && !m_bGeneratingReport)
304 m_pCtxt->RemoveAll();
305 m_pCtxt->InitDiffItemList();
308 InitDiffContext(m_pCtxt.get());
310 pf->GetHeaderInterface()->SetPaneCount(m_nDirs);
311 pf->GetHeaderInterface()->SetOnSetFocusCallback([&](int pane) {
312 m_pDirView->SetActivePane(pane);
313 GetOptionsMgr()->SaveOption(OPT_ACTIVE_PANE, pane);
315 for (int nIndex = 0; nIndex < m_nDirs; nIndex++)
317 UpdateHeaderPath(nIndex);
318 // draw the headers as inactive ones
319 pf->GetHeaderInterface()->SetActive(nIndex, false);
321 pf->GetHeaderInterface()->Resize();
322 int nPane = GetOptionsMgr()->GetInt(OPT_ACTIVE_PANE);
323 m_pDirView->SetActivePane((nPane >= 0 && nPane < m_nDirs) ? nPane : 0);
325 // Show current compare method name and active filter name in statusbar
326 pf->SetFilterStatusDisplay(theApp.GetGlobalFileFilter()->GetFilterNameOrMask().c_str());
327 pf->SetCompareMethodStatusDisplay(m_pCtxt->GetCompareMethod());
329 // Folder names to compare are in the compare context
330 m_diffThread.SetContext(m_pCtxt.get());
331 m_diffThread.RemoveListener(this, &CDirDoc::DiffThreadCallback);
332 m_diffThread.AddListener(this, &CDirDoc::DiffThreadCallback);
333 if (m_bGeneratingReport)
335 m_diffThread.SetCollectFunction([&](DiffFuncStruct* myStruct) {
337 if (m_pReport->GetCopyToClipboard())
340 if (m_pReport->GetReportType() == REPORT_TYPE_SIMPLEHTML)
343 if (!m_pReport->GetReportFile().empty())
345 myStruct->context->m_pCompareStats->IncreaseTotalItems(
346 (m_pDirView->GetListCtrl().GetItemCount() - (myStruct->context->m_bRecursive ? 0 : 1)) * m);
348 m_diffThread.SetCompareFunction([&](DiffFuncStruct* myStruct) {
349 m_pReport->SetDiffFuncStruct(myStruct);
350 myStruct->pSemaphore->wait();
352 if (m_pReport->GenerateReport(errStr))
356 if (GetReportFile().empty())
357 LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
361 String msg = strutils::format_string1(
362 _("Error creating the report:\n%1"),
364 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
367 SetGeneratingReport(false);
370 m_diffThread.SetMarkedRescan(false);
372 else if (m_bMarkedRescan)
374 m_diffThread.SetCollectFunction([](DiffFuncStruct* myStruct) {
375 int nItems = DirScan_UpdateMarkedItems(myStruct, nullptr);
376 myStruct->context->m_pCompareStats->IncreaseTotalItems(nItems);
378 m_diffThread.SetCompareFunction([](DiffFuncStruct* myStruct) {
379 DirScan_CompareRequestedItems(myStruct, nullptr);
381 m_diffThread.SetMarkedRescan(true);
385 m_diffThread.SetCollectFunction([](DiffFuncStruct* myStruct) {
386 bool casesensitive = false;
387 int depth = myStruct->context->m_bRecursive ? -1 : 0;
388 PathContext paths = myStruct->context->GetNormalizedPaths();
389 String subdir[3] = {_T(""), _T(""), _T("")}; // blank to start at roots specified in diff context
390 // Build results list (except delaying file comparisons until below)
391 DirScan_GetItems(paths, subdir, myStruct,
392 casesensitive, depth, nullptr, myStruct->context->m_bWalkUniques);
394 m_diffThread.SetCompareFunction([](DiffFuncStruct* myStruct) {
395 DirScan_CompareItems(myStruct, nullptr);
397 m_diffThread.SetMarkedRescan(false);
399 m_diffThread.CompareDirectories();
400 m_bMarkedRescan = false;
404 * @brief Empty & reload listview (of files & columns) with comparison results
405 * @todo Better solution for special items ("..")?
407 void CDirDoc::Redisplay()
409 if (m_pDirView == nullptr)
412 // Do not redisplay an empty CDirView
413 // Not only does it not have results, but AddSpecialItems will crash
414 // trying to dereference null context pointer to get to paths
418 m_pDirView->Redisplay();
421 CDirView * CDirDoc::GetMainView() const
423 CDirView *pView = nullptr;
424 if (POSITION pos = GetFirstViewPosition())
426 pView = static_cast<CDirView*>(GetNextView(pos));
427 ASSERT_KINDOF(CDirView, pView);
433 * @brief Update in-memory diffitem status from disk and update view.
434 * @param [in] diffPos POSITION of item in UI list.
435 * @param [in] idx index to reload.
436 * @note Do not call this function from DirView code! This function
437 * calls slow DirView functions to get item position and to update GUI.
438 * Use UpdateStatusFromDisk() function instead.
440 void CDirDoc::ReloadItemStatus(DIFFITEM *diffPos, int idx)
442 // in case just copied (into existence) or modified
443 m_pCtxt->UpdateStatusFromDisk(diffPos, idx);
445 int nIdx = m_pDirView->GetItemIndex(diffPos);
449 m_pDirView->UpdateDiffItemStatus(nIdx);
453 void CDirDoc::InitStatusStrings()
459 * @brief Update any resources necessary after a GUI language change
461 void CDirDoc::UpdateResources()
463 if (m_pDirView != nullptr)
464 m_pDirView->UpdateResources();
472 * @brief Stash away our view pointer.
474 void CDirDoc::SetDirView(CDirView * newView)
476 m_pDirView = newView;
477 // MFC has a view list for us, so lets check against it
478 POSITION pos = GetFirstViewPosition();
479 CDirView * temp = static_cast<CDirView *>(GetNextView(pos));
480 ASSERT(temp == m_pDirView); // verify that our stashed pointer is the same as MFC's
484 * @brief A new MergeDoc has been opened.
486 void CDirDoc::AddMergeDoc(IMergeDoc * pMergeDoc)
488 ASSERT(pMergeDoc != nullptr);
489 m_MergeDocs.AddTail(pMergeDoc);
493 * @brief MergeDoc informs us it is closing.
495 void CDirDoc::MergeDocClosing(IMergeDoc * pMergeDoc)
497 ASSERT(pMergeDoc != nullptr);
498 if (POSITION pos = m_MergeDocs.CPtrList::Find(pMergeDoc))
499 m_MergeDocs.RemoveAt(pos);
503 // If dir compare is empty (no compare results) and we are not closing
504 // because of reuse close also dir compare
505 if (m_pDirView != nullptr)
507 if (m_pCtxt == nullptr)
508 m_pDirView->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
510 else if (m_MergeDocs.GetCount() == 0)
517 * @brief Close MergeDocs opened from DirDoc.
519 * Asks confirmation for docs containing unsaved data and then
521 * @return `true` if success, `false` if user canceled or closing failed
523 bool CDirDoc::CloseMergeDocs()
525 while (!m_MergeDocs.IsEmpty())
526 if (!m_MergeDocs.GetTail()->CloseNow())
532 * @brief Update changed item's compare status
533 * @param [in] paths Paths for files we update
534 * @param [in] nDiffs Total amount of differences
535 * @param [in] nTrivialDiffs Amount of ignored differences
536 * @param [in] bIdentical `true` if files became identical, `false` otherwise.
538 void CDirDoc::UpdateChangedItem(const PathContext &paths,
539 UINT nDiffs, UINT nTrivialDiffs, bool bIdentical)
541 DIFFITEM *pos = FindItemFromPaths(*m_pCtxt, paths);
542 // If we failed files could have been swapped so lets try again
545 PathContext pathsSwapped(paths);
546 std::swap(pathsSwapped[0], pathsSwapped[pathsSwapped.GetSize() - 1]);
547 pos = FindItemFromPaths(*m_pCtxt, pathsSwapped);
548 if (!pos && paths.GetSize() > 2)
550 pathsSwapped = paths;
551 std::swap(pathsSwapped[0], pathsSwapped[1]);
552 pos = FindItemFromPaths(*m_pCtxt, pathsSwapped);
553 if (!pos && paths.GetSize() > 2)
555 pathsSwapped = paths;
556 std::swap(pathsSwapped[1], pathsSwapped[2]);
557 pos = FindItemFromPaths(*m_pCtxt, pathsSwapped);
562 // Update status if paths were found for items.
563 // Fail means we had unique items compared as 'renamed' items
564 // so there really is not status to update.
567 // Figure out new status code
568 UINT diffcode = (bIdentical ? DIFFCODE::SAME : DIFFCODE::DIFF);
570 // Update both views and diff context memory
571 m_pCtxt->SetDiffStatusCode(pos, diffcode, DIFFCODE::COMPAREFLAGS);
573 if (nDiffs != -1 && nTrivialDiffs != -1)
574 m_pCtxt->SetDiffCounts(pos, nDiffs, nTrivialDiffs);
575 for (int i = 0; i < m_pCtxt->GetCompareDirs(); ++i)
576 ReloadItemStatus(pos, i);
581 * @brief Cleans up after directory compare
583 void CDirDoc::CompareReady()
585 // Close and destroy the dialog after compare
586 if (m_pCmpProgressBar != nullptr)
587 m_pDirView->GetParentFrame()->ShowControlBar(m_pCmpProgressBar.get(), FALSE, FALSE);
588 m_pCmpProgressBar.reset();
592 * @brief Refresh cached options.
594 * For compare speed, we have to cache some frequently needed options,
595 * instead of getting option value every time from OptionsMgr. This
596 * function must be called every time options are changed to OptionsMgr.
598 void CDirDoc::RefreshOptions()
600 if (m_pCtxt != nullptr)
601 m_pCtxt->m_bRecursive = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
602 if (m_pDirView != nullptr)
603 m_pDirView->RefreshOptions();
607 * @brief Write path and filename to headerbar
608 * @note SetText() does not repaint unchanged text
610 void CDirDoc::UpdateHeaderPath(int nIndex)
612 CDirFrame *pf = m_pDirView->GetParentFrame();
613 ASSERT(pf != nullptr);
616 if (!m_strDesc[nIndex].empty())
617 sText = m_strDesc[nIndex];
620 sText = m_pCtxt->GetPath(nIndex);
621 ApplyDisplayRoot(nIndex, sText);
624 pf->GetHeaderInterface()->SetText(nIndex, sText);
628 * @brief virtual override called just before document is saved and closed
630 BOOL CDirDoc::SaveModified()
632 // Do not allow closing if there is a thread running
633 if (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
635 int ans = LangMessageBox(IDS_CONFIRM_CLOSE_WINDOW, MB_YESNO | MB_ICONWARNING);
638 m_diffThread.Abort();
639 while (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
644 return CDocument::SaveModified();
648 * @brief Send signal to thread to stop current scan
650 * @sa CDirCompStateBar::OnStop()
652 void CDirDoc::AbortCurrentScan()
654 m_diffThread.Abort();
658 * @brief Send signal to thread to pause current scan
660 void CDirDoc::PauseCurrentScan()
662 m_diffThread.Pause();
666 * @brief Send signal to thread to continue current scan
668 void CDirDoc::ContinueCurrentScan()
670 m_diffThread.Continue();
674 * @brief Returns true if there is an active scan that hasn't been aborted.
676 bool CDirDoc::IsCurrentScanAbortable() const
678 return (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING
679 && !m_diffThread.IsAborting());
683 * @brief Set directory description texts shown in headerbar
685 void CDirDoc::SetDescriptions(const String strDesc[])
687 if (strDesc != nullptr)
688 std::copy_n(strDesc, m_nDirs, m_strDesc);
692 * @brief Replace internal root by display root
693 * When we have a archive file open, this function converts physical folder
694 * (that is in the temp folder where archive was extracted) to the virtual
695 * path for showing. The virtual path is path to the archive file, archive
696 * file name and folder inside the archive.
697 * @param [in, out] sText Path to convert.
699 void CDirDoc::ApplyDisplayRoot(int nIndex, String &sText)
701 if (m_pTempPathContext != nullptr)
703 if (sText.find(m_pTempPathContext->m_strRoot[nIndex]) == String::npos)
705 for (int pane = 0; pane < m_nDirs; ++pane)
707 if (sText.find(m_pTempPathContext->m_strRoot[pane]) != String::npos)
714 sText.erase(0, m_pTempPathContext->m_strRoot[nIndex].length());
715 sText.insert(0, m_pTempPathContext->m_strDisplayRoot[nIndex]);
720 * @brief Set document title to given string or items compared.
722 * Formats and sets caption for directory compare window. Caption
723 * has left- and right-side paths separated with '-'.
725 * @param [in] lpszTitle New title for window. If this parameter
726 * is not `nullptr` we use this string, otherwise format caption from
729 void CDirDoc::SetTitle(LPCTSTR lpszTitle)
731 if (m_pDirView == nullptr)
734 if (lpszTitle != nullptr)
735 CDocument::SetTitle(lpszTitle);
736 else if (m_pCtxt == nullptr || m_pCtxt->GetLeftPath().empty() ||
737 m_pCtxt->GetRightPath().empty() ||
738 (m_nDirs > 2 && m_pCtxt->GetMiddlePath().empty()))
740 String title = _("Folder Comparison Results");
741 CDocument::SetTitle(title.c_str());
747 for (int index = 0; index < m_nDirs; index++)
749 String strPath = m_pCtxt->GetPath(index);
750 ApplyDisplayRoot(index, strPath);
751 sDirName[index] = paths::FindFileName(strPath);
753 if (std::count(&sDirName[0], &sDirName[0] + m_nDirs, sDirName[0]) == m_nDirs)
754 sTitle = sDirName[0] + strutils::format(_T(" x %d"), m_nDirs);
756 sTitle = strutils::join(&sDirName[0], &sDirName[0] + m_nDirs, _T(" - "));
757 CDocument::SetTitle(sTitle.c_str());
762 * @brief A string to display as a tooltip for MDITabbar
764 CString CDirDoc::GetTooltipString() const
766 return CMergeFrameCommon::GetTooltipString(m_pCtxt->GetNormalizedPaths(), m_strDesc, nullptr, nullptr).c_str();
770 * @brief Checks if current folders are opened from archive file.
771 * @return true if we are inside archive, false otherwise.
773 bool CDirDoc::IsArchiveFolders() const
775 if (m_pTempPathContext != nullptr)
781 void CDirDoc::Swap(int idx1, int idx2)
783 if (m_diffThread.GetThreadState() != CDiffThread::THREAD_COMPLETED)
785 std::swap(m_bRO[idx1], m_bRO[idx2]);
786 std::swap(m_strDesc[idx1], m_strDesc[idx2]);
787 if (m_pTempPathContext != nullptr)
788 m_pTempPathContext->Swap(idx1, idx2);
789 m_pCtxt->Swap(idx1, idx2);
790 m_pCompareStats->Swap(idx1, idx2);
791 for (int nIndex = 0; nIndex < m_nDirs; nIndex++)
792 UpdateHeaderPath(nIndex);
796 bool CDirDoc::MoveableToNextDiff()
798 if (m_pDirView == nullptr)
800 CMessageBoxDialog dlg(nullptr, _("Do you want to move to the next file?").c_str());
801 const int nFormerResult = dlg.GetFormerResult();
802 if (nFormerResult != -1 && nFormerResult == IDNO)
804 return m_pDirView->HasNextDiff();
807 bool CDirDoc::MoveableToPrevDiff()
809 if (m_pDirView == nullptr)
811 CMessageBoxDialog dlg(nullptr, _("Do you want to move to the previous file?").c_str());
812 const int nFormerResult = dlg.GetFormerResult();
813 if (nFormerResult != -1 && nFormerResult == IDNO)
815 return m_pDirView->HasPrevDiff();
818 void CDirDoc::MoveToNextDiff(IMergeDoc *pMergeDoc)
820 if (m_pDirView == nullptr)
822 if (AfxMessageBox(_("Do you want to move to the next file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN, IDS_MOVE_TO_NEXTFILE) == IDYES)
824 pMergeDoc->CloseNow();
825 m_pDirView->OpenNextDiff();
826 GetMainFrame()->OnUpdateFrameTitle(FALSE);
830 void CDirDoc::MoveToPrevDiff(IMergeDoc *pMergeDoc)
832 if (m_pDirView == nullptr)
834 if (AfxMessageBox(_("Do you want to move to the previous file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN, IDS_MOVE_TO_PREVFILE) == IDYES)
836 pMergeDoc->CloseNow();
837 m_pDirView->OpenPrevDiff();
838 GetMainFrame()->OnUpdateFrameTitle(FALSE);
842 void CDirDoc::MoveToFirstFile(IMergeDoc* pMergeDoc)
844 if (m_pDirView == nullptr)
846 if (AfxMessageBox(_("Do you want to move to the first file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN, IDS_MOVE_TO_FIRSTFILE) == IDYES)
848 pMergeDoc->CloseNow();
849 m_pDirView->OpenFirstFile();
850 GetMainFrame()->OnUpdateFrameTitle(FALSE);
854 void CDirDoc::MoveToNextFile(IMergeDoc* pMergeDoc)
856 if (m_pDirView == nullptr)
858 if (AfxMessageBox(_("Do you want to move to the next file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN, IDS_MOVE_TO_NEXTFILE) == IDYES)
860 pMergeDoc->CloseNow();
861 m_pDirView->OpenNextFile();
862 GetMainFrame()->OnUpdateFrameTitle(FALSE);
866 void CDirDoc::MoveToPrevFile(IMergeDoc* pMergeDoc)
868 if (m_pDirView == nullptr)
870 if (AfxMessageBox(_("Do you want to move to the previous file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN, IDS_MOVE_TO_PREVFILE) == IDYES)
872 pMergeDoc->CloseNow();
873 m_pDirView->OpenPrevFile();
874 GetMainFrame()->OnUpdateFrameTitle(FALSE);
878 void CDirDoc::MoveToLastFile(IMergeDoc* pMergeDoc)
880 if (m_pDirView == nullptr)
882 if (AfxMessageBox(_("Do you want to move to the last file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN, IDS_MOVE_TO_LASTFILE) == IDYES)
884 pMergeDoc->CloseNow();
885 m_pDirView->OpenLastFile();
886 GetMainFrame()->OnUpdateFrameTitle(FALSE);
890 bool CDirDoc::IsFirstFile()
892 if (m_pDirView == nullptr)
894 return m_pDirView->IsFirstFile();
897 bool CDirDoc::IsLastFile()
899 if (m_pDirView == nullptr)
901 return m_pDirView->IsLastFile();
904 bool CDirDoc::CompareFilesIfFilesAreLarge(int nFiles, const FileLocation ifileloc[])
907 bool bLargeFile = false;
908 for (int i = 0; i < nFiles; ++i)
910 di.diffFileInfo[i].SetFile(paths::FindFileName(ifileloc[i].filepath));
911 if (di.diffFileInfo[i].Update(ifileloc[i].filepath))
912 di.diffcode.setSideFlag(i);
915 di.diffcode.diffcode |= DIFFCODE::THREEWAY;
917 size_t fileSizeThreshold = GetOptionsMgr()->GetInt(OPT_FILE_SIZE_THRESHOLD);
918 for (int i = 0; i < nFiles; ++i)
920 if (di.diffFileInfo[i].size != DirItem::FILE_SIZE_NONE && di.diffFileInfo[i].size > fileSizeThreshold)
927 for (int i = 0; i < nFiles; ++i)
928 paths.SetPath(i, ifileloc[i].filepath.empty() ? _T("NUL") : paths::GetParentPath(ifileloc[i].filepath));
929 CDiffContext ctxt(paths, CMP_QUICK_CONTENT);
930 DirViewColItems ci(nFiles, std::vector<String>{});
931 String msg = LoadResString(IDS_COMPARE_LARGE_FILES);
934 String sidestr[] = { _("Left:"), _("Right:") };
935 for (int i = 0; i < nFiles; ++i)
937 if (ifileloc[i].filepath.empty())
939 msg += strutils::format(_T("%s %s\n\n"), sidestr[i], _("None"));
943 msg += strutils::format(_T("%s %s\n %s: %s\n %s: %s (%s)\n\n"),
944 sidestr[i].c_str(), ifileloc[i].filepath.c_str(),
945 ci.GetColDisplayName(3 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 3 + i, di).c_str(),
946 ci.GetColDisplayName(8 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 8 + i, di).c_str(), ci.ColGetTextToDisplay(&ctxt, 10 + i, di).c_str());
952 String sidestr[] = { _("Left:"), _("Middle:"), _("Right:") };
953 for (int i = 0; i < nFiles; ++i)
955 if (ifileloc[i].filepath.empty())
957 msg += strutils::format(_T("%s %s\n\n"), sidestr[i], _("None"));
961 msg += strutils::format(_T("%s %s\n %s: %s\n %s: %s (%s)\n\n"),
962 sidestr[i].c_str(), ifileloc[i].filepath.c_str(),
963 ci.GetColDisplayName(3 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 3 + i, di).c_str(),
964 ci.GetColDisplayName(10 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 10 + i, di).c_str(), ci.ColGetTextToDisplay(&ctxt, 13 + i, di).c_str());
968 CMessageBoxDialog dlg(
969 m_pDirView ? m_pDirView->GetParentFrame() : nullptr,
971 MB_YESNOCANCEL | MB_ICONQUESTION | MB_DONT_ASK_AGAIN, 0U,
972 _T("CompareLargeFiles"));
973 INT_PTR ans = dlg.DoModal();
976 else if (ans == IDNO)
979 InitDiffContext(&ctxt);
980 FolderCmp cmp(&ctxt);
981 CWaitCursor waitstatus;
982 di.diffcode.diffcode |= cmp.prepAndCompareFiles(di);
983 if (di.diffcode.isResultSame())
985 ctxt.GetComparePaths(di, paths);
986 CMergeFrameCommon::ShowIdenticalMessage(paths, true,
987 [](LPCTSTR msg, UINT flags, UINT id) -> int { return AfxMessageBox(msg, flags, id); });
991 AfxMessageBox(ci.ColGetTextToDisplay(&ctxt, 2, di).c_str(), MB_OK | MB_ICONINFORMATION);
993 theApp.SetLastCompareResult(di.diffcode.isResultDiff() ? 1 : 0);
997 void CDirDoc::OnBnClickedComparisonStop()
999 if (m_pCmpProgressBar != nullptr)
1000 m_pCmpProgressBar->EndUpdating();
1004 void CDirDoc::OnBnClickedComparisonPause()
1006 if (m_pCmpProgressBar != nullptr)
1007 m_pCmpProgressBar->SetPaused(true);
1011 void CDirDoc::OnBnClickedComparisonContinue()
1013 if (m_pCmpProgressBar != nullptr)
1014 m_pCmpProgressBar->SetPaused(false);
1015 ContinueCurrentScan();