OSDN Git Service

DirActions.cpp: Fix Left/Right Date and Left/Right Size columns not updating when...
[winmerge-jp/winmerge-jp.git] / Src / DirActions.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  *  @file DirActions.cpp
4  *
5  *  @brief Implementation of methods of CDirView that copy/move/delete files
6  */
7
8 // It would be nice to make this independent of the UI (CDirView)
9 // but it needs access to the list of selected items.
10 // One idea would be to provide an iterator over them.
11 //
12
13 #include "pch.h"
14 #include "DirActions.h"
15 #include "MergeApp.h"
16 #include "UnicodeString.h"
17 #include "7zCommon.h"
18 #include "ShellFileOperations.h"
19 #include "DiffItem.h"
20 #include "FileActionScript.h"
21 #include "locality.h"
22 #include "FileFilterHelper.h"
23 #include "DebugNew.h"
24
25 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, int count,
26                 const String& src, const String& dest, bool destIsSide);
27 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, int count,
28                 const String& src, const String& dest, bool destIsSide);
29 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
30                 int origin, int destination, size_t count,
31                 const String& src, const String& dest, bool destIsSide);
32
33 ContentsChangedException::ContentsChangedException(const String& failpath)
34         : m_msg(strutils::format_string1(
35                 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
36                 failpath))
37 {
38 }
39
40 FileOperationException::FileOperationException(const String& msg)
41         : m_msg(msg)
42 {
43 }
44
45 /**
46  * @brief Ask user a confirmation for copying item(s).
47  * Shows a confirmation dialog for copy operation. Depending ont item count
48  * dialog shows full paths to items (single item) or base paths of compare
49  * (multiple items).
50  * @param [in] origin Origin side of the item(s).
51  * @param [in] destination Destination side of the item(s).
52  * @param [in] count Number of items.
53  * @param [in] src Source path.
54  * @param [in] dest Destination path.
55  * @param [in] destIsSide Is destination path either of compare sides?
56  * @return true if copy should proceed, false if aborted.
57  */
58 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, size_t count,
59                 const String& src, const String& dest, bool destIsSide)
60 {
61         String caption = _("Confirm Copy");
62         String strQuestion = count == 1 ? _("Are you sure you want to copy?") : 
63                 strutils::format(_("Are you sure you want to copy %d items?").c_str(), count);
64
65         ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
66                 destination, count,     src, dest, destIsSide);
67 }
68
69 /**
70  * @brief Ask user a confirmation for moving item(s).
71  * Shows a confirmation dialog for move operation. Depending ont item count
72  * dialog shows full paths to items (single item) or base paths of compare
73  * (multiple items).
74  * @param [in] origin Origin side of the item(s).
75  * @param [in] destination Destination side of the item(s).
76  * @param [in] count Number of items.
77  * @param [in] src Source path.
78  * @param [in] dest Destination path.
79  * @param [in] destIsSide Is destination path either of compare sides?
80  * @return true if copy should proceed, false if aborted.
81  */
82 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, size_t count,
83                 const String& src, const String& dest, bool destIsSide)
84 {
85         String caption = _("Confirm Move");
86         String strQuestion = count == 1 ? _("Are you sure you want to move?") : 
87                 strutils::format(_("Are you sure you want to move %d items?").c_str(), count);
88
89         ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
90                 destination, count,     src, dest, destIsSide);
91 }
92
93 /**
94  * @brief Show a (copy/move) confirmation dialog.
95  * @param [in] caption Caption of the dialog.
96  * @param [in] question Guestion to ask from user.
97  * @param [in] origin Origin side of the item(s).
98  * @param [in] destination Destination side of the item(s).
99  * @param [in] count Number of items.
100  * @param [in] src Source path.
101  * @param [in] dest Destination path.
102  * @param [in] destIsSide Is destination path either of compare sides?
103  * @return true if copy should proceed, false if aborted.
104  */
105 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
106                 int origin, int destination, size_t count,
107                 const String& src, const String& dest, bool destIsSide)
108 {
109         ConfirmationNeededException exp;
110         String sOrig;
111         String sDest;
112         
113         exp.m_caption = caption;
114         
115         if (origin == 0)
116                 sOrig = _("From left:");
117         else if (origin == ctxt.GetCompareDirs() - 1)
118                 sOrig = _("From right:");
119         else
120                 sOrig = _("From middle:");
121
122         if (destIsSide)
123         {
124                 // Copy to left / right
125                 if (destination == 0)
126                         sDest = _("To left:");
127                 else if (destination == ctxt.GetCompareDirs() - 1)
128                         sDest = _("To right:");
129                 else
130                         sDest = _("To middle:");
131         }
132         else
133         {
134                 // Copy left/right to..
135                 sDest = _("To:");
136         }
137
138         String strSrc(src);
139         if (paths::DoesPathExist(src) == paths::IS_EXISTING_DIR)
140                 strSrc = paths::AddTrailingSlash(src);
141         String strDest(dest);
142         if (paths::DoesPathExist(dest) == paths::IS_EXISTING_DIR)
143                 strDest = paths::AddTrailingSlash(dest);
144
145         exp.m_question = question;
146         exp.m_fromText = std::move(sOrig);
147         exp.m_toText = std::move(sDest);
148         exp.m_fromPath = std::move(strSrc);
149         exp.m_toPath = std::move(strDest);
150
151         throw exp;
152 }
153
154 /**
155  * @brief Confirm actions with user as appropriate
156  * (type, whether single or multiple).
157  */
158 void ConfirmActionList(const CDiffContext& ctxt, const FileActionScript & actionList)
159 {
160         // TODO: We need better confirmation for file actions.
161         // Maybe we should show a list of files with actions done..
162         FileActionItem item = actionList.GetHeadActionItem();
163
164         bool bDestIsSide = true;
165
166         // special handling for the single item case, because it is probably the most common,
167         // and we can give the user exact details easily for it
168         switch(item.atype)
169         {
170         case FileAction::ACT_COPY:
171                 if (item.UIResult == FileActionItem::UI_DONT_CARE)
172                         bDestIsSide = false;
173
174                 if (actionList.GetActionItemCount() == 1)
175                 {
176                         ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
177                                 actionList.GetActionItemCount(), item.src, item.dest,
178                                 bDestIsSide);
179                 }
180                 else
181                 {
182                         String src = ctxt.GetPath(item.UIOrigin);
183                         String dst;
184
185                         if (bDestIsSide)
186                         {
187                                 dst = ctxt.GetPath(item.UIDestination);
188                         }
189                         else
190                         {
191                                 if (!actionList.m_destBase.empty())
192                                         dst = actionList.m_destBase;
193                                 else
194                                         dst = item.dest;
195                         }
196
197                         ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
198                                 actionList.GetActionItemCount(), src, dst, bDestIsSide);
199                 }
200                 break;
201                 
202         case FileAction::ACT_DEL:
203                 break;
204
205         case FileAction::ACT_MOVE:
206                 bDestIsSide = false;
207                 if (actionList.GetActionItemCount() == 1)
208                 {
209                         ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
210                                 actionList.GetActionItemCount(), item.src, item.dest,
211                                 bDestIsSide);
212                 }
213                 else
214                 {
215                         String src = ctxt.GetPath(item.UIOrigin);;
216                         String dst;
217
218                         if (!actionList.m_destBase.empty())
219                                 dst = actionList.m_destBase;
220                         else
221                                 dst = item.dest;
222
223                         ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
224                                 actionList.GetActionItemCount(), src, dst, bDestIsSide);
225                 }
226                 break;
227
228         // Invalid operation
229         default: 
230                 LogErrorString(_T("Unknown fileoperation in CDirView::ConfirmActionList()"));
231                 throw "Unknown fileoperation in ConfirmActionList()";
232                 break;
233         }
234 }
235
236 /**
237  * @brief Update results for FileActionItem.
238  * This functions is called to update DIFFITEM after FileActionItem.
239  * @param [in] act Action that was done.
240  * @param [in] ctxt Compare context: contains difflist, encoding info etc.
241  * @param [in,out] di Item to update the results.
242  */
243 UPDATEITEM_TYPE UpdateDiffAfterOperation(const FileActionItem & act, CDiffContext& ctxt, DIFFITEM &di)
244 {
245         bool bUpdateSrc  = false;
246         bool bUpdateDest = false;
247         bool bRemoveItem = false;
248
249         // Use FileActionItem types for simplicity for now.
250         // Better would be to use FileAction contained, since it is not
251         // UI dependent.
252         switch (act.UIResult)
253         {
254         case FileActionItem::UI_SYNC:
255                 bUpdateSrc = true;
256                 bUpdateDest = true;
257                 CopyDiffSideAndProperties(di, act.UIOrigin, act.UIDestination);
258                 if (ctxt.GetCompareDirs() > 2)
259                         SetDiffCompare(di, DIFFCODE::NOCMP);
260                 else
261                         UpdateCompareFlagsAfterSync(di, ctxt.m_bRecursive);
262                 SetDiffCounts(di, 0, 0);
263                 break;
264
265         case FileActionItem::UI_DEL:
266                 if (di.diffcode.isSideOnly(act.UIOrigin))
267                 {
268                         bRemoveItem = true;
269                 }
270                 else
271                 {
272                         UnsetDiffSide(di, act.UIOrigin);
273                         SetDiffCompare(di, DIFFCODE::NOCMP);
274                         bUpdateSrc = true;
275                 }
276                 break;
277         }
278
279         if (bRemoveItem)
280                 return UPDATEITEM_REMOVE;
281         if (bUpdateSrc | bUpdateDest)
282                 return UPDATEITEM_UPDATE;
283         return UPDATEITEM_NONE;
284 }
285
286 /**
287  * @brief Find the CDiffContext diffpos of an item from its left & right paths
288  * @return POSITION to item, `nullptr` if not found.
289  * @note Filenames must be same, if they differ `nullptr` is returned.
290  */
291 DIFFITEM *FindItemFromPaths(const CDiffContext& ctxt, const PathContext& paths)
292 {
293         int nBuffer;
294         String file[3], path[3], base;
295         for (nBuffer = 0; nBuffer < paths.GetSize(); ++nBuffer)
296         {
297                 String p = paths[nBuffer];
298                 file[nBuffer] = paths::FindFileName(p);
299                 if (file[nBuffer].empty())
300                         return 0;
301                 // Path can contain (because of difftools?) '/' and '\'
302                 // so for comparing purposes, convert whole path to use '\\'
303                 path[nBuffer] = paths::ToWindowsPath(String(p, 0, p.length() - file[nBuffer].length())); // include trailing backslash
304                 base = ctxt.GetPath(nBuffer); // include trailing backslash
305                 if (path[nBuffer].compare(0, base.length(), base.c_str()) != 0)
306                         return 0;
307                 path[nBuffer].erase(0, base.length()); // turn into relative path
308                 if (String::size_type length = path[nBuffer].length())
309                         path[nBuffer].resize(length - 1); // remove trailing backslash
310         }
311
312         // Filenames must be identical
313         if (std::any_of(file, file + paths.GetSize(), [&](auto& it) { return strutils::compare_nocase(it, file[0]) != 0; }))
314                 return 0;
315
316         DIFFITEM *pos = ctxt.GetFirstDiffPosition();
317         if (paths.GetSize() == 2)
318         {
319                 while (DIFFITEM *currentPos = pos) // Save our current pos before getting next
320                 {
321                         const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
322                         if (di.diffFileInfo[0].path == path[0] &&
323                                 di.diffFileInfo[1].path == path[1] &&
324                                 di.diffFileInfo[0].filename == file[0] &&
325                                 di.diffFileInfo[1].filename == file[1])
326                         {
327                                 return currentPos;
328                         }
329                 }
330         }
331         else
332         {
333                 while (DIFFITEM *currentPos = pos) // Save our current pos before getting next
334                 {
335                         const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
336                         if (di.diffFileInfo[0].path == path[0] &&
337                                 di.diffFileInfo[1].path == path[1] &&
338                                 di.diffFileInfo[2].path == path[2] &&
339                                 di.diffFileInfo[0].filename == file[0] &&
340                                 di.diffFileInfo[1].filename == file[1] &&
341                                 di.diffFileInfo[2].filename == file[2])
342                         {
343                                 return currentPos;
344                         }
345                 }
346         }
347         return 0;
348 }
349
350 /// is it possible to copy item to left ?
351 bool IsItemCopyable(const DIFFITEM &di, int index)
352 {
353         // don't let them mess with error items
354         if (di.diffcode.isResultError()) return false;
355         // can't copy same items
356         if (di.diffcode.isResultSame()) return false;
357         // impossible if not existing
358         if (!di.diffcode.exists(index)) return false;
359         // everything else can be copied to other side
360         return true;
361 }
362
363 /// is it possible to delete item ?
364 bool IsItemDeletable(const DIFFITEM &di, int index)
365 {
366         // don't let them mess with error items
367         if (di.diffcode.isResultError()) return false;
368         // impossible if not existing
369         if (!di.diffcode.exists(index)) return false;
370         // everything else can be deleted
371         return true;
372 }
373
374 /// is it possible to delete both items ?
375 bool IsItemDeletableOnBoth(const CDiffContext& ctxt, const DIFFITEM &di)
376 {
377         // don't let them mess with error items
378         if (di.diffcode.isResultError()) return false;
379         // impossible if only on right or left
380         for (int i = 0; i < ctxt.GetCompareDirs(); ++i)
381                 if (!di.diffcode.exists(i)) return false;
382
383         // everything else can be deleted on both
384         return true;
385 }
386
387 /// is it possible to compare these two items?
388 bool AreItemsOpenable(const CDiffContext& ctxt, SELECTIONTYPE selectionType, const DIFFITEM &di1, const DIFFITEM &di2, bool openableForDir /*= true*/)
389 {
390         String sLeftBasePath = ctxt.GetPath(0);
391         String sRightBasePath = ctxt.GetPath(1);
392
393         // Must be both directory or neither
394         if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory()) return false;
395
396         if (!openableForDir && di1.diffcode.isDirectory()) return false;
397
398         switch (selectionType)
399         {
400         case SELECTIONTYPE_NORMAL:
401                 // Must be on different sides, or one on one side & one on both
402                 if (di1.diffcode.isSideFirstOnly() && (di2.diffcode.isSideSecondOnly() ||
403                         di2.diffcode.isSideBoth()))
404                         return true;
405                 if (di1.diffcode.isSideSecondOnly() && (di2.diffcode.isSideFirstOnly() ||
406                         di2.diffcode.isSideBoth()))
407                         return true;
408                 if (di1.diffcode.isSideBoth() && (di2.diffcode.isSideFirstOnly() ||
409                         di2.diffcode.isSideSecondOnly()))
410                         return true;
411                 break;
412         case SELECTIONTYPE_LEFT1LEFT2:
413                 if (di1.diffcode.exists(0) && di2.diffcode.exists(0))
414                         return true;
415                 break;
416         case SELECTIONTYPE_RIGHT1RIGHT2:
417                 if (di1.diffcode.exists(1) && di2.diffcode.exists(1))
418                         return true;
419                 break;
420         case SELECTIONTYPE_LEFT1RIGHT2:
421                 if (di1.diffcode.exists(0) && di2.diffcode.exists(1))
422                         return true;
423                 break;
424         case SELECTIONTYPE_LEFT2RIGHT1:
425                 if (di1.diffcode.exists(1) && di2.diffcode.exists(0))
426                         return true;
427                 break;
428         }
429
430         // Allow to compare items if left & right path refer to same directory
431         // (which means there is effectively two files involved). No need to check
432         // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
433         if (strutils::compare_nocase(sLeftBasePath, sRightBasePath) == 0)
434                 return true;
435
436         return false;
437 }
438 /// is it possible to compare these three items?
439 bool AreItemsOpenable(const CDiffContext& ctxt, const DIFFITEM &di1, const DIFFITEM &di2, const DIFFITEM &di3, bool openableForDir /*= true*/)
440 {
441         if (ctxt.GetCompareDirs() < 3)
442                 return false;
443
444         String sLeftBasePath = ctxt.GetPath(0);
445         String sMiddleBasePath = ctxt.GetPath(1);
446         String sRightBasePath = ctxt.GetPath(2);
447
448         // Must be both directory or neither
449         if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory() || di1.diffcode.isDirectory() != di3.diffcode.isDirectory()) return false;
450
451         if (!openableForDir && di1.diffcode.isDirectory()) return false;
452
453         // Must be on different sides, or one on one side & one on both
454         if (di1.diffcode.isSideFirstOnly() && di2.diffcode.isSideSecondOnly() && di3.diffcode.isSideThirdOnly())
455                 return true;
456         if (di1.diffcode.isSideFirstOnly() && di2.diffcode.isSideThirdOnly() && di3.diffcode.isSideSecondOnly())
457                 return true;
458         if (di1.diffcode.isSideSecondOnly() && di2.diffcode.isSideFirstOnly() && di3.diffcode.isSideThirdOnly())
459                 return true;
460         if (di1.diffcode.isSideSecondOnly() && di2.diffcode.isSideThirdOnly() && di3.diffcode.isSideFirstOnly())
461                 return true;
462         if (di1.diffcode.isSideThirdOnly() && di2.diffcode.isSideFirstOnly() && di3.diffcode.isSideSecondOnly())
463                 return true;
464         if (di1.diffcode.isSideThirdOnly() && di2.diffcode.isSideSecondOnly() && di3.diffcode.isSideFirstOnly())
465                 return true;
466
467         // Allow to compare items if left & right path refer to same directory
468         // (which means there is effectively two files involved). No need to check
469         // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
470         if (strutils::compare_nocase(sLeftBasePath, sMiddleBasePath) == 0 && strutils::compare_nocase(sLeftBasePath, sRightBasePath) == 0)
471                 return true;
472
473         return false;
474 }
475
476 /// is it possible to open item ?
477 bool IsItemOpenableOn(const DIFFITEM &di, int index)
478 {
479         // impossible if not existing
480         if (!di.diffcode.exists(index)) return false;
481
482         // everything else can be opened on right
483         return true;
484 }
485
486 /// is it possible to open left ... item ?
487 bool IsItemOpenableOnWith(const DIFFITEM &di, int index)
488 {
489         return (!di.diffcode.isDirectory() && IsItemOpenableOn(di, index));
490 }
491 /// is it possible to copy to... left item?
492 bool IsItemCopyableToOn(const DIFFITEM &di, int index)
493 {
494         // impossible if only on right
495         if (!di.diffcode.exists(index)) return false;
496
497         // everything else can be copied to from left
498         return true;
499 }
500
501 // When navigating differences, do we stop at this one ?
502 bool IsItemNavigableDiff(const CDiffContext& ctxt, const DIFFITEM &di)
503 {
504         // Not a valid diffitem, one of special items (e.g "..")
505         if (di.diffcode.diffcode == 0)
506                 return false;
507         if (di.diffcode.isResultFiltered() || di.diffcode.isResultError())
508                 return false;
509         if (!di.diffcode.isResultDiff() && IsItemExistAll(ctxt, di))
510                 return false;
511         return true;
512 }
513
514 bool IsItemExistAll(const CDiffContext& ctxt, const DIFFITEM &di)
515 {
516         // Not a valid diffitem, one of special items (e.g "..")
517         if (di.diffcode.diffcode == 0)
518                 return false;
519         return di.diffcode.existAll();
520 }
521
522
523 /**
524  * @brief Determines if the user wants to see given item.
525  * This function determines what items to show and what items to hide. There
526  * are lots of combinations, but basically we check if menuitem is enabled or
527  * disabled and show/hide matching items. For non-recursive compare we never
528  * hide folders as that would disable user browsing into them. And we even
529  * don't really know if folders are identical or different as we haven't
530  * compared them.
531  * @param [in] di Item to check.
532  * @return true if item should be shown, false if not.
533  * @sa CDirDoc::Redisplay()
534  */
535 bool IsShowable(const CDiffContext& ctxt, const DIFFITEM &di, const DirViewFilterSettings& filter)
536 {
537         if (di.customFlags & ViewCustomFlags::HIDDEN)
538                 return false;
539
540         if (di.diffcode.isResultFiltered())
541         {
542                 // Treat SKIPPED as a 'super'-flag. If item is skipped and user
543                 // wants to see skipped items show item regardless of other flags
544                 return filter.show_skipped;
545         }
546
547         if (di.diffcode.isDirectory())
548         {
549                 // Subfolders in non-recursive compare can only be skipped or unique
550                 if (!ctxt.m_bRecursive)
551                 {
552                         // left/right filters
553                         if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
554                                 return false;
555                         if (ctxt.GetCompareDirs() < 3)
556                         {
557                                 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
558                                         return false;
559                         }
560                         else
561                         {
562                                 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
563                                         return false;
564                                 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
565                                         return false;
566                                 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
567                                         return false;
568                                 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
569                                         return false;
570                                 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
571                                         return false;
572                         }
573
574                         // result filters
575                         if (di.diffcode.isResultError() && false/* !GetMainFrame()->m_bShowErrors FIXME:*/)
576                                 return false;
577                 }
578                 else // recursive mode (including tree-mode)
579                 {
580                         // left/right filters
581                         if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
582                                 return false;
583                         if (ctxt.GetCompareDirs() < 3)
584                         {
585                                 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
586                                         return false;
587                         }
588                         else
589                         {
590                                 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
591                                         return false;
592                                 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
593                                         return false;
594                                 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
595                                         return false;
596                                 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
597                                         return false;
598                                 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
599                                         return false;
600                         }
601
602                         // ONLY filter folders by result (identical/different) for tree-view.
603                         // In the tree-view we show subfolders with identical/different
604                         // status. The flat view only shows files inside folders. So if we
605                         // filter by status the files inside folder are filtered too and
606                         // users see files appearing/disappearing without clear logic.          
607                         if (filter.tree_mode)
608                         {
609                                 // result filters
610                                 if (di.diffcode.isResultError() && false/* !GetMainFrame()->m_bShowErrors FIXME:*/)
611                                         return false;
612
613                                 // result filters
614                                 if (di.diffcode.isResultSame() && !filter.show_identical)
615                                         return false;
616                                 bool bShowable = true;
617                                 if (ctxt.GetCompareDirs() < 3)
618                                 {
619                                         if (di.diffcode.isResultDiff() && !filter.show_different)
620                                                 bShowable = false;
621                                 }
622                                 else
623                                 {
624                                         if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF1STONLY)
625                                         {
626                                                 if (!filter.show_different_left_only)
627                                                         bShowable = false;
628                                         }
629                                         else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF2NDONLY)
630                                         {
631                                                 if (!filter.show_different_middle_only)
632                                                         bShowable = false;
633                                         }
634                                         else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF3RDONLY)
635                                         {
636                                                 if (!filter.show_different_right_only)
637                                                         bShowable = false;
638                                         }
639                                         else if (di.diffcode.isResultDiff() && !filter.show_different)
640                                                 bShowable = false;
641                                 }
642                                 if (!bShowable)
643                                 {
644                                         DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(&di);
645                                         while (diffpos != nullptr)
646                                         {
647                                                 const DIFFITEM &dic = ctxt.GetNextSiblingDiffPosition(diffpos);
648                                                 if (IsShowable(ctxt, dic, filter))
649                                                         return true;
650                                         }
651                                         return false;
652                                 }
653                         }
654                 }
655         }
656         else
657         {
658                 // left/right filters
659                 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
660                         return false;
661                 if (ctxt.GetCompareDirs() < 3)
662                 {
663                         if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
664                                 return false;
665                 }
666                 else
667                 {
668                         if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
669                                 return false;
670                         if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
671                                 return false;
672                         if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
673                                 return false;
674                         if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
675                                 return false;
676                         if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
677                                 return false;
678                 }
679
680                 // file type filters
681                 if (di.diffcode.isBin() && !filter.show_binaries)
682                         return false;
683
684                 // result filters
685                 if (di.diffcode.isResultSame() && !filter.show_identical)
686                         return false;
687                 if (di.diffcode.isResultError() && false/* && !GetMainFrame()->m_bShowErrors FIXME:*/)
688                         return false;
689                 if (ctxt.GetCompareDirs() < 3)
690                 {
691                         if (di.diffcode.isResultDiff() && !filter.show_different)
692                                 return false;
693                 }
694                 else
695                 {
696                         if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF1STONLY)
697                         {
698                                 if (!filter.show_different_left_only)
699                                         return false;
700                         }
701                         else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF2NDONLY)
702                         {
703                                 if (!filter.show_different_middle_only)
704                                         return false;
705                         }
706                         else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF3RDONLY)
707                         {
708                                 if (!filter.show_different_right_only)
709                                         return false;
710                         }
711                         else if (di.diffcode.isResultDiff() && !filter.show_different)
712                                 return false;
713                 }
714         }
715         return true;
716 }
717
718 /**
719  * @brief Open one selected item.
720  * @param [in] pos1 Item position.
721  * @param [in,out] di1 Pointer to first diffitem.
722  * @param [in,out] di2 Pointer to second diffitem.
723  * @param [in,out] di3 Pointer to third diffitem.
724  * @param [out] paths First/Second/Third paths.
725  * @param [out] sel1 Item's selection index in listview.
726  * @param [in,out] isDir Is item folder?
727  * @param [in] openableForDir Are items openable if the items are directories?
728  * return false if there was error or item was completely processed.
729  */
730 bool GetOpenOneItem(const CDiffContext& ctxt, DIFFITEM *pos1, const DIFFITEM *pdi[3],
731                 PathContext & paths, int & sel1, bool & isdir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
732 {
733         pdi[0] = &ctxt.GetDiffAt(pos1);
734         pdi[1] = pdi[0];
735         pdi[2] = pdi[0];
736
737         if (!openableForDir && pdi[0]->diffcode.isDirectory()) return false;
738
739         paths = GetItemFileNames(ctxt, *pdi[0]);
740         encoding[0] = pdi[0]->diffFileInfo[0].encoding;
741         encoding[1] = pdi[0]->diffFileInfo[1].encoding;
742         encoding[2] = pdi[0]->diffFileInfo[2].encoding;
743
744         for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
745                 nPane[nIndex] = nIndex;
746
747         if (pdi[0]->diffcode.isDirectory())
748                 isdir = true;
749
750         if (isdir && (pdi[0]->diffcode.existsFirst() && pdi[1]->diffcode.existsSecond() && pdi[2]->diffcode.existsThird()))
751         {
752                 // Check both folders exist. If either folder is missing that means
753                 // folder has been changed behind our back, so we just tell user to
754                 // refresh the compare.
755                 paths::PATH_EXISTENCE path1Exists = paths::DoesPathExist(paths[0]);
756                 paths::PATH_EXISTENCE path2Exists = paths::DoesPathExist(paths[1]);
757                 if (path1Exists != paths::IS_EXISTING_DIR || path2Exists != paths::IS_EXISTING_DIR)
758                 {
759                         String invalid = path1Exists == paths::IS_EXISTING_DIR ? paths[0] : paths[1];
760                         errmsg = strutils::format_string1(
761                                 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
762                                 invalid);
763                         return false;
764                 }
765         }
766
767         return true;
768 }
769
770 /**
771  * @brief Open two selected items.
772  * @param [in] pos1 First item position.
773  * @param [in] pos2 Second item position.
774  * @param [in,out] di1 Pointer to first diffitem.
775  * @param [in,out] di2 Pointer to second diffitem.
776  * @param [out] paths First/Second/Third paths.
777  * @param [out] sel1 First item's selection index in listview.
778  * @param [out] sel2 Second item's selection index in listview.
779  * @param [in,out] isDir Is item folder?
780  * @param [in] openableForDir Are items openable if the items are directories?
781  * return false if there was error or item was completely processed.
782  */
783 bool GetOpenTwoItems(const CDiffContext& ctxt, SELECTIONTYPE selectionType, DIFFITEM *pos1, DIFFITEM *pos2, const DIFFITEM *pdi[3],
784                 PathContext & paths, int & sel1, int & sel2, bool & isDir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
785 {
786         // Two items selected, get their info
787         pdi[0] = &ctxt.GetDiffAt(pos1);
788         pdi[1] = &ctxt.GetDiffAt(pos2);
789         nPane[0] = 0;
790         nPane[1] = 1;
791
792         // Check for binary & side compatibility & file/dir compatibility
793         if (!AreItemsOpenable(ctxt, selectionType, *pdi[0], *pdi[1], openableForDir))
794         {
795                 return false;
796         }
797
798         switch (selectionType)
799         {
800         case SELECTIONTYPE_NORMAL:
801                 // Ensure that di1 is on left (swap if needed)
802                 if (pdi[0]->diffcode.isSideSecondOnly() || (pdi[0]->diffcode.isSideBoth() &&
803                                 pdi[1]->diffcode.isSideFirstOnly()))
804                 {
805                         std::swap(pdi[0], pdi[1]);
806                         std::swap(sel1, sel2);
807                 }
808                 break;
809         case SELECTIONTYPE_LEFT1LEFT2:
810                 nPane[0] = nPane[1] = 0;
811                 break;
812         case SELECTIONTYPE_RIGHT1RIGHT2:
813                 nPane[0] = nPane[1] = 1;
814                 break;
815         case SELECTIONTYPE_LEFT1RIGHT2:
816                 break;
817         case SELECTIONTYPE_LEFT2RIGHT1:
818                 std::swap(pdi[0], pdi[1]);
819                 std::swap(sel1, sel2);
820                 break;
821         }
822
823         PathContext files1, files2;
824         files1 = GetItemFileNames(ctxt, *pdi[0]);
825         files2 = GetItemFileNames(ctxt, *pdi[1]);
826         paths.SetLeft(files1[nPane[0]]);
827         paths.SetRight(files2[nPane[1]]);
828         encoding[0] = pdi[0]->diffFileInfo[nPane[0]].encoding;
829         encoding[1] = pdi[1]->diffFileInfo[nPane[1]].encoding;
830
831         if (pdi[0]->diffcode.isDirectory())
832         {
833                 isDir = true;
834                 if (paths::GetPairComparability(paths) != paths::IS_EXISTING_DIR)
835                 {
836                         errmsg = _("The selected folder is invalid.");
837                         return false;
838                 }
839         }
840
841         return true;
842 }
843
844 /**
845  * @brief Open three selected items.
846  * @param [in] pos1 First item position.
847  * @param [in] pos2 Second item position.
848  * @param [in] pos3 Third item position.
849  * @param [in,out] di1 Pointer to first diffitem.
850  * @param [in,out] di2 Pointer to second diffitem.
851  * @param [in,out] di3 Pointer to third diffitem.
852  * @param [out] paths First/Second/Third paths.
853  * @param [out] sel1 First item's selection index in listview.
854  * @param [out] sel2 Second item's selection index in listview.
855  * @param [out] sel3 Third item's selection index in listview.
856  * @param [in,out] isDir Is item folder?
857  * @param [in] openableForDir Are items openable if the items are directories?
858  * return false if there was error or item was completely processed.
859  */
860 bool GetOpenThreeItems(const CDiffContext& ctxt, DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3, const DIFFITEM *pdi[3],
861         PathContext & paths, int & sel1, int & sel2, int & sel3, bool & isDir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
862 {
863         assert(pos1 && pos2 && pos3 && ctxt.GetCompareDirs() > 2);
864
865         String pathLeft, pathMiddle, pathRight;
866
867         for (int nIndex = 0; nIndex < 3; ++nIndex)
868                 nPane[nIndex] = nIndex;
869
870         // Three items selected, get their info
871         pdi[0] = &ctxt.GetDiffAt(pos1);
872         pdi[1] = &ctxt.GetDiffAt(pos2);
873         pdi[2] = &ctxt.GetDiffAt(pos3);
874
875         // Check for binary & side compatibility & file/dir compatibility
876         if (!::AreItemsOpenable(ctxt, *pdi[0], *pdi[1], *pdi[2], openableForDir))
877         {
878                 return false;
879         }
880         // Ensure that pdi[0] is on left (swap if needed)
881         if (pdi[0]->diffcode.exists(0) && pdi[1]->diffcode.exists(1) && pdi[2]->diffcode.exists(2))
882         {
883         }
884         else if (pdi[0]->diffcode.exists(0) && pdi[1]->diffcode.exists(2) && pdi[2]->diffcode.exists(1))
885         {
886                 std::swap(pdi[1], pdi[2]);
887                 std::swap(nPane[1], nPane[2]);
888                 std::swap(sel2, sel3);
889         }
890         else if (pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(0) && pdi[2]->diffcode.exists(2))
891         {
892                 std::swap(pdi[0], pdi[1]);
893                 std::swap(nPane[0], nPane[1]);
894                 std::swap(sel1, sel2);
895         }
896         else if (pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(2) && pdi[2]->diffcode.exists(0))
897         {
898                 std::swap(pdi[0], pdi[2]);
899                 std::swap(nPane[0], nPane[2]);
900                 std::swap(sel1, sel3);
901                 std::swap(pdi[1], pdi[2]);
902                 std::swap(nPane[1], nPane[2]);
903                 std::swap(sel2, sel3);
904         }
905         else if (pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(0) && pdi[2]->diffcode.exists(1))
906         {
907                 std::swap(pdi[0], pdi[1]);
908                 std::swap(nPane[0], nPane[1]);
909                 std::swap(sel1, sel2);
910                 std::swap(pdi[1], pdi[2]);
911                 std::swap(nPane[1], nPane[2]);
912                 std::swap(sel2, sel3);
913         }
914         else if (pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(1) && pdi[2]->diffcode.exists(0))
915         {
916                 std::swap(pdi[0], pdi[2]);
917                 std::swap(nPane[0], nPane[2]);
918                 std::swap(sel1, sel3);
919         }
920
921         // Fill in pathLeft & & pathMiddle & pathRight
922         PathContext pathsTemp = GetItemFileNames(ctxt, *pdi[0]);
923         pathLeft = pathsTemp[0];
924         pathsTemp = GetItemFileNames(ctxt, *pdi[1]);
925         pathMiddle = pathsTemp[1];
926         pathsTemp = GetItemFileNames(ctxt, *pdi[2]);
927         pathRight = pathsTemp[2];
928
929         paths.SetLeft(pathLeft);
930         paths.SetMiddle(pathMiddle);
931         paths.SetRight(pathRight);
932
933         encoding[0] = pdi[0]->diffFileInfo[0].encoding;
934         encoding[1] = pdi[1]->diffFileInfo[1].encoding;
935         encoding[2] = pdi[2]->diffFileInfo[2].encoding;
936
937         if (pdi[0]->diffcode.isDirectory())
938         {
939                 isDir = true;
940                 if (paths::GetPairComparability(paths) != paths::IS_EXISTING_DIR)
941                 {
942                         errmsg = _("The selected folder is invalid.");
943                         return false;
944                 } 
945         }
946
947         return true;
948 }
949
950 /**
951  * @brief Get the file names on both sides for specified item.
952  * @note Return empty strings if item is special item.
953  */
954 void GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM &di, String& strLeft, String& strRight)
955 {
956         const String leftrelpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
957         const String rightrelpath = paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename);
958         const String & leftpath = ctxt.GetPath(0);
959         const String & rightpath = ctxt.GetPath(1);
960         strLeft = paths::ConcatPath(leftpath, leftrelpath);
961         strRight = paths::ConcatPath(rightpath, rightrelpath);
962 }
963
964 String GetItemFileName(const CDiffContext& ctxt, const DIFFITEM &di, int index)
965 {
966         return paths::ConcatPath(ctxt.GetPath(index), paths::ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename));
967 }
968
969 PathContext GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM &di)
970 {
971         PathContext paths;
972         for (int nIndex = 0; nIndex < ctxt.GetCompareDirs(); nIndex++)
973         {
974                 const String relpath = paths::ConcatPath(di.diffFileInfo[nIndex].path, di.diffFileInfo[nIndex].filename);
975                 const String & path = ctxt.GetPath(nIndex);
976                 paths.SetPath(nIndex, paths::ConcatPath(path, relpath));
977         }
978         return paths;
979 }
980
981 /**
982  * @brief Return image index appropriate for this row
983  */
984 int GetColImage(const DIFFITEM &di)
985 {
986         // Must return an image index into image list created above in OnInitDialog
987         if (di.diffcode.isResultError())
988                 return DIFFIMG_ERROR;
989         if (di.diffcode.isResultAbort())
990                 return DIFFIMG_ABORT;
991         if (di.diffcode.isResultFiltered())
992                 return (di.diffcode.isDirectory() ? DIFFIMG_DIRSKIP : DIFFIMG_SKIP);
993         if (di.diffcode.isSideFirstOnly())
994                 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
995         if (di.diffcode.isSideSecondOnly())
996                 return ((di.diffcode.diffcode & DIFFCODE::THREEWAY) == 0 ? 
997                         (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
998                         (di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
999         if (di.diffcode.isSideThirdOnly())
1000                 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
1001         if ((di.diffcode.diffcode & DIFFCODE::THREEWAY) != 0)
1002         {
1003                 if (!di.diffcode.exists(0))
1004                         return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
1005                 if (!di.diffcode.exists(1))
1006                         return (di.diffcode.isDirectory() ? DIFFIMG_MDIRMISSING : DIFFIMG_MMISSING);
1007                 if (!di.diffcode.exists(2))
1008                         return (di.diffcode.isDirectory() ? DIFFIMG_RDIRMISSING : DIFFIMG_RMISSING);
1009         }
1010         if (di.diffcode.isResultSame())
1011         {
1012                 if (di.diffcode.isDirectory())
1013                         return DIFFIMG_DIRSAME;
1014                 else
1015                 {
1016                         if (di.diffcode.isText())
1017                                 return DIFFIMG_TEXTSAME;
1018                         else if (di.diffcode.isBin())
1019                                 return DIFFIMG_BINSAME;
1020                         else if (di.diffcode.isImage())
1021                                 return DIFFIMG_IMAGESAME;
1022                         else
1023                                 return DIFFIMG_SAME;
1024                 }
1025         }
1026         // diff
1027         if (di.diffcode.isResultDiff())
1028         {
1029                 if (di.diffcode.isDirectory())
1030                         return DIFFIMG_DIRDIFF;
1031                 else
1032                 {
1033                         if (di.diffcode.isText())
1034                                 return DIFFIMG_TEXTDIFF;
1035                         else if (di.diffcode.isBin())
1036                                 return DIFFIMG_BINDIFF;
1037                         else if (di.diffcode.isImage())
1038                                 return DIFFIMG_IMAGEDIFF;
1039                         else
1040                                 return DIFFIMG_DIFF;
1041                 }
1042         }
1043         return (di.diffcode.isDirectory() ? DIFFIMG_DIR : DIFFIMG_FILE );
1044 }
1045
1046 /**
1047  * @brief Copy side status of diffitem
1048  * @note This does not update UI - ReloadItemStatus() does
1049  * @sa CDirDoc::ReloadItemStatus()
1050  */
1051 void CopyDiffSideAndProperties(DIFFITEM& di, int src, int dst)
1052 {
1053         if (di.diffcode.exists(src))
1054         {
1055                 di.diffcode.diffcode |= (DIFFCODE::FIRST << dst);
1056                 // copy file properties other than ctime 
1057                 di.diffFileInfo[dst].encoding = di.diffFileInfo[src].encoding;
1058                 di.diffFileInfo[dst].m_textStats = di.diffFileInfo[src].m_textStats;
1059                 di.diffFileInfo[dst].version = di.diffFileInfo[src].version;
1060                 di.diffFileInfo[dst].size = di.diffFileInfo[src].size;
1061                 di.diffFileInfo[dst].mtime = di.diffFileInfo[src].mtime;
1062                 di.diffFileInfo[dst].flags = di.diffFileInfo[src].flags;
1063         }
1064         if (di.HasChildren())
1065         {
1066                 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1067                         CopyDiffSideAndProperties(*pdic, src, dst);
1068         }
1069 }
1070
1071 /**
1072  * @brief Unset side status of diffitem
1073  * @note This does not update UI - ReloadItemStatus() does
1074  * @sa CDirDoc::ReloadItemStatus()
1075  */
1076 void UnsetDiffSide(DIFFITEM& di, int index)
1077 {
1078         di.diffcode.diffcode &= ~(DIFFCODE::FIRST << index);
1079         di.diffFileInfo[index].ClearPartial();
1080         di.nidiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
1081         di.nsdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
1082         if (di.HasChildren())
1083         {
1084                 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1085                         UnsetDiffSide(*pdic, index);
1086         }
1087 }
1088
1089 /**
1090  * @brief Set compare status of diffitem
1091  * @note This does not update UI - ReloadItemStatus() does
1092  * @sa CDirDoc::ReloadItemStatus()
1093  */
1094 void SetDiffCompare(DIFFITEM& di, unsigned diffcode)
1095 {
1096         SetDiffStatus(di, diffcode, DIFFCODE::COMPAREFLAGS);
1097 }
1098
1099 /**
1100  * @brief Set status for diffitem
1101  * @param diffcode New code
1102  * @param mask Defines allowed set of flags to change
1103  * @param idx Item's index to list in UI
1104  */
1105 void SetDiffStatus(DIFFITEM& di, unsigned  diffcode, unsigned mask)
1106 {
1107         // TODO: Why is the update broken into these pieces ?
1108         // Someone could figure out these pieces and probably simplify this.
1109
1110         // Update DIFFITEM code (comparison result)
1111         assert( ((~mask) & diffcode) == 0 ); // make sure they only set flags in their mask
1112
1113         di.diffcode.diffcode &= (~mask); // remove current data
1114         di.diffcode.diffcode |= diffcode; // add new data
1115         if (di.HasChildren())
1116         {
1117                 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1118                         SetDiffStatus(*pdic, diffcode, mask);
1119         }
1120         // update DIFFITEM time (and other disk info), and tell views
1121 }
1122
1123 void UpdateStatusFromDisk(CDiffContext& ctxt, DIFFITEM& di, int index)
1124 {
1125         ctxt.UpdateStatusFromDisk(&di, index);
1126         if (di.HasChildren())
1127         {
1128                 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1129                         UpdateStatusFromDisk(ctxt, *pdic, index);
1130         }
1131 }
1132
1133 /**
1134  * @brief Update compare flags recursively after sync.
1135  * @param [in,out] di Item to update the compare flag.
1136  * @param [in] bRecursive `true` if tree mode is on
1137  * @return number of diff items
1138  */
1139 int UpdateCompareFlagsAfterSync(DIFFITEM& di, bool bRecursive)
1140 {
1141         // Do not update compare flags for filtered items.
1142         if (di.diffcode.isResultFiltered())
1143                 return 0;
1144
1145         int res = 0;
1146         if (di.HasChildren())
1147         {
1148                 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1149                 {
1150                         int ndiff = UpdateCompareFlagsAfterSync(*pdic, bRecursive);
1151                         if (ndiff > 0)
1152                         {
1153                                 res += ndiff;
1154                         }
1155                 }
1156
1157                 // Update compare flags for items that exist on both sides.
1158                 // (Do not update compare flags for items that exist on only one side.)
1159                 if (di.diffcode.existAll())
1160                 {
1161                         di.diffcode.diffcode &= (~DIFFCODE::COMPAREFLAGS);
1162                         unsigned flag = (res > 0) ? DIFFCODE::DIFF : DIFFCODE::SAME;
1163                         di.diffcode.diffcode |= flag;
1164                 }
1165         }
1166         else {
1167                 // Update compare flags for files and directories in tree mode.
1168                 // (Do not update directory compare flags when not in tree mode.)
1169                 if (!di.diffcode.isDirectory() || bRecursive)
1170                 {
1171                         if (di.diffcode.existAll())
1172                         {
1173                                 di.diffcode.diffcode &= (~DIFFCODE::COMPAREFLAGS);
1174                                 di.diffcode.diffcode |= DIFFCODE::SAME;
1175                         }
1176                         else
1177                         {
1178                                 res++;
1179                         }
1180                 }
1181         }
1182
1183         return res;
1184 }
1185
1186 /**
1187  * @brief Update the paths of the diff items recursively.
1188  * @param[in] nDirs Number of directories to compare.
1189  * @param[in,out] di\81@Item to update the path.
1190  */
1191 void UpdatePaths(int nDirs, DIFFITEM& di)
1192 {
1193         assert(nDirs == 2 || nDirs == 3);
1194
1195         if (di.HasChildren())
1196         {
1197                 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1198                 {
1199                         for (int i = 0; i < nDirs; i++)
1200                                 pdic->diffFileInfo[i].path = paths::ConcatPath(di.diffFileInfo[i].path, di.diffFileInfo[i].filename);
1201                         UpdatePaths(nDirs, *pdic);
1202                 }
1203         }
1204 }
1205
1206 void SetDiffCounts(DIFFITEM& di, unsigned diffs, unsigned ignored)
1207 {
1208         di.nidiffs = ignored; // see StoreDiffResult() in DirScan.cpp
1209         di.nsdiffs = diffs;
1210 }
1211
1212 /**
1213  * @brief Set item's view-flag.
1214  * @param [in] key Item fow which flag is set.
1215  * @param [in] flag Flag value to set.
1216  * @param [in] mask Mask for possible flag values.
1217  */
1218 void SetItemViewFlag(DIFFITEM& di, unsigned flag, unsigned mask)
1219 {
1220         unsigned curFlags = di.customFlags;
1221         curFlags &= ~mask; // Zero bits masked
1222         curFlags |= flag;
1223         di.customFlags = curFlags;
1224 }
1225
1226 /**
1227  * @brief Set all item's view-flag.
1228  * @param [in] flag Flag value to set.
1229  * @param [in] mask Mask for possible flag values.
1230  */
1231 void SetItemViewFlag(CDiffContext& ctxt, unsigned flag, unsigned mask)
1232 {
1233         DIFFITEM *pos = ctxt.GetFirstDiffPosition();
1234
1235         while (pos != nullptr)
1236         {
1237                 unsigned curFlags = ctxt.GetCustomFlags1(pos);
1238                 curFlags &= ~mask; // Zero bits masked
1239                 curFlags |= flag;
1240                 ctxt.SetCustomFlags1(pos, curFlags);
1241                 ctxt.GetNextDiffPosition(pos);
1242         }
1243 }
1244
1245 /**
1246  * @brief Mark selected items as needing for rescan.
1247  * @return Count of items to rescan.
1248  */
1249 void MarkForRescan(DIFFITEM &di)
1250 {
1251         SetDiffStatus(di, 0, DIFFCODE::TEXTFLAGS | DIFFCODE::SIDEFLAGS | DIFFCODE::COMPAREFLAGS);
1252         SetDiffStatus(di, DIFFCODE::NEEDSCAN, DIFFCODE::SCANFLAGS);
1253 }
1254
1255 /**
1256  * @brief Return string such as "15 of 30 Files Affected" or "30 Files Affected"
1257  */
1258 String FormatFilesAffectedString(int nFilesAffected, int nFilesTotal)
1259 {
1260         if (nFilesAffected == nFilesTotal)
1261                 return strutils::format_string1(_("(%1 Files Affected)"), NumToStr(nFilesTotal));
1262         else
1263                 return strutils::format_string2(_("(%1 of %2 Files Affected)"), NumToStr(nFilesAffected), NumToStr(nFilesTotal));
1264 }
1265
1266 String FormatMenuItemString(const String& fmt1, const String& fmt2, int count, int total)
1267 {
1268         if (count == total)
1269                 return strutils::format_string1(fmt1, NumToStr(total));
1270         else
1271                 return strutils::format_string2(fmt2, NumToStr(count), NumToStr(total));
1272 }
1273
1274 String FormatMenuItemString(SIDE_TYPE src, SIDE_TYPE dst, int count, int total)
1275 {
1276         String fmt1, fmt2;
1277         if (src == SIDE_LEFT && dst == SIDE_RIGHT)
1278         {
1279                 fmt1 = _("Left to Right (%1)");
1280                 fmt2 = _("Left to Right (%1 of %2)");
1281         }
1282         else if (src == SIDE_LEFT && dst == SIDE_MIDDLE)
1283         {
1284                 fmt1 = _("Left to Middle (%1)");
1285                 fmt2 = _("Left to Middle (%1 of %2)");
1286         }
1287         else if (src == SIDE_MIDDLE && dst == SIDE_LEFT)
1288         {
1289                 fmt1 = _("Middle to Left (%1)");
1290                 fmt2 = _("Middle to Left (%1 of %2)");
1291         }
1292         else if (src == SIDE_MIDDLE && dst == SIDE_RIGHT)
1293         {
1294                 fmt1 = _("Middle to Right (%1)");
1295                 fmt2 = _("Middle to Right (%1 of %2)");
1296         }
1297         else if (src == SIDE_RIGHT && dst == SIDE_LEFT)
1298         {
1299                 fmt1 = _("Right to Left (%1)");
1300                 fmt2 = _("Right to Left (%1 of %2)");
1301         }
1302         else if (src == SIDE_RIGHT && dst == SIDE_MIDDLE)
1303         {
1304                 fmt1 = _("Right to Middle (%1)");
1305                 fmt2 = _("Right to Middle (%1 of %2)");
1306         }
1307         return FormatMenuItemString(fmt1, fmt2, count, total);
1308 }
1309
1310 String FormatMenuItemString(SIDE_TYPE src, int count, int total)
1311 {
1312         String fmt1, fmt2;
1313         if (src == SIDE_LEFT)
1314         {
1315                 fmt1 = _("Left (%1)");
1316                 fmt2 = _("Left (%1 of %2)");
1317         }
1318         else if (src == SIDE_MIDDLE)
1319         {
1320                 fmt1 = _("Middle (%1)");
1321                 fmt2 = _("Middle (%1 of %2)");
1322         }
1323         else if (src == SIDE_RIGHT)
1324         {
1325                 fmt1 = _("Right (%1)");
1326                 fmt2 = _("Right (%1 of %2)");
1327         }
1328         return FormatMenuItemString(fmt1, fmt2, count, total);
1329 }
1330
1331 String FormatMenuItemStringAll(int nDirs, int count, int total)
1332 {
1333         if (nDirs < 3)
1334                 return FormatMenuItemString(_("Both (%1)"), _("Both (%1 of %2)"), count, total);
1335         else
1336                 return FormatMenuItemString(_("All (%1)"), _("All (%1 of %2)"), count, total);
1337 }
1338
1339 String FormatMenuItemStringTo(SIDE_TYPE src, int count, int total)
1340 {
1341         String fmt1, fmt2;
1342         if (src == SIDE_LEFT)
1343         {
1344                 fmt1 = _("Left to... (%1)");
1345                 fmt2 = _("Left to... (%1 of %2)");
1346         }
1347         else if (src == SIDE_MIDDLE)
1348         {
1349                 fmt1 = _("Middle to... (%1)");
1350                 fmt2 = _("Middle to... (%1 of %2)");
1351         }
1352         else if (src == SIDE_RIGHT)
1353         {
1354                 fmt1 = _("Right to... (%1)");
1355                 fmt2 = _("Right to... (%1 of %2)");
1356         }
1357         return FormatMenuItemString(fmt1, fmt2, count, total);
1358 }
1359
1360 String FormatMenuItemStringAllTo(int nDirs, int count, int total)
1361 {
1362         if (nDirs < 3)
1363                 return FormatMenuItemString(_("Both to... (%1)"), _("Both to... (%1 of %2)"), count, total);
1364         else
1365                 return FormatMenuItemString(_("All to... (%1)"), _("All to... (%1 of %2)"), count, total);
1366 }
1367
1368 String FormatMenuItemStringDifferencesTo(int count, int total)
1369 {
1370         return FormatMenuItemString(_("Differences to... (%1)"), _("Differences to... (%1 of %2)"), count, total);
1371 }
1372
1373 /**
1374  * @brief Rename a file without moving it to different directory.
1375  *
1376  * @param szOldFileName [in] Full path of file to rename.
1377  * @param szNewFileName [in] New file name (without the path).
1378  *
1379  * @return true if file was renamed successfully.
1380  */
1381 bool RenameOnSameDir(const String& szOldFileName, const String& szNewFileName)
1382 {
1383         bool bSuccess = false;
1384
1385         if (paths::DOES_NOT_EXIST != paths::DoesPathExist(szOldFileName))
1386         {
1387                 String sFullName = paths::ConcatPath(paths::GetPathOnly(szOldFileName), szNewFileName);
1388
1389                 // No need to rename if new file already exist.
1390                 if ((sFullName != szOldFileName) ||
1391                         (paths::DOES_NOT_EXIST == paths::DoesPathExist(sFullName)))
1392                 {
1393                         ShellFileOperations fileOp;
1394                         fileOp.SetOperation(FO_RENAME, 0);
1395                         fileOp.AddSourceAndDestination(szOldFileName, sFullName);
1396                         bSuccess = fileOp.Run();
1397                 }
1398                 else
1399                 {
1400                         bSuccess = true;
1401                 }
1402         }
1403
1404         return bSuccess;
1405 }
1406
1407 /**
1408  * @brief Convert number to string.
1409  * Converts number to string, with commas between digits in
1410  * locale-appropriate manner.
1411 */
1412 String NumToStr(int n)
1413 {
1414         return locality::NumToLocaleStr(n);
1415 }
1416
1417 void ExpandSubdirs(CDiffContext& ctxt, DIFFITEM& dip)
1418 {
1419         dip.customFlags |= ViewCustomFlags::EXPANDED;
1420         DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(&dip);
1421         while (diffpos != nullptr)
1422         {
1423                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1424                 if (!di.IsAncestor(&dip))
1425                         break;
1426                 if (di.HasChildren())
1427                         di.customFlags |= ViewCustomFlags::EXPANDED;
1428         }
1429 }
1430
1431 void ExpandAllSubdirs(CDiffContext& ctxt)
1432 {
1433         DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1434         while (diffpos != nullptr)
1435         {
1436                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1437                 di.customFlags |= ViewCustomFlags::EXPANDED;
1438         }
1439 }
1440
1441 void CollapseAllSubdirs(CDiffContext& ctxt)
1442 {
1443         DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1444         while (diffpos != nullptr)
1445         {
1446                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1447                 di.customFlags &= ~ViewCustomFlags::EXPANDED;
1448         }
1449 }
1450
1451 DirViewTreeState *SaveTreeState(const CDiffContext& ctxt)
1452 {
1453         DirViewTreeState *pTreeState = new DirViewTreeState();
1454         DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1455         while (diffpos != nullptr)
1456         {
1457                 const DIFFITEM &di = ctxt.GetNextDiffPosition(diffpos);
1458                 if (di.HasChildren())
1459                 {
1460                         String relpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1461                         pTreeState->insert(std::pair<String, bool>(relpath, !!(di.customFlags & ViewCustomFlags::EXPANDED)));
1462                 }
1463         }
1464         return pTreeState;
1465 }
1466
1467 void RestoreTreeState(CDiffContext& ctxt, DirViewTreeState *pTreeState)
1468 {
1469         DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1470         while (diffpos != nullptr)
1471         {
1472                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1473                 if (di.HasChildren())
1474                 {
1475                         String relpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1476                         std::map<String, bool>::iterator p = pTreeState->find(relpath);
1477                         if (p != pTreeState->end())
1478                         {
1479                                 di.customFlags &= ~ViewCustomFlags::EXPANDED;
1480                                 di.customFlags |= (p->second ? ViewCustomFlags::EXPANDED : 0);
1481                         }
1482                 }
1483         }
1484 }
1485
1486 /**
1487  * @brief Tell if user may use ".." and move to parents directory.
1488  * This function checks if current compare's parent folders should
1489  * be allowed to open. I.e. if current compare folders are:
1490  * - C:\Work\Project1 and
1491  * - C:\Work\Project2
1492  * we check if C:\Work and C:\Work should be allowed to opened.
1493  * For regular folders we allow opening if both folders exist.
1494  * @param [out] leftParent Left parent folder to open.
1495  * @param [out] rightParent Right parent folder to open.
1496  * @return Info if opening parent folders should be enabled:
1497  * - No : upward RESTRICTED
1498  * - ParentIsRegularPath : upward ENABLED
1499  * - ParentIsTempPath : upward ENABLED
1500  */
1501 AllowUpwardDirectory::ReturnCode
1502 CheckAllowUpwardDirectory(const CDiffContext& ctxt, const CTempPathContext *pTempPathContext, PathContext &pathsParent)
1503 {
1504         std::vector<String> path(ctxt.GetCompareDirs());
1505         for (int i = 0; i < static_cast<int>(path.size()); ++i)
1506                 path[i] = ctxt.GetNormalizedPath(i);
1507
1508         // If we have temp context it means we are comparing archives
1509         if (pTempPathContext != nullptr)
1510         {
1511                 std::vector<String> name(path.size());
1512                 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1513                         name[i] = paths::FindFileName(path[i]);
1514
1515                 String::size_type cchLeftRoot = pTempPathContext->m_strRoot[0].length();
1516                 if (path[0].length() <= cchLeftRoot)
1517                 {
1518                         pathsParent.SetSize(ctxt.GetCompareDirs());
1519                         if (pTempPathContext->m_pParent)
1520                         {
1521                                 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1522                                         pathsParent[i] = pTempPathContext->m_pParent->m_strRoot[i];
1523                                 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1524                                         return AllowUpwardDirectory::Never;
1525                                 return AllowUpwardDirectory::ParentIsTempPath;
1526                         }
1527                         for (int i = 0; i < static_cast<int>(path.size()); ++i)
1528                                 pathsParent[i] = pTempPathContext->m_strDisplayRoot[i];
1529                         if (pathsParent.GetSize() < 3)
1530                         {
1531                                 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1]))
1532                                         return AllowUpwardDirectory::Never;
1533                         }
1534                         else
1535                         {
1536                                 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1], pathsParent[2]))
1537                                         return AllowUpwardDirectory::Never;
1538                         }
1539                         if (path.size() == 2 && 
1540                                 strutils::compare_nocase(name[0], _T("ORIGINAL")) == 0 && 
1541                                 strutils::compare_nocase(name[1], _T("ALTERED")) == 0)
1542                         {
1543                                 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1544                                         pathsParent[i] = paths::GetParentPath(pathsParent[i]);
1545                                 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1546                                         name[i] = paths::FindFileName(pathsParent[i]);
1547                                 if (strutils::compare_nocase(name[0], name[1]) == 0)
1548                                 {
1549                                         if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1550                                                 return AllowUpwardDirectory::Never;
1551                                         return AllowUpwardDirectory::ParentIsTempPath;
1552                                 }
1553                         }
1554                         return AllowUpwardDirectory::No;
1555                 }
1556         }
1557
1558         // If regular parent folders exist, allow opening them
1559         pathsParent.SetSize(ctxt.GetCompareDirs());
1560         for (int i = 0; i < static_cast<int>(path.size()); ++i)
1561                 pathsParent[i] = paths::GetParentPath(path[i]);
1562         if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1563                 return AllowUpwardDirectory::Never;
1564         return AllowUpwardDirectory::ParentIsRegularPath;
1565 }