OSDN Git Service

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