OSDN Git Service

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