OSDN Git Service

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