OSDN Git Service

resource.h: Add IDS_PLUGIN_DESCRIPTION*
[winmerge-jp/winmerge-jp.git] / Src / DirDoc.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /** 
8  * @file  DirDoc.cpp
9  *
10  * @brief Implementation file for CDirDoc
11  *
12  */
13
14 #include "StdAfx.h"
15 #include "DirDoc.h"
16 #include <Poco/StringTokenizer.h>
17 #include <boost/range/mfc.hpp>
18 #include "Merge.h"
19 #include "IMergeDoc.h"
20 #include "CompareOptions.h"
21 #include "UnicodeString.h"
22 #include "CompareStats.h"
23 #include "FilterList.h"
24 #include "SubstitutionList.h"
25 #include "DirView.h"
26 #include "DirFrame.h"
27 #include "MainFrm.h"
28 #include "paths.h"
29 #include "7zCommon.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"
36 #include "unicoder.h"
37 #include "DirActions.h"
38 #include "DirScan.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>
45
46 #ifdef _DEBUG
47 #define new DEBUG_NEW
48 #endif
49
50 using Poco::StringTokenizer;
51 using boost::begin;
52 using boost::end;
53
54 int CDirDoc::m_nDirsTemp = 2;
55
56 /////////////////////////////////////////////////////////////////////////////
57 // CDirDoc
58
59 IMPLEMENT_DYNCREATE(CDirDoc, CDocument)
60
61 /**
62  * @brief Constructor.
63  */
64 CDirDoc::CDirDoc()
65 : m_pCtxt(nullptr)
66 , m_pDirView(nullptr)
67 , m_pCompareStats(nullptr)
68 , m_bMarkedRescan(false)
69 , m_pTempPathContext(nullptr)
70 , m_bGeneratingReport(false)
71 , m_pReport(nullptr)
72 {
73         m_nDirs = m_nDirsTemp;
74
75         m_bRO[0] = false;
76         m_bRO[1] = false;
77         m_bRO[2] = false;
78 }
79
80 /**
81  * @brief Destructor.
82  *
83  * Clears document list and deleted possible archive-temp files.
84  */
85 CDirDoc::~CDirDoc()
86 {
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)
92         {
93                 m_pTempPathContext = m_pTempPathContext->DeleteHead();
94         }
95 }
96
97 /**
98  * @brief Called when new dirdoc is created.
99  */
100 BOOL CDirDoc::OnNewDocument()
101 {
102         if (!CDocument::OnNewDocument())
103                 return FALSE;
104
105         return TRUE;
106 }
107
108
109 BEGIN_MESSAGE_MAP(CDirDoc, CDocument)
110         //{{AFX_MSG_MAP(CDirDoc)
111                 // NOTE - the ClassWizard will add and remove mapping macros here.
112         //}}AFX_MSG_MAP
113 END_MESSAGE_MAP()
114
115
116 /////////////////////////////////////////////////////////////////////////////
117 // CDirDoc serialization
118
119 void CDirDoc::Serialize(CArchive& ar)
120 {
121         if (ar.IsStoring())
122         {
123                 // TODO: add storing code here
124         }
125         else
126         {
127                 // TODO: add loading code here
128         }
129 }
130
131 /////////////////////////////////////////////////////////////////////////////
132 // CDirDoc commands
133
134 /**
135  * @brief Initialise directory compare for given paths.
136  *
137  * Initialises directory compare with paths given and recursive choice.
138  * Previous compare context is first free'd.
139  * @param [in] paths Paths to compare
140  * @param [in] bRecursive If `true` subdirectories are included to compare.
141  */
142 void CDirDoc::InitCompare(const PathContext & paths, bool bRecursive, CTempPathContext *pTempPathContext)
143 {
144         // Abort previous comparing
145         while (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
146         {
147                 m_diffThread.Abort();
148                 Sleep(50);
149         }
150
151         m_pDirView->DeleteAllDisplayItems();
152         // Anything that can go wrong here will yield an exception.
153         // Default implementation of operator new() never returns `nullptr`.
154         
155         if (m_pCompareStats == nullptr)
156                 m_pCompareStats.reset(new CompareStats(m_nDirs));
157
158         m_pCtxt.reset(new CDiffContext(paths,
159                         GetOptionsMgr()->GetInt(OPT_CMP_METHOD)));
160         m_pCtxt->m_bRecursive = bRecursive;
161
162         if (pTempPathContext != nullptr)
163         {
164                 int nIndex;
165                 for (nIndex = 0; nIndex < m_nDirs; nIndex++)
166                         ApplyDisplayRoot(nIndex, pTempPathContext->m_strDisplayRoot[nIndex]);
167                 pTempPathContext->m_pParent = m_pTempPathContext;
168                 m_pTempPathContext = pTempPathContext;
169                 for (nIndex = 0; nIndex < m_nDirs; nIndex++)
170                         m_pTempPathContext->m_strRoot[nIndex] = m_pCtxt->GetNormalizedPath(nIndex);
171         }
172 }
173
174
175 /**
176  * @brief Load line filters to the compare context.
177  * Loads linefilters, converts them to UTF-8 and sets them for compare context.
178  */
179 void CDirDoc::LoadLineFilterList(CDiffContext *pCtxt)
180 {
181         ASSERT(pCtxt != nullptr);
182         
183         bool bFilters = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
184         String filters = theApp.m_pLineFilters->GetAsString();
185         if (!bFilters || filters.empty())
186         {
187                 pCtxt->m_pFilterList.reset();
188                 return;
189         }
190
191         if (pCtxt->m_pFilterList)
192                 pCtxt->m_pFilterList->RemoveAllFilters();
193         else
194                 pCtxt->m_pFilterList.reset(new FilterList());
195
196         std::string regexp_str = ucr::toUTF8(filters);
197
198         // Add every "line" of regexps to regexp list
199         StringTokenizer tokens(regexp_str, "\r\n");
200         for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
201                 pCtxt->m_pFilterList->AddRegExp(*it);
202 }
203
204 void CDirDoc::LoadSubstitutionFiltersList(CDiffContext* pCtxt)
205 {
206         ASSERT(pCtxt != nullptr);
207
208         bool SubstitutionFiltersEnabled = theApp.m_pSubstitutionFiltersList->GetEnabled();
209         if (!SubstitutionFiltersEnabled || theApp.m_pSubstitutionFiltersList->GetCount() == 0)
210         {
211                 pCtxt->m_pSubstitutionList.reset();
212                 return;
213         }
214
215         pCtxt->m_pSubstitutionList = theApp.m_pSubstitutionFiltersList->MakeSubstitutionList();
216 }
217
218 void CDirDoc::DiffThreadCallback(int& state)
219 {
220         PostMessage(m_pDirView->GetSafeHwnd(), MSG_UI_UPDATE, state, false);
221 }
222
223 void CDirDoc::InitDiffContext(CDiffContext *pCtxt)
224 {
225         LoadLineFilterList(pCtxt);
226         LoadSubstitutionFiltersList(pCtxt);
227
228         DIFFOPTIONS options = {0};
229         Options::DiffOptions::Load(GetOptionsMgr(), options);
230
231         pCtxt->CreateCompareOptions(GetOptionsMgr()->GetInt(OPT_CMP_METHOD), options);
232
233         pCtxt->m_iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
234         if ((pCtxt->m_iGuessEncodingType >> 16) == 0)
235                 pCtxt->m_iGuessEncodingType |= 50001 << 16;
236         pCtxt->m_bIgnoreSmallTimeDiff = GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME);
237         pCtxt->m_bStopAfterFirstDiff = GetOptionsMgr()->GetBool(OPT_CMP_STOP_AFTER_FIRST);
238         pCtxt->m_nQuickCompareLimit = GetOptionsMgr()->GetInt(OPT_CMP_QUICK_LIMIT);
239         pCtxt->m_nBinaryCompareLimit = GetOptionsMgr()->GetInt(OPT_CMP_BINARY_LIMIT);
240         pCtxt->m_bPluginsEnabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
241         pCtxt->m_bWalkUniques = GetOptionsMgr()->GetBool(OPT_CMP_WALK_UNIQUE_DIRS);
242         pCtxt->m_bIgnoreReparsePoints = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_REPARSE_POINTS);
243         pCtxt->m_bIgnoreCodepage = GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE);
244         pCtxt->m_bEnableImageCompare = GetOptionsMgr()->GetBool(OPT_CMP_ENABLE_IMGCMP_IN_DIRCMP);
245         pCtxt->m_dColorDistanceThreshold = GetOptionsMgr()->GetInt(OPT_CMP_IMG_THRESHOLD) / 1000.0;
246
247         m_imgfileFilter.UseMask(true);
248         m_imgfileFilter.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
249         pCtxt->m_pImgfileFilter = &m_imgfileFilter;
250
251         pCtxt->m_pCompareStats = m_pCompareStats.get();
252
253         // Make sure filters are up-to-date
254         theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
255         pCtxt->m_piFilterGlobal = theApp.m_pGlobalFileFilter.get();
256         
257         // All plugin management is done by our plugin manager
258         pCtxt->m_piPluginInfos = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED) ? &m_pluginman : nullptr;
259 }
260
261 /**
262  * @brief Perform directory comparison again from scratch
263  */
264 void CDirDoc::Rescan()
265 {
266         if (m_pCtxt == nullptr)
267                 return;
268
269         CDirFrame *pf = m_pDirView->GetParentFrame();
270
271         // If we're already doing a rescan, bail out
272         UINT threadState = m_diffThread.GetThreadState();
273         if (threadState == CDiffThread::THREAD_COMPARING)
274                 return;
275
276         if (!m_bGeneratingReport)
277                 m_pCompareStats->Reset();
278         m_pDirView->StartCompare(m_pCompareStats.get());
279
280         if (!m_bGeneratingReport)
281                 m_pDirView->DeleteAllDisplayItems();
282         // Don't clear if only scanning selected items
283         if (!m_bMarkedRescan && !m_bGeneratingReport)
284         {
285                 m_pCtxt->RemoveAll();
286                 m_pCtxt->InitDiffItemList();
287         }
288
289         InitDiffContext(m_pCtxt.get());
290
291         pf->GetHeaderInterface()->SetPaneCount(m_nDirs);
292         pf->GetHeaderInterface()->SetOnSetFocusCallback([&](int pane) {
293                 m_pDirView->SetActivePane(pane);
294                 GetOptionsMgr()->SaveOption(OPT_ACTIVE_PANE, pane);
295         });
296         for (int nIndex = 0; nIndex < m_nDirs; nIndex++)
297         {
298                 UpdateHeaderPath(nIndex);
299                 // draw the headers as inactive ones
300                 pf->GetHeaderInterface()->SetActive(nIndex, false);
301         }
302         pf->GetHeaderInterface()->Resize();
303         int nPane = GetOptionsMgr()->GetInt(OPT_ACTIVE_PANE);
304         m_pDirView->SetActivePane((nPane >= 0 && nPane < m_nDirs) ? nPane : 0);
305
306         // Show current compare method name and active filter name in statusbar
307         pf->SetFilterStatusDisplay(theApp.m_pGlobalFileFilter->GetFilterNameOrMask().c_str());
308         pf->SetCompareMethodStatusDisplay(m_pCtxt->GetCompareMethod());
309
310         // Folder names to compare are in the compare context
311         m_diffThread.SetContext(m_pCtxt.get());
312         m_diffThread.RemoveListener(this, &CDirDoc::DiffThreadCallback);
313         m_diffThread.AddListener(this, &CDirDoc::DiffThreadCallback);
314         if (m_bGeneratingReport)
315         {
316                 m_diffThread.SetCollectFunction([&](DiffFuncStruct* myStruct) {
317                         int m = 0;
318                         if (m_pReport->GetCopyToClipboard())
319                         {
320                                 ++m;
321                                 if (m_pReport->GetReportType() == REPORT_TYPE_SIMPLEHTML)
322                                         ++m;
323                         }
324                         if (!m_pReport->GetReportFile().empty())
325                                 ++m;
326                         myStruct->context->m_pCompareStats->IncreaseTotalItems(
327                                 (m_pDirView->GetListCtrl().GetItemCount() - (myStruct->context->m_bRecursive ? 0 : 1)) * m);
328                 });
329                 m_diffThread.SetCompareFunction([&](DiffFuncStruct* myStruct) {
330                         m_pReport->SetDiffFuncStruct(myStruct);
331                         myStruct->pSemaphore->wait();
332                         String errStr;
333                         if (m_pReport->GenerateReport(errStr))
334                         {
335                                 if (errStr.empty())
336                                 {
337                                         if (GetReportFile().empty())
338                                                 LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
339                                 }
340                                 else
341                                 {
342                                         String msg = strutils::format_string1(
343                                                 _("Error creating the report:\n%1"),
344                                                 errStr);
345                                         AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
346                                 }
347                         }
348                         SetGeneratingReport(false);
349                         SetReport(nullptr);
350                 });
351         }
352         else if (m_bMarkedRescan)
353         {
354                 m_diffThread.SetCollectFunction(nullptr);
355                 m_diffThread.SetCompareFunction([](DiffFuncStruct* myStruct) {
356                         int nItems = DirScan_UpdateMarkedItems(myStruct, nullptr);
357                         myStruct->context->m_pCompareStats->IncreaseTotalItems(nItems);
358                         DirScan_CompareRequestedItems(myStruct, nullptr);
359                 });
360         }
361         else
362         {
363                 m_diffThread.SetCollectFunction([](DiffFuncStruct* myStruct) {
364                         bool casesensitive = false;
365                         int depth = myStruct->context->m_bRecursive ? -1 : 0;
366                         PathContext paths = myStruct->context->GetNormalizedPaths();
367                         String subdir[3] = {_T(""), _T(""), _T("")}; // blank to start at roots specified in diff context
368                         // Build results list (except delaying file comparisons until below)
369                         DirScan_GetItems(paths, subdir, myStruct,
370                                         casesensitive, depth, nullptr, myStruct->context->m_bWalkUniques);
371                 });
372                 m_diffThread.SetCompareFunction([](DiffFuncStruct* myStruct) {
373                         DirScan_CompareItems(myStruct, nullptr);
374                 });
375         }
376         m_diffThread.CompareDirectories();
377         m_bMarkedRescan = false;
378 }
379
380 /**
381  * @brief Empty & reload listview (of files & columns) with comparison results
382  * @todo Better solution for special items ("..")?
383  */
384 void CDirDoc::Redisplay()
385 {
386         if (m_pDirView == nullptr)
387                 return;
388
389         // Do not redisplay an empty CDirView
390         // Not only does it not have results, but AddSpecialItems will crash
391         // trying to dereference null context pointer to get to paths
392         if (!HasDiffs())
393                 return;
394
395         m_pDirView->Redisplay();
396 }
397
398 CDirView * CDirDoc::GetMainView() const
399 {
400         CDirView *pView = nullptr;
401         if (POSITION pos = GetFirstViewPosition())
402         {
403                 pView = static_cast<CDirView*>(GetNextView(pos));
404                 ASSERT_KINDOF(CDirView, pView);
405         }
406         return pView;
407 }
408
409 /**
410  * @brief Update in-memory diffitem status from disk and update view.
411  * @param [in] diffPos POSITION of item in UI list.
412  * @param [in] idx index to reload.
413  * @note Do not call this function from DirView code! This function
414  * calls slow DirView functions to get item position and to update GUI.
415  * Use UpdateStatusFromDisk() function instead.
416  */
417 void CDirDoc::ReloadItemStatus(DIFFITEM *diffPos, int idx)
418 {
419         // in case just copied (into existence) or modified
420         m_pCtxt->UpdateStatusFromDisk(diffPos, idx);
421
422         int nIdx = m_pDirView->GetItemIndex(diffPos);
423         if (nIdx != -1)
424         {
425                 // Update view
426                 m_pDirView->UpdateDiffItemStatus(nIdx);
427         }
428 }
429
430 void CDirDoc::InitStatusStrings()
431 {
432
433 }
434
435 /**
436  * @brief Update any resources necessary after a GUI language change
437  */
438 void CDirDoc::UpdateResources()
439 {
440         if (m_pDirView != nullptr)
441                 m_pDirView->UpdateResources();
442
443         SetTitle(nullptr);
444
445         Redisplay();
446 }
447
448 /**
449  * @brief Stash away our view pointer.
450  */
451 void CDirDoc::SetDirView(CDirView * newView)
452 {
453         m_pDirView = newView;
454         // MFC has a view list for us, so lets check against it
455         POSITION pos = GetFirstViewPosition();
456         CDirView * temp = static_cast<CDirView *>(GetNextView(pos));
457         ASSERT(temp == m_pDirView); // verify that our stashed pointer is the same as MFC's
458 }
459
460 /**
461  * @brief A new MergeDoc has been opened.
462  */
463 void CDirDoc::AddMergeDoc(IMergeDoc * pMergeDoc)
464 {
465         ASSERT(pMergeDoc != nullptr);
466         m_MergeDocs.AddTail(pMergeDoc);
467 }
468
469 /**
470  * @brief MergeDoc informs us it is closing.
471  */
472 void CDirDoc::MergeDocClosing(IMergeDoc * pMergeDoc)
473 {
474         ASSERT(pMergeDoc != nullptr);
475         if (POSITION pos = m_MergeDocs.CPtrList::Find(pMergeDoc))
476                 m_MergeDocs.RemoveAt(pos);
477         else
478                 ASSERT(false);
479
480         // If dir compare is empty (no compare results) and we are not closing
481         // because of reuse close also dir compare
482         if (m_pDirView != nullptr)
483         {
484                 if (m_pCtxt == nullptr)
485                         m_pDirView->PostMessage(WM_COMMAND, ID_FILE_CLOSE);
486         }
487         else if (m_MergeDocs.GetCount() == 0)
488         {
489                 delete this;
490         }
491 }
492
493 /**
494  * @brief Close MergeDocs opened from DirDoc.
495  *
496  * Asks confirmation for docs containing unsaved data and then
497  * closes MergeDocs.
498  * @return `true` if success, `false` if user canceled or closing failed
499  */
500 bool CDirDoc::CloseMergeDocs()
501 {
502         while (!m_MergeDocs.IsEmpty())
503                 if (!m_MergeDocs.GetTail()->CloseNow())
504                         return false;
505         return true;
506 }
507
508 /**
509  * @brief Update changed item's compare status
510  * @param [in] paths Paths for files we update
511  * @param [in] nDiffs Total amount of differences
512  * @param [in] nTrivialDiffs Amount of ignored differences
513  * @param [in] bIdentical `true` if files became identical, `false` otherwise.
514  */
515 void CDirDoc::UpdateChangedItem(const PathContext &paths,
516         UINT nDiffs, UINT nTrivialDiffs, bool bIdentical)
517 {
518         DIFFITEM *pos = FindItemFromPaths(*m_pCtxt, paths);
519         // If we failed files could have been swapped so lets try again
520         if (!pos)
521         {
522                 PathContext pathsSwapped(paths);
523                 std::swap(pathsSwapped[0], pathsSwapped[pathsSwapped.GetSize() - 1]);
524                 pos = FindItemFromPaths(*m_pCtxt, pathsSwapped);
525                 if (!pos && paths.GetSize() > 2)
526                 {
527                         pathsSwapped = paths;
528                         std::swap(pathsSwapped[0], pathsSwapped[1]);
529                         pos = FindItemFromPaths(*m_pCtxt, pathsSwapped);
530                         if (!pos && paths.GetSize() > 2)
531                         {
532                                 pathsSwapped = paths;
533                                 std::swap(pathsSwapped[1], pathsSwapped[2]);
534                                 pos = FindItemFromPaths(*m_pCtxt, pathsSwapped);
535                         }
536                 }
537         }
538         
539         // Update status if paths were found for items.
540         // Fail means we had unique items compared as 'renamed' items
541         // so there really is not status to update.
542         if (pos > 0)
543         {
544                 // Figure out new status code
545                 UINT diffcode = (bIdentical ? DIFFCODE::SAME : DIFFCODE::DIFF);
546
547                 // Update both views and diff context memory
548                 m_pCtxt->SetDiffStatusCode(pos, diffcode, DIFFCODE::COMPAREFLAGS);
549
550                 if (nDiffs != -1 && nTrivialDiffs != -1)
551                         m_pCtxt->SetDiffCounts(pos, nDiffs, nTrivialDiffs);
552                 for (int i = 0; i < m_pCtxt->GetCompareDirs(); ++i)
553                         ReloadItemStatus(pos, i);
554         }
555 }
556
557 /**
558  * @brief Cleans up after directory compare
559  */
560 void CDirDoc::CompareReady()
561 {
562 }
563
564 /**
565  * @brief Refresh cached options.
566  *
567  * For compare speed, we have to cache some frequently needed options,
568  * instead of getting option value every time from OptionsMgr. This
569  * function must be called every time options are changed to OptionsMgr.
570  */
571 void CDirDoc::RefreshOptions()
572 {
573         if (m_pCtxt != nullptr)
574                 m_pCtxt->m_bRecursive = GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS);
575         if (m_pDirView != nullptr)
576                 m_pDirView->RefreshOptions();
577 }
578
579 /**
580  * @brief Write path and filename to headerbar
581  * @note SetText() does not repaint unchanged text
582  */
583 void CDirDoc::UpdateHeaderPath(int nIndex)
584 {
585         CDirFrame *pf = m_pDirView->GetParentFrame();
586         ASSERT(pf != nullptr);
587         String sText;
588
589         if (!m_strDesc[nIndex].empty())
590                 sText = m_strDesc[nIndex];
591         else
592         {
593                 sText = m_pCtxt->GetPath(nIndex);
594                 ApplyDisplayRoot(nIndex, sText);
595         }
596
597         pf->GetHeaderInterface()->SetText(nIndex, sText);
598 }
599
600 /**
601  * @brief virtual override called just before document is saved and closed
602  */
603 BOOL CDirDoc::SaveModified() 
604 {
605         // Do not allow closing if there is a thread running
606         if (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
607         {
608                 int ans = LangMessageBox(IDS_CONFIRM_CLOSE_WINDOW, MB_YESNO | MB_ICONWARNING);
609                 if (ans == IDNO)
610                         return FALSE;
611                 m_diffThread.Abort();
612                 while (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING)
613                         Sleep(50);
614         }
615         
616         return CDocument::SaveModified();
617 }
618
619 /**
620  * @brief Send signal to thread to stop current scan
621  *
622  * @sa CDirCompStateBar::OnStop()
623  */
624 void CDirDoc::AbortCurrentScan()
625 {
626         m_diffThread.Abort();
627 }
628
629 /**
630  * @brief Send signal to thread to pause current scan
631  */
632 void CDirDoc::PauseCurrentScan()
633 {
634         m_diffThread.Pause();
635 }
636
637 /**
638  * @brief Send signal to thread to continue current scan
639  */
640 void CDirDoc::ContinueCurrentScan()
641 {
642         m_diffThread.Continue();
643 }
644
645 /**
646  * @brief Returns true if there is an active scan that hasn't been aborted.
647  */
648 bool CDirDoc::IsCurrentScanAbortable() const
649 {
650         return (m_diffThread.GetThreadState() == CDiffThread::THREAD_COMPARING 
651                 && !m_diffThread.IsAborting());
652 }
653
654 /**
655  * @brief Set directory description texts shown in headerbar
656  */
657 void CDirDoc::SetDescriptions(const String strDesc[])
658 {
659         if (strDesc != nullptr)
660                 std::copy_n(strDesc, m_nDirs, m_strDesc);
661 }
662
663 /**
664  * @brief Replace internal root by display root
665  * When we have a archive file open, this function converts physical folder
666  * (that is in the temp folder where archive was extracted) to the virtual
667  * path for showing. The virtual path is path to the archive file, archive
668  * file name and folder inside the archive.
669  * @param [in, out] sText Path to convert.
670  */
671 void CDirDoc::ApplyDisplayRoot(int nIndex, String &sText)
672 {
673         if (m_pTempPathContext != nullptr)
674         {
675                 if (sText.find(m_pTempPathContext->m_strRoot[nIndex]) == String::npos)
676                 {
677                         for (int pane = 0; pane < m_nDirs; ++pane)
678                         {
679                                 if (sText.find(m_pTempPathContext->m_strRoot[pane]) != String::npos)
680                                 {
681                                         nIndex = pane;
682                                         break;
683                                 }
684                         }
685                 }
686                 sText.erase(0, m_pTempPathContext->m_strRoot[nIndex].length());
687                 sText.insert(0, m_pTempPathContext->m_strDisplayRoot[nIndex]);
688         }
689 }
690
691 /**
692  * @brief Set document title to given string or items compared.
693  * 
694  * Formats and sets caption for directory compare window. Caption
695  * has left- and right-side paths separated with '-'.
696  *
697  * @param [in] lpszTitle New title for window. If this parameter
698  * is not `nullptr` we use this string, otherwise format caption from
699  * actual paths.
700  */
701 void CDirDoc::SetTitle(LPCTSTR lpszTitle)
702 {
703         if (m_pDirView == nullptr)
704                 return;
705
706         if (lpszTitle != nullptr)
707                 CDocument::SetTitle(lpszTitle);
708         else if (m_pCtxt == nullptr || m_pCtxt->GetLeftPath().empty() ||
709                 m_pCtxt->GetRightPath().empty() || 
710                 (m_nDirs > 2 && m_pCtxt->GetMiddlePath().empty()))
711         {
712                 String title = _("Folder Comparison Results");
713                 CDocument::SetTitle(title.c_str());
714         }
715         else
716         {
717                 String sTitle;
718                 String sDirName[3];
719                 for (int index = 0; index < m_nDirs; index++)
720                 {
721                         String strPath = m_pCtxt->GetPath(index);
722                         ApplyDisplayRoot(index, strPath);
723                         sDirName[index] = paths::FindFileName(strPath);
724                 }
725                 if (std::count(&sDirName[0], &sDirName[0] + m_nDirs, sDirName[0]) == m_nDirs)
726                         sTitle = sDirName[0] + strutils::format(_T(" x %d"), m_nDirs);
727                 else
728                         sTitle = strutils::join(&sDirName[0], &sDirName[0] + m_nDirs, _T(" - "));
729                 CDocument::SetTitle(sTitle.c_str());
730         }       
731 }
732
733
734 /**
735  * @brief Checks if current folders are opened from archive file.
736  * @return true if we are inside archive, false otherwise.
737  */
738 bool CDirDoc::IsArchiveFolders() const
739 {
740         if (m_pTempPathContext != nullptr)
741                 return true;
742         else
743                 return false;
744 }
745
746 void CDirDoc::Swap(int idx1, int idx2)
747 {
748         std::swap(m_bRO[idx1], m_bRO[idx2]);
749         std::swap(m_strDesc[idx1], m_strDesc[idx2]);
750         if (m_pTempPathContext != nullptr)
751                 m_pTempPathContext->Swap(idx1, idx2);
752         m_pCtxt->Swap(idx1, idx2);
753         m_pCompareStats->Swap(idx1, idx2);
754         for (int nIndex = 0; nIndex < m_nDirs; nIndex++)
755                 UpdateHeaderPath(nIndex);
756         SetTitle(nullptr);
757 }
758
759 bool CDirDoc::MoveableToNextDiff()
760 {
761         if (m_pDirView == nullptr)
762                 return false;
763         CMessageBoxDialog dlg(nullptr, _("Do you want to move to the next file?").c_str());
764         const int nFormerResult = dlg.GetFormerResult();
765         if (nFormerResult != -1 && nFormerResult == IDNO)
766                 return false;
767         return m_pDirView->HasNextDiff();
768 }
769
770 bool CDirDoc::MoveableToPrevDiff()
771 {
772         if (m_pDirView == nullptr)
773                 return false;
774         CMessageBoxDialog dlg(nullptr, _("Do you want to move to the previous file?").c_str());
775         const int nFormerResult = dlg.GetFormerResult();
776         if (nFormerResult != -1 && nFormerResult == IDNO)
777                 return false;
778         return m_pDirView->HasPrevDiff();
779 }
780
781 void CDirDoc::MoveToNextDiff(IMergeDoc *pMergeDoc)
782 {
783         if (m_pDirView == nullptr)
784                 return;
785         if (AfxMessageBox(_("Do you want to move to the next file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
786         {
787                 pMergeDoc->CloseNow();
788                 m_pDirView->OpenNextDiff();
789                 GetMainFrame()->OnUpdateFrameTitle(FALSE);
790         }
791 }
792
793 void CDirDoc::MoveToPrevDiff(IMergeDoc *pMergeDoc)
794 {
795         if (m_pDirView == nullptr)
796                 return;
797         if (AfxMessageBox(_("Do you want to move to the previous file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
798         {
799                 pMergeDoc->CloseNow();
800                 m_pDirView->OpenPrevDiff();
801                 GetMainFrame()->OnUpdateFrameTitle(FALSE);
802         }
803 }
804
805 void CDirDoc::MoveToFirstFile(IMergeDoc* pMergeDoc)
806 {
807         if (m_pDirView == nullptr)
808                 return;
809         if (AfxMessageBox(_("Do you want to move to the first file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
810         {
811                 pMergeDoc->CloseNow();
812                 m_pDirView->OpenFirstFile();
813                 GetMainFrame()->OnUpdateFrameTitle(FALSE);
814         }
815 }
816
817 void CDirDoc::MoveToNextFile(IMergeDoc* pMergeDoc)
818 {
819         if (m_pDirView == nullptr)
820                 return;
821         if (AfxMessageBox(_("Do you want to move to the next file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
822         {
823                 pMergeDoc->CloseNow();
824                 m_pDirView->OpenNextFile();
825                 GetMainFrame()->OnUpdateFrameTitle(FALSE);
826         }
827 }
828
829 void CDirDoc::MoveToPrevFile(IMergeDoc* pMergeDoc)
830 {
831         if (m_pDirView == nullptr)
832                 return;
833         if (AfxMessageBox(_("Do you want to move to the previous file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
834         {
835                 pMergeDoc->CloseNow();
836                 m_pDirView->OpenPrevFile();
837                 GetMainFrame()->OnUpdateFrameTitle(FALSE);
838         }
839 }
840
841 void CDirDoc::MoveToLastFile(IMergeDoc* pMergeDoc)
842 {
843         if (m_pDirView == nullptr)
844                 return;
845         if (AfxMessageBox(_("Do you want to move to the last file?").c_str(), MB_YESNO | MB_DONT_ASK_AGAIN) == IDYES)
846         {
847                 pMergeDoc->CloseNow();
848                 m_pDirView->OpenLastFile();
849                 GetMainFrame()->OnUpdateFrameTitle(FALSE);
850         }
851 }
852
853 bool CDirDoc::IsFirstFile()
854 {
855         if (m_pDirView == nullptr)
856                 return true;
857         return m_pDirView->IsFirstFile();
858 }
859
860 bool CDirDoc::IsLastFile()
861 {
862         if (m_pDirView == nullptr)
863                 return true;
864         return m_pDirView->IsLastFile();
865 }
866
867 bool CDirDoc::CompareFilesIfFilesAreLarge(int nFiles, const FileLocation ifileloc[])
868 {
869         DIFFITEM di;
870         bool bLargeFile = false;
871         for (int i = 0; i < nFiles; ++i)
872         {
873                 di.diffFileInfo[i].SetFile(paths::FindFileName(ifileloc[i].filepath));
874                 if (di.diffFileInfo[i].Update(ifileloc[i].filepath))
875                         di.diffcode.setSideFlag(i);
876         }
877         if (nFiles == 3)
878                 di.diffcode.diffcode |= DIFFCODE::THREEWAY;
879
880         size_t fileSizeThreshold = GetOptionsMgr()->GetInt(OPT_FILE_SIZE_THRESHOLD);
881         for (int i = 0; i < nFiles; ++i)
882         {
883                 if (di.diffFileInfo[i].size != DirItem::FILE_SIZE_NONE && di.diffFileInfo[i].size > fileSizeThreshold)
884                         bLargeFile = true;
885         }
886         if (!bLargeFile)
887                 return false;
888
889         PathContext paths;
890         for (int i = 0; i < nFiles; ++i)
891                 paths.SetPath(i, ifileloc[i].filepath.empty() ? _T("NUL") : paths::GetParentPath(ifileloc[i].filepath));
892         CDiffContext ctxt(paths, CMP_QUICK_CONTENT);
893         DirViewColItems ci(nFiles);
894         String msg = LoadResString(IDS_COMPARE_LARGE_FILES);
895         if (nFiles < 3)
896         {
897                 String sidestr[] = { _("Left:"), _("Right:") };
898                 for (int i = 0; i < nFiles; ++i)
899                 {
900                         if (ifileloc[i].filepath.empty())
901                         {
902                                 msg += strutils::format(_T("%s %s\n\n"), sidestr[i], _("None"));
903                         }
904                         else
905                         {
906                                 msg += strutils::format(_T("%s %s\n %s:  %s\n %s: %s (%s)\n\n"),
907                                         sidestr[i].c_str(), ifileloc[i].filepath.c_str(),
908                                         ci.GetColDisplayName(3 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 3 + i, di).c_str(),
909                                         ci.GetColDisplayName(8 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 8 + i, di).c_str(), ci.ColGetTextToDisplay(&ctxt, 10 + i, di).c_str());
910                         }
911                 }
912         }
913         else
914         {
915                 String sidestr[] = { _("Left:"), _("Middle:"), _("Right:") };
916                 for (int i = 0; i < nFiles; ++i)
917                 {
918                         if (ifileloc[i].filepath.empty())
919                         {
920                                 msg += strutils::format(_T("%s %s\n\n"), sidestr[i], _("None"));
921                         }
922                         else
923                         {
924                                 msg += strutils::format(_T("%s %s\n %s:  %s\n %s: %s (%s)\n\n"),
925                                         sidestr[i].c_str(), ifileloc[i].filepath.c_str(),
926                                         ci.GetColDisplayName(3 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 3 + i, di).c_str(),
927                                         ci.GetColDisplayName(10 + i).c_str(), ci.ColGetTextToDisplay(&ctxt, 10 + i, di).c_str(), ci.ColGetTextToDisplay(&ctxt, 13 + i, di).c_str());
928                         }
929                 }
930         }
931         CMessageBoxDialog dlg(
932                 m_pDirView ? m_pDirView->GetParentFrame() : nullptr,
933                 msg.c_str(), _T(""),
934                 MB_YESNOCANCEL | MB_ICONQUESTION | MB_DONT_ASK_AGAIN, 0U,
935                 _T("CompareLargeFiles"));
936         INT_PTR ans = dlg.DoModal();
937         if (ans == IDCANCEL)
938                 return true;
939         else if (ans == IDNO)
940                 return false;
941
942         InitDiffContext(&ctxt);
943         FolderCmp cmp(&ctxt);
944         CWaitCursor waitstatus;
945         di.diffcode.diffcode |= cmp.prepAndCompareFiles(di);
946         AfxMessageBox(ci.ColGetTextToDisplay(&ctxt, 2, di).c_str(), MB_OK | MB_ICONINFORMATION);
947         theApp.SetLastCompareResult(di.diffcode.isResultDiff() ? 1 : 0);
948         return true;
949 }
950