OSDN Git Service

Merge with stable
[winmerge-jp/winmerge-jp.git] / Src / DirActions.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    see Merge.cpp for license (GPLv2+) statement
3 //
4 /////////////////////////////////////////////////////////////////////////////
5 /**
6  *  @file DirActions.cpp
7  *
8  *  @brief Implementation of methods of CDirView that copy/move/delete files
9  */
10 // ID line follows -- this is updated by SVN
11 // $Id: DirActions.cpp 6572 2009-03-18 18:51:20Z kimmov $
12
13 // It would be nice to make this independent of the UI (CDirView)
14 // but it needs access to the list of selected items.
15 // One idea would be to provide an iterator over them.
16 //
17
18 #include "DirActions.h"
19 #include "MergeApp.h"
20 #include "UnicodeString.h"
21 #include "7zCommon.h"
22 #include "ShellFileOperations.h"
23 #include "DiffItem.h"
24 #include "FileActionScript.h"
25 #include "locality.h"
26 #include "FileFilterHelper.h"
27 #include "coretools.h"
28 #include "OptionsMgr.h"
29
30 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, int count,
31                 const String& src, const String& dest, bool destIsSide);
32 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, int count,
33                 const String& src, const String& dest, bool destIsSide);
34 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
35                 int origin, int destination, size_t count,
36                 const String& src, const String& dest, bool destIsSide);
37
38 ContentsChangedException::ContentsChangedException(const String& failpath)
39 {
40         m_msg = string_format_string1(
41         _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
42         failpath);
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                 string_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                 string_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.c_str();
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) == IS_EXISTING_DIR)
140                 strSrc = paths_AddTrailingSlash(src);
141         String strDest(dest);
142         if (paths_DoesPathExist(dest) == 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)
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                         ctxt.RemoveDiff(reinterpret_cast<uintptr_t>(&di));
268                         bRemoveItem = true;
269                 }
270                 else
271                 {
272                         di.diffcode.unsetSideFlag(act.UIOrigin);
273                         SetDiffCompare(di, DIFFCODE::NOCMP);
274                         bUpdateSrc = true;
275                 }
276                 break;
277         }
278
279         if (bUpdateSrc)
280                 ctxt.UpdateStatusFromDisk(reinterpret_cast<uintptr_t>(&di), act.UIOrigin);
281         if (bUpdateDest)
282                 ctxt.UpdateStatusFromDisk(reinterpret_cast<uintptr_t>(&di), act.UIDestination);
283
284         if (bRemoveItem)
285                 return UPDATEITEM_REMOVE;
286         if (bUpdateSrc | bUpdateDest)
287                 return UPDATEITEM_UPDATE;
288         return UPDATEITEM_NONE;
289 }
290
291 /**
292  * @brief Find the CDiffContext diffpos of an item from its left & right paths
293  * @return POSITION to item, NULL if not found.
294  * @note Filenames must be same, if they differ NULL is returned.
295  */
296 uintptr_t FindItemFromPaths(const CDiffContext& ctxt, const String& pathLeft, const String& pathRight)
297 {
298         String file1 = paths_FindFileName(pathLeft);
299         String file2 = paths_FindFileName(pathRight);
300
301         // Filenames must be identical
302         if (string_compare_nocase(file1, file2) != 0)
303                 return NULL;
304
305         String path1(pathLeft, 0, pathLeft.length() - file1.length()); // include trailing backslash
306         String path2(pathRight, 0, pathRight.length() - file2.length()); // include trailing backslash
307
308         // Path can contain (because of difftools?) '/' and '\'
309         // so for comparing purposes, convert whole path to use '\\'
310         replace_char(&*path1.begin(), '/', '\\');
311         replace_char(&*path2.begin(), '/', '\\');
312
313         String base1 = ctxt.GetLeftPath(); // include trailing backslash
314         if (path1.compare(0, base1.length(), base1.c_str()) != 0)
315                 return NULL;
316         path1.erase(0, base1.length()); // turn into relative path
317         if (String::size_type length = path1.length())
318                 path1.resize(length - 1); // remove trailing backslash
319
320         String base2 = ctxt.GetRightPath(); // include trailing backslash
321         if (path2.compare(0, base2.length(), base2.c_str()) != 0)
322                 return NULL;
323         path2.erase(0, base2.length()); // turn into relative path
324         if (String::size_type length = path2.length())
325                 path2.resize(length - 1); // remove trailing backslash
326
327         uintptr_t pos = ctxt.GetFirstDiffPosition();
328         while (uintptr_t currentPos = pos) // Save our current pos before getting next
329         {
330                 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
331                 if (di.diffFileInfo[0].path == path1 &&
332                         di.diffFileInfo[1].path == path2 &&
333                         di.diffFileInfo[0].filename == file1 &&
334                         di.diffFileInfo[1].filename == file2)
335                 {
336                         return currentPos;
337                 }
338         }
339         return 0;
340 }
341
342 /// is it possible to copy item to left ?
343 bool IsItemCopyable(const DIFFITEM & di, int index)
344 {
345         // don't let them mess with error items
346         if (di.diffcode.isResultError()) return false;
347         // can't copy same items
348         if (di.diffcode.isResultSame()) return false;
349         // impossible if not existing
350         if (!di.diffcode.isExists(index)) return false;
351         // everything else can be copied to other side
352         return true;
353 }
354
355 /// is it possible to delete item ?
356 bool IsItemDeletable(const DIFFITEM & di, int index)
357 {
358         // don't let them mess with error items
359         if (di.diffcode.isResultError()) return false;
360         // impossible if not existing
361         if (!di.diffcode.isExists(index)) return false;
362         // everything else can be deleted
363         return true;
364 }
365
366 /// is it possible to delete both items ?
367 bool IsItemDeletableOnBoth(const CDiffContext& ctxt, const DIFFITEM & di)
368 {
369         // don't let them mess with error items
370         if (di.diffcode.isResultError()) return false;
371         // impossible if only on right or left
372         for (int i = 0; i < ctxt.GetCompareDirs(); ++i)
373                 if (!di.diffcode.isExists(i)) return false;
374
375         // everything else can be deleted on both
376         return true;
377 }
378
379 /**
380  * @brief Determine if item can be opened.
381  * Basically we only disable opening unique files at the moment.
382  * Unique folders can be opened since we ask for creating matching folder
383  * to another side.
384  * @param [in] di DIFFITEM for item to check.
385  * @return true if the item can be opened, false otherwise.
386  */
387 bool IsItemOpenable(const CDiffContext& ctxt, const DIFFITEM & di, bool treemode)
388 {
389         if (treemode && ctxt.m_bRecursive)
390         {
391                 if (di.diffcode.isDirectory() || !IsItemExistAll(ctxt, di))
392                         return false;
393         }
394         else 
395         {
396                 if (!di.diffcode.isDirectory() && !IsItemExistAll(ctxt, di))
397                         return false;
398         }
399         return true;
400 }
401 /// is it possible to compare these two items?
402 bool AreItemsOpenable(const CDiffContext& ctxt, SELECTIONTYPE selectionType, const DIFFITEM & di1, const DIFFITEM & di2)
403 {
404         String sLeftBasePath = ctxt.GetPath(0);
405         String sRightBasePath = ctxt.GetPath(1);
406
407         // Must be both directory or neither
408         if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory()) return false;
409
410         switch (selectionType)
411         {
412         case SELECTIONTYPE_NORMAL:
413                 // Must be on different sides, or one on one side & one on both
414                 if (di1.diffcode.isSideFirstOnly() && (di2.diffcode.isSideSecondOnly() ||
415                         di2.diffcode.isSideBoth()))
416                         return true;
417                 if (di1.diffcode.isSideSecondOnly() && (di2.diffcode.isSideFirstOnly() ||
418                         di2.diffcode.isSideBoth()))
419                         return true;
420                 if (di1.diffcode.isSideBoth() && (di2.diffcode.isSideFirstOnly() ||
421                         di2.diffcode.isSideSecondOnly()))
422                         return true;
423                 break;
424         case SELECTIONTYPE_LEFT1LEFT2:
425                 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(0))
426                         return true;
427                 break;
428         case SELECTIONTYPE_RIGHT1RIGHT2:
429                 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(1))
430                         return true;
431                 break;
432         case SELECTIONTYPE_LEFT1RIGHT2:
433                 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(1))
434                         return true;
435                 break;
436         case SELECTIONTYPE_LEFT2RIGHT1:
437                 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(0))
438                         return true;
439                 break;
440         }
441
442         // Allow to compare items if left & right path refer to same directory
443         // (which means there is effectively two files involved). No need to check
444         // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
445         if (string_compare_nocase(sLeftBasePath, sRightBasePath) == 0)
446                 return true;
447
448         return false;
449 }
450 /// is it possible to compare these three items?
451 bool AreItemsOpenable(const CDiffContext& ctxt, const DIFFITEM & di1, const DIFFITEM & di2, const DIFFITEM & di3)
452 {
453         String sLeftBasePath = ctxt.GetPath(0);
454         String sMiddleBasePath = ctxt.GetPath(1);
455         String sRightBasePath = ctxt.GetPath(2);
456         String sLeftPath1 = paths_ConcatPath(di1.getFilepath(0, sLeftBasePath), di1.diffFileInfo[0].filename);
457         String sLeftPath2 = paths_ConcatPath(di2.getFilepath(0, sLeftBasePath), di2.diffFileInfo[0].filename);
458         String sLeftPath3 = paths_ConcatPath(di3.getFilepath(0, sLeftBasePath), di3.diffFileInfo[0].filename);
459         String sMiddlePath1 = paths_ConcatPath(di1.getFilepath(1, sMiddleBasePath), di1.diffFileInfo[1].filename);
460         String sMiddlePath2 = paths_ConcatPath(di2.getFilepath(1, sMiddleBasePath), di2.diffFileInfo[1].filename);
461         String sMiddlePath3 = paths_ConcatPath(di3.getFilepath(1, sMiddleBasePath), di3.diffFileInfo[1].filename);
462         String sRightPath1 = paths_ConcatPath(di1.getFilepath(2, sRightBasePath), di1.diffFileInfo[2].filename);
463         String sRightPath2 = paths_ConcatPath(di2.getFilepath(2, sRightBasePath), di2.diffFileInfo[2].filename);
464         String sRightPath3 = paths_ConcatPath(di3.getFilepath(2, sRightBasePath), di3.diffFileInfo[2].filename);
465         // Must not be binary (unless archive)
466         if
467         (
468                 (di1.diffcode.isBin() || di2.diffcode.isBin() || di3.diffcode.isBin())
469         &&!     (
470                         HasZipSupport()
471                 &&      (sLeftPath1.empty() || ArchiveGuessFormat(sLeftPath1))
472                 &&      (sMiddlePath1.empty() || ArchiveGuessFormat(sMiddlePath1))
473                 &&      (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
474                 &&      (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2))
475                 &&      (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
476                 &&      (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2)) /* FIXME: */
477                 )
478         )
479         {
480                 return false;
481         }
482
483         // Must be both directory or neither
484         if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory() && di1.diffcode.isDirectory() != di3.diffcode.isDirectory()) return false;
485
486         // Must be on different sides, or one on one side & one on both
487         if (di1.diffcode.isExists(0) && di2.diffcode.isExists(1) && di3.diffcode.isExists(2))
488                 return true;
489         if (di1.diffcode.isExists(0) && di2.diffcode.isExists(2) && di3.diffcode.isExists(1))
490                 return true;
491         if (di1.diffcode.isExists(1) && di2.diffcode.isExists(0) && di3.diffcode.isExists(2))
492                 return true;
493         if (di1.diffcode.isExists(1) && di2.diffcode.isExists(2) && di3.diffcode.isExists(0))
494                 return true;
495         if (di1.diffcode.isExists(2) && di2.diffcode.isExists(0) && di3.diffcode.isExists(1))
496                 return true;
497         if (di1.diffcode.isExists(2) && di2.diffcode.isExists(1) && di3.diffcode.isExists(0))
498                 return true;
499
500         // Allow to compare items if left & right path refer to same directory
501         // (which means there is effectively two files involved). No need to check
502         // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
503         if (string_compare_nocase(sLeftBasePath, sMiddleBasePath) == 0 && string_compare_nocase(sLeftBasePath, sRightBasePath) == 0)
504                 return true;
505
506         return false;
507 }
508 /// is it possible to open item ?
509 bool IsItemOpenableOn(const DIFFITEM & di, int index)
510 {
511         // impossible if not existing
512         if (!di.diffcode.isExists(index)) return false;
513
514         // everything else can be opened on right
515         return true;
516 }
517
518 /// is it possible to open left ... item ?
519 bool IsItemOpenableOnWith(const DIFFITEM & di, int index)
520 {
521         return (!di.diffcode.isDirectory() && IsItemOpenableOn(di, index));
522 }
523 /// is it possible to copy to... left item?
524 bool IsItemCopyableToOn(const DIFFITEM & di, int index)
525 {
526         // impossible if only on right
527         if (!di.diffcode.isExists(index)) return false;
528
529         // everything else can be copied to from left
530         return true;
531 }
532
533 // When navigating differences, do we stop at this one ?
534 bool IsItemNavigableDiff(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         if (di.diffcode.isResultFiltered() || di.diffcode.isResultError())
540                 return false;
541         if (!di.diffcode.isResultDiff() && IsItemExistAll(ctxt, di))
542                 return false;
543         return true;
544 }
545
546 bool IsItemExistAll(const CDiffContext& ctxt, const DIFFITEM & di)
547 {
548         // Not a valid diffitem, one of special items (e.g "..")
549         if (di.diffcode.diffcode == 0)
550                 return false;
551         if (ctxt.GetCompareDirs() == 2)
552                 return di.diffcode.isSideBoth();
553         else
554                 return di.diffcode.isSideAll();
555 }
556
557
558 /**
559  * @brief Determines if the user wants to see given item.
560  * This function determines what items to show and what items to hide. There
561  * are lots of combinations, but basically we check if menuitem is enabled or
562  * disabled and show/hide matching items. For non-recursive compare we never
563  * hide folders as that would disable user browsing into them. And we even
564  * don't really know if folders are identical or different as we haven't
565  * compared them.
566  * @param [in] di Item to check.
567  * @return true if item should be shown, false if not.
568  * @sa CDirDoc::Redisplay()
569  */
570 bool IsShowable(const CDiffContext& ctxt, const DIFFITEM & di, const DirViewFilterSettings& filter)
571 {
572         if (di.customFlags1 & ViewCustomFlags::HIDDEN)
573                 return false;
574
575         if (di.diffcode.isResultFiltered())
576         {
577                 // Treat SKIPPED as a 'super'-flag. If item is skipped and user
578                 // wants to see skipped items show item regardless of other flags
579                 return filter.show_skipped;
580         }
581
582         if (di.diffcode.isDirectory())
583         {
584                 // Subfolders in non-recursive compare can only be skipped or unique
585                 if (!ctxt.m_bRecursive)
586                 {
587                         // left/right filters
588                         if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
589                                 return false;
590                         if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
591                                 return false;
592
593                         // result filters
594                         if (di.diffcode.isResultError() && FALSE/* !GetMainFrame()->m_bShowErrors FIXME:*/)
595                                 return false;
596                 }
597                 else // recursive mode (including tree-mode)
598                 {
599                         // left/right filters
600                         if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
601                                 return false;
602                         if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
603                                 return false;
604
605                         // ONLY filter folders by result (identical/different) for tree-view.
606                         // In the tree-view we show subfolders with identical/different
607                         // status. The flat view only shows files inside folders. So if we
608                         // filter by status the files inside folder are filtered too and
609                         // users see files appearing/disappearing without clear logic.          
610                         if (filter.tree_mode)
611                         {
612                                 // result filters
613                                 if (di.diffcode.isResultError() && FALSE/* !GetMainFrame()->m_bShowErrors FIXME:*/)
614                                         return false;
615
616                                 // result filters
617                                 if (di.diffcode.isResultSame() && !filter.show_identical)
618                                         return false;
619                                 if (di.diffcode.isResultDiff() && !filter.show_different)
620                                         return false;
621                         }
622                 }
623         }
624         else
625         {
626                 // left/right filters
627                 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
628                         return false;
629                 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
630                         return false;
631
632                 // file type filters
633                 if (di.diffcode.isBin() && !filter.show_binaries)
634                         return false;
635
636                 // result filters
637                 if (di.diffcode.isResultSame() && !filter.show_identical)
638                         return false;
639                 if (di.diffcode.isResultError() /* && !GetMainFrame()->m_bShowErrors FIXME:*/)
640                         return false;
641                 if (di.diffcode.isResultDiff() && !filter.show_different)
642                         return false;
643         }
644         return true;
645 }
646
647 /**
648  * @brief Open one selected item.
649  * @param [in] pos1 Item position.
650  * @param [in,out] di1 Pointer to first diffitem.
651  * @param [in,out] di2 Pointer to second diffitem.
652  * @param [in,out] di3 Pointer to third diffitem.
653  * @param [out] paths First/Second/Third paths.
654  * @param [out] sel1 Item's selection index in listview.
655  * @param [in,out] isDir Is item folder?
656  * return false if there was error or item was completely processed.
657  */
658 bool GetOpenOneItem(const CDiffContext& ctxt, uintptr_t pos1, const DIFFITEM **di1, const DIFFITEM **di2, const DIFFITEM **di3,
659                 PathContext & paths, int & sel1, bool & isdir, String& errmsg)
660 {
661         *di1 = &ctxt.GetDiffAt(pos1);
662         *di2 = *di1;
663         *di3 = *di1;
664
665         paths = GetItemFileNames(ctxt, **di1);
666
667         if ((*di1)->diffcode.isDirectory())
668                 isdir = true;
669
670         if (isdir && ((*di1)->diffcode.isExistsFirst() && (*di1)->diffcode.isExistsSecond() && (*di1)->diffcode.isExistsThird()))
671         {
672                 // Check both folders exist. If either folder is missing that means
673                 // folder has been changed behind our back, so we just tell user to
674                 // refresh the compare.
675                 PATH_EXISTENCE path1Exists = paths_DoesPathExist(paths[0]);
676                 PATH_EXISTENCE path2Exists = paths_DoesPathExist(paths[1]);
677                 if (path1Exists != IS_EXISTING_DIR || path2Exists != IS_EXISTING_DIR)
678                 {
679                         String invalid = path1Exists == IS_EXISTING_DIR ? paths[0] : paths[1];
680                         errmsg = string_format_string1(
681                                 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
682                                 invalid);
683                         return false;
684                 }
685         }
686
687         return true;
688 }
689
690 /**
691  * @brief Open two selected items.
692  * @param [in] pos1 First item position.
693  * @param [in] pos2 Second item position.
694  * @param [in,out] di1 Pointer to first diffitem.
695  * @param [in,out] di2 Pointer to second diffitem.
696  * @param [out] paths First/Second/Third paths.
697  * @param [out] sel1 First item's selection index in listview.
698  * @param [out] sel2 Second item's selection index in listview.
699  * @param [in,out] isDir Is item folder?
700  * return false if there was error or item was completely processed.
701  */
702 bool GetOpenTwoItems(const CDiffContext& ctxt, SELECTIONTYPE selectionType, uintptr_t pos1, uintptr_t pos2, const DIFFITEM **di1, const DIFFITEM **di2,
703                 PathContext & paths, int & sel1, int & sel2, bool & isDir, String& errmsg)
704 {
705         String pathLeft, pathRight;
706
707         // Two items selected, get their info
708         *di1 = &ctxt.GetDiffAt(pos1);
709         *di2 = &ctxt.GetDiffAt(pos2);
710
711         // Check for binary & side compatibility & file/dir compatibility
712         if (!AreItemsOpenable(ctxt, selectionType, **di1, **di2))
713         {
714                 return false;
715         }
716
717         String temp;
718         switch (selectionType)
719         {
720         case SELECTIONTYPE_NORMAL:
721                 // Ensure that di1 is on left (swap if needed)
722                 if ((*di1)->diffcode.isSideSecondOnly() || ((*di1)->diffcode.isSideBoth() &&
723                                 (*di2)->diffcode.isSideFirstOnly()))
724                 {
725                         const DIFFITEM * temp = *di1;
726                         *di1 = *di2;
727                         *di2 = temp;
728                         int num = sel1;
729                         sel1 = sel2;
730                         sel2 = num;
731                 }
732                 // Fill in pathLeft & pathRight
733                 GetItemFileNames(ctxt, **di1, pathLeft, temp);
734                 GetItemFileNames(ctxt, **di2, temp, pathRight);
735                 break;
736         case SELECTIONTYPE_LEFT1LEFT2:
737                 GetItemFileNames(ctxt, **di1, pathLeft, temp);
738                 GetItemFileNames(ctxt, **di2, pathRight, temp);
739                 break;
740         case SELECTIONTYPE_RIGHT1RIGHT2:
741                 GetItemFileNames(ctxt, **di1, temp, pathLeft);
742                 GetItemFileNames(ctxt, **di2, temp, pathRight);
743                 break;
744         case SELECTIONTYPE_LEFT1RIGHT2:
745                 GetItemFileNames(ctxt, **di1, pathLeft, temp);
746                 GetItemFileNames(ctxt, **di2, temp, pathRight);
747                 break;
748         case SELECTIONTYPE_LEFT2RIGHT1:
749                 GetItemFileNames(ctxt, **di1, temp, pathRight);
750                 GetItemFileNames(ctxt, **di2, pathLeft, temp);
751                 break;
752         }
753
754         if ((*di1)->diffcode.isDirectory())
755         {
756                 isDir = true;
757                 if (GetPairComparability(PathContext(pathLeft, pathRight)) != IS_EXISTING_DIR)
758                 {
759                         errmsg = _("The selected folder is invalid.");
760                         return false;
761                 }
762         }
763
764         paths.SetLeft(pathLeft);
765         paths.SetRight(pathRight);
766
767         return true;
768 }
769
770 /**
771  * @brief Open three selected items.
772  * @param [in] pos1 First item position.
773  * @param [in] pos2 Second item position.
774  * @param [in] pos3 Third item position.
775  * @param [in,out] di1 Pointer to first diffitem.
776  * @param [in,out] di2 Pointer to second diffitem.
777  * @param [in,out] di3 Pointer to third diffitem.
778  * @param [out] paths First/Second/Third paths.
779  * @param [out] sel1 First item's selection index in listview.
780  * @param [out] sel2 Second item's selection index in listview.
781  * @param [out] sel3 Third item's selection index in listview.
782  * @param [in,out] isDir Is item folder?
783  * return false if there was error or item was completely processed.
784  */
785 bool GetOpenThreeItems(const CDiffContext& ctxt, uintptr_t pos1, uintptr_t pos2, uintptr_t pos3, const DIFFITEM **di1, const DIFFITEM **di2, const DIFFITEM **di3,
786                 PathContext & paths, int & sel1, int & sel2, int & sel3, bool & isDir, String& errmsg)
787 {
788         String pathLeft, pathMiddle, pathRight;
789
790         if (!pos3)
791         {
792                 // Two items selected, get their info
793                 *di1 = &ctxt.GetDiffAt(pos1);
794                 *di2 = &ctxt.GetDiffAt(pos2);
795
796                 // Check for binary & side compatibility & file/dir compatibility
797                 if (!::AreItemsOpenable(ctxt, **di1, **di2, **di2) && 
798                         !::AreItemsOpenable(ctxt, **di1, **di1, **di2))
799                 {
800                         return false;
801                 }
802                 // Ensure that di1 is on left (swap if needed)
803                 if ((*di1)->diffcode.isExists(0) && (*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2))
804                 {
805                         *di3 = *di2;
806                         *di2 = *di1;
807                         sel3 = sel2;
808                         sel2 = sel1;
809                 }
810                 else if ((*di1)->diffcode.isExists(0) && (*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(1))
811                 {
812                         *di3 = *di1;
813                         sel3 = sel1;
814                 }
815                 else if ((*di1)->diffcode.isExists(1) && (*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(0))
816                 {
817                         std::swap(*di1, *di2);
818                         std::swap(sel1, sel2);
819                         *di3 = *di2;
820                         sel3 = sel2;
821                 }
822                 else if ((*di2)->diffcode.isExists(0) && (*di2)->diffcode.isExists(1) && (*di1)->diffcode.isExists(2))
823                 {
824                         std::swap(*di1, *di2);
825                         std::swap(sel1, sel2);
826                         *di3 = *di2;
827                         *di2 = *di1;
828                         sel3 = sel2;
829                         sel2 = sel1;
830                 }
831                 else if ((*di2)->diffcode.isExists(0) && (*di2)->diffcode.isExists(2) && (*di1)->diffcode.isExists(1))
832                 {
833                         std::swap(*di1, *di2);
834                         std::swap(sel1, sel2);
835                         *di3 = *di1;
836                         sel3 = sel1;
837                 }
838                 else if ((*di2)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2) && (*di1)->diffcode.isExists(0))
839                 {
840                         *di3 = *di2;
841                         sel3 = sel2;
842                 }
843         }
844         else
845         {
846                 // Three items selected, get their info
847                 *di1 = &ctxt.GetDiffAt(pos1);
848                 *di2 = &ctxt.GetDiffAt(pos2);
849                 *di3 = &ctxt.GetDiffAt(pos3);
850
851                 // Check for binary & side compatibility & file/dir compatibility
852                 if (!::AreItemsOpenable(ctxt, **di1, **di2, **di3))
853                 {
854                         return false;
855                 }
856                 // Ensure that di1 is on left (swap if needed)
857                 if ((*di1)->diffcode.isExists(0) && (*di2)->diffcode.isExists(1) && (*di3)->diffcode.isExists(2))
858                 {
859                 }
860                 else if ((*di1)->diffcode.isExists(0) && (*di2)->diffcode.isExists(2) && (*di3)->diffcode.isExists(1))
861                 {
862                         std::swap(*di2, *di3);
863                         std::swap(sel2, sel3);
864                 }
865                 else if ((*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(0) && (*di3)->diffcode.isExists(2))
866                 {
867                         std::swap(*di1, *di2);
868                         std::swap(sel1, sel2);
869                 }
870                 else if ((*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2) && (*di3)->diffcode.isExists(0))
871                 {
872                         std::swap(*di1, *di3);
873                         std::swap(sel1, sel3);
874                         std::swap(*di2, *di3);
875                         std::swap(sel2, sel3);
876                 }
877                 else if ((*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(0) && (*di3)->diffcode.isExists(1))
878                 {
879                         std::swap(*di1, *di2);
880                         std::swap(sel1, sel2);
881                         std::swap(*di2, *di3);
882                         std::swap(sel2, sel3);
883                 }
884                 else if ((*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(1) && (*di3)->diffcode.isExists(0))
885                 {
886                         std::swap(*di1, *di3);
887                         std::swap(sel1, sel3);
888                 }
889         }
890
891         // Fill in pathLeft & & pathMiddle & pathRight
892         PathContext pathsTemp = GetItemFileNames(ctxt, **di1);
893         pathLeft = pathsTemp[0];
894         pathsTemp = GetItemFileNames(ctxt, **di2);
895         pathMiddle = pathsTemp[1];
896         pathsTemp = GetItemFileNames(ctxt, **di3);
897         pathRight = pathsTemp[2];
898
899         if ((*di1)->diffcode.isDirectory())
900         {
901                 isDir = true;
902                 if (GetPairComparability(PathContext(pathLeft, pathMiddle, pathRight)) != IS_EXISTING_DIR)
903                 {
904                         errmsg = _("The selected folder is invalid.");
905                         return false;
906                 } 
907         }
908
909         paths.SetLeft(pathLeft.c_str());
910         paths.SetRight(pathRight.c_str());
911
912         return true;
913 }
914
915 /**
916  * @brief Get the file names on both sides for specified item.
917  * @note Return empty strings if item is special item.
918  */
919 void GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM & di, String& strLeft, String& strRight)
920 {
921         const String leftrelpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
922         const String rightrelpath = paths_ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename);
923         const String & leftpath = ctxt.GetPath(0);
924         const String & rightpath = ctxt.GetPath(1);
925         strLeft = paths_ConcatPath(leftpath, leftrelpath);
926         strRight = paths_ConcatPath(rightpath, rightrelpath);
927 }
928
929 String GetItemFileName(const CDiffContext& ctxt, const DIFFITEM & di, int index)
930 {
931         return paths_ConcatPath(ctxt.GetPath(index), paths_ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename));
932 }
933
934 PathContext GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM & di)
935 {
936         PathContext paths;
937         for (int nIndex = 0; nIndex < ctxt.GetCompareDirs(); nIndex++)
938         {
939                 const String relpath = paths_ConcatPath(di.diffFileInfo[nIndex].path, di.diffFileInfo[nIndex].filename);
940                 const String & path = ctxt.GetPath(nIndex);
941                 paths.SetPath(nIndex, paths_ConcatPath(path, relpath));
942         }
943         return paths;
944 }
945
946 /**
947  * @brief Return image index appropriate for this row
948  */
949 int GetColImage(const CDiffContext&ctxt, const DIFFITEM & di)
950 {
951         // Must return an image index into image list created above in OnInitDialog
952         if (di.diffcode.isResultError())
953                 return DIFFIMG_ERROR;
954         if (di.diffcode.isResultAbort())
955                 return DIFFIMG_ABORT;
956         if (di.diffcode.isResultFiltered())
957                 return (di.diffcode.isDirectory() ? DIFFIMG_DIRSKIP : DIFFIMG_SKIP);
958         if (di.diffcode.isSideFirstOnly())
959                 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
960         if (di.diffcode.isSideSecondOnly())
961                 return (ctxt.GetCompareDirs() < 3 ? 
962                         (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
963                         (di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
964         if (di.diffcode.isSideThirdOnly())
965                 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
966         if (ctxt.GetCompareDirs() == 3)
967         {
968                 if (!di.diffcode.isExists(0))
969                         return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
970                 if (!di.diffcode.isExists(1))
971                         return (di.diffcode.isDirectory() ? DIFFIMG_MDIRMISSING : DIFFIMG_MMISSING);
972                 if (!di.diffcode.isExists(2))
973                         return (di.diffcode.isDirectory() ? DIFFIMG_RDIRMISSING : DIFFIMG_RMISSING);
974         }
975         if (di.diffcode.isResultSame())
976         {
977                 if (di.diffcode.isDirectory())
978                         return DIFFIMG_DIRSAME;
979                 else
980                 {
981                         if (di.diffcode.isText())
982                                 return DIFFIMG_TEXTSAME;
983                         else if (di.diffcode.isBin())
984                                 return DIFFIMG_BINSAME;
985                         else
986                                 return DIFFIMG_SAME;
987                 }
988         }
989         // diff
990         if (di.diffcode.isResultDiff())
991         {
992                 if (di.diffcode.isDirectory())
993                         return DIFFIMG_DIRDIFF;
994                 else
995                 {
996                         if (di.diffcode.isText())
997                                 return DIFFIMG_TEXTDIFF;
998                         else if (di.diffcode.isBin())
999                                 return DIFFIMG_BINDIFF;
1000                         else
1001                                 return DIFFIMG_DIFF;
1002                 }
1003         }
1004         return (di.diffcode.isDirectory() ? DIFFIMG_DIR : DIFFIMG_ABORT);
1005 }
1006
1007 /**
1008  * @brief Set side status of diffitem
1009  * @note This does not update UI - ReloadItemStatus() does
1010  * @sa CDirDoc::ReloadItemStatus()
1011  */
1012 void SetDiffSide(DIFFITEM& di, unsigned diffcode)
1013 {
1014         SetDiffStatus(di, diffcode, DIFFCODE::SIDEFLAGS);
1015 }
1016
1017 /**
1018  * @brief Set compare status of diffitem
1019  * @note This does not update UI - ReloadItemStatus() does
1020  * @sa CDirDoc::ReloadItemStatus()
1021  */
1022 void SetDiffCompare(DIFFITEM& di, unsigned diffcode)
1023 {
1024         SetDiffStatus(di, diffcode, DIFFCODE::COMPAREFLAGS);
1025 }
1026
1027 /**
1028  * @brief Set status for diffitem
1029  * @param diffcode New code
1030  * @param mask Defines allowed set of flags to change
1031  * @param idx Item's index to list in UI
1032  */
1033 void SetDiffStatus(DIFFITEM& di, unsigned  diffcode, unsigned mask)
1034 {
1035         // TODO: Why is the update broken into these pieces ?
1036         // Someone could figure out these pieces and probably simplify this.
1037
1038         // Update DIFFITEM code (comparison result)
1039         assert(! ((~mask) & diffcode) ); // make sure they only set flags in their mask
1040         di.diffcode.diffcode &= (~mask); // remove current data
1041         di.diffcode.diffcode |= diffcode; // add new data
1042
1043         // update DIFFITEM time (and other disk info), and tell views
1044 }
1045
1046 void SetDiffCounts(DIFFITEM& di, unsigned diffs, unsigned ignored)
1047 {
1048         di.nidiffs = ignored; // see StoreDiffResult() in DirScan.cpp
1049         di.nsdiffs = diffs;
1050 }
1051
1052 /**
1053  * @brief Set item's view-flag.
1054  * @param [in] key Item fow which flag is set.
1055  * @param [in] flag Flag value to set.
1056  * @param [in] mask Mask for possible flag values.
1057  */
1058 void SetItemViewFlag(DIFFITEM& di, unsigned flag, unsigned mask)
1059 {
1060         unsigned curFlags = di.customFlags1;
1061         curFlags &= ~mask; // Zero bits masked
1062         curFlags |= flag;
1063         di.customFlags1 = curFlags;
1064 }
1065
1066 /**
1067  * @brief Set all item's view-flag.
1068  * @param [in] flag Flag value to set.
1069  * @param [in] mask Mask for possible flag values.
1070  */
1071 void SetItemViewFlag(CDiffContext& ctxt, unsigned flag, unsigned mask)
1072 {
1073         uintptr_t pos = ctxt.GetFirstDiffPosition();
1074
1075         while (pos != NULL)
1076         {
1077                 UINT curFlags = ctxt.GetCustomFlags1(pos);
1078                 curFlags &= ~mask; // Zero bits masked
1079                 curFlags |= flag;
1080                 ctxt.SetCustomFlags1(pos, curFlags);
1081                 ctxt.GetNextDiffPosition(pos);
1082         }
1083 }
1084
1085 /**
1086  * @brief Mark selected items as needing for rescan.
1087  * @return Count of items to rescan.
1088  */
1089 void MarkForRescan(DIFFITEM &di)
1090 {
1091         SetDiffStatus(di, 0, DIFFCODE::TEXTFLAGS | DIFFCODE::SIDEFLAGS | DIFFCODE::COMPAREFLAGS);
1092         SetDiffStatus(di, DIFFCODE::NEEDSCAN, DIFFCODE::SCANFLAGS);
1093 }
1094
1095 /**
1096  * @brief Return string such as "15 of 30 Files Affected" or "30 Files Affected"
1097  */
1098 String FormatFilesAffectedString(int nFilesAffected, int nFilesTotal)
1099 {
1100         if (nFilesAffected == nFilesTotal)
1101                 return string_format_string1(_("(%1 Files Affected)"), NumToStr(nFilesTotal));
1102         else
1103                 return string_format_string2(_("(%1 of %2 Files Affected)"), NumToStr(nFilesAffected), NumToStr(nFilesTotal));
1104 }
1105
1106 String FormatMenuItemString(const String& fmt1, const String& fmt2, int count, int total)
1107 {
1108         if (count == total)
1109                 return string_format_string1(fmt1, NumToStr(total));
1110         else
1111                 return string_format_string2(fmt2, NumToStr(count), NumToStr(total));
1112 }
1113
1114 String FormatMenuItemString(SIDE_TYPE src, SIDE_TYPE dst, int count, int total)
1115 {
1116         String fmt1, fmt2;
1117         if (src == SIDE_LEFT && dst == SIDE_RIGHT)
1118         {
1119                 fmt1 = _("Left to Right (%1)");
1120                 fmt2 = _("Left to Right (%1 of %2)");
1121         }
1122         else if (src == SIDE_LEFT && dst == SIDE_MIDDLE)
1123         {
1124                 fmt1 = _("Left to Middle (%1)");
1125                 fmt2 = _("Left to middle (%1 of %2)");
1126         }
1127         else if (src == SIDE_MIDDLE && dst == SIDE_LEFT)
1128         {
1129                 fmt1 = _("Middle to Left (%1)");
1130                 fmt2 = _("Middle to Left (%1 of %2)");
1131         }
1132         else if (src == SIDE_MIDDLE && dst == SIDE_RIGHT)
1133         {
1134                 fmt1 = _("Middle to Right (%1)");
1135                 fmt2 = _("Middle to Right (%1 of %2)");
1136         }
1137         else if (src == SIDE_RIGHT && dst == SIDE_LEFT)
1138         {
1139                 fmt1 = _("Right to Left (%1)");
1140                 fmt2 = _("Right to Left (%1 of %2)");
1141         }
1142         else if (src == SIDE_RIGHT && dst == SIDE_MIDDLE)
1143         {
1144                 fmt1 = _("Right to Middle (%1)");
1145                 fmt2 = _("Right to Middle (%1 of %2)");
1146         }
1147         return FormatMenuItemString(fmt1, fmt2, count, total);
1148 }
1149
1150 String FormatMenuItemString(SIDE_TYPE src, int count, int total)
1151 {
1152         String fmt1, fmt2;
1153         if (src == SIDE_LEFT)
1154         {
1155                 fmt1 = _("Left (%1)");
1156                 fmt2 = _("Left (%1 of %2)");
1157         }
1158         else if (src == SIDE_MIDDLE)
1159         {
1160                 fmt1 = _("Middle (%1)");
1161                 fmt2 = _("Middle (%1 of %2)");
1162         }
1163         else if (src == SIDE_RIGHT)
1164         {
1165                 fmt1 = _("Right (%1)");
1166                 fmt2 = _("Right (%1 of %2)");
1167         }
1168         return FormatMenuItemString(fmt1, fmt2, count, total);
1169 }
1170
1171 String FormatMenuItemStringBoth(int count, int total)
1172 {
1173         return FormatMenuItemString(_("Both (%1)"), _("Both (%1 of %2)"), count, total);
1174 }
1175
1176 String FormatMenuItemStringTo(SIDE_TYPE src, int count, int total)
1177 {
1178         String fmt1, fmt2;
1179         if (src == SIDE_LEFT)
1180         {
1181                 fmt1 = _("Left to... (%1)");
1182                 fmt2 = _("Left to... (%1 of %2)");
1183         }
1184         else if (src == SIDE_MIDDLE)
1185         {
1186                 fmt1 = _("Middle to... (%1)");
1187                 fmt2 = _("Middle to... (%1 of %2)");
1188         }
1189         else if (src == SIDE_RIGHT)
1190         {
1191                 fmt1 = _("Right to... (%1)");
1192                 fmt2 = _("Right to... (%1 of %2)");
1193         }
1194         return FormatMenuItemString(fmt1, fmt2, count, total);
1195 }
1196
1197 /**
1198  * @brief Rename a file without moving it to different directory.
1199  *
1200  * @param szOldFileName [in] Full path of file to rename.
1201  * @param szNewFileName [in] New file name (without the path).
1202  *
1203  * @return true if file was renamed successfully.
1204  */
1205 bool RenameOnSameDir(const String& szOldFileName, const String& szNewFileName)
1206 {
1207         bool bSuccess = false;
1208
1209         if (DOES_NOT_EXIST != paths_DoesPathExist(szOldFileName))
1210         {
1211                 String sFullName = paths_ConcatPath(paths_GetPathOnly(szOldFileName), szNewFileName);
1212
1213                 // No need to rename if new file already exist.
1214                 if ((sFullName != szOldFileName) ||
1215                         (DOES_NOT_EXIST == paths_DoesPathExist(sFullName)))
1216                 {
1217                         ShellFileOperations fileOp;
1218                         fileOp.SetOperation(FO_RENAME, 0);
1219                         fileOp.AddSourceAndDestination(szOldFileName, sFullName);
1220                         bSuccess = fileOp.Run();
1221                 }
1222                 else
1223                 {
1224                         bSuccess = true;
1225                 }
1226         }
1227
1228         return bSuccess;
1229 }
1230
1231 /**
1232  * @brief Convert number to string.
1233  * Converts number to string, with commas between digits in
1234  * locale-appropriate manner.
1235 */
1236 String NumToStr(int n)
1237 {
1238         return locality::NumToLocaleStr(n);
1239 }
1240
1241 void ExpandSubdirs(CDiffContext& ctxt, DIFFITEM& dip)
1242 {
1243         dip.customFlags1 |= ViewCustomFlags::EXPANDED;
1244         uintptr_t diffpos = ctxt.GetFirstChildDiffPosition(reinterpret_cast<uintptr_t>(&dip));
1245         while (diffpos)
1246         {
1247                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1248                 if (!di.IsAncestor(&dip))
1249                         break;
1250                 if (di.HasChildren())
1251                         di.customFlags1 |= ViewCustomFlags::EXPANDED;
1252         }
1253 }
1254
1255 void ExpandAllSubdirs(CDiffContext& ctxt)
1256 {
1257         uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1258         while (diffpos)
1259         {
1260                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1261                 di.customFlags1 |= ViewCustomFlags::EXPANDED;
1262         }
1263 }
1264
1265 void CollapseAllSubdirs(CDiffContext& ctxt)
1266 {
1267         uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1268         while (diffpos)
1269         {
1270                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1271                 di.customFlags1 &= ~ViewCustomFlags::EXPANDED;
1272         }
1273 }
1274
1275 DirViewTreeState *SaveTreeState(const CDiffContext& ctxt)
1276 {
1277         DirViewTreeState *pTreeState = new DirViewTreeState();
1278         uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1279         while (diffpos)
1280         {
1281                 const DIFFITEM &di = ctxt.GetNextDiffPosition(diffpos);
1282                 if (di.HasChildren())
1283                 {
1284                         String relpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1285                         pTreeState->insert(std::pair<String, bool>(relpath, !!(di.customFlags1 & ViewCustomFlags::EXPANDED)));
1286                 }
1287         }
1288         return pTreeState;
1289 }
1290
1291 void RestoreTreeState(CDiffContext& ctxt, DirViewTreeState *pTreeState)
1292 {
1293         uintptr_t diffpos = ctxt.GetFirstDiffPosition();
1294         while (diffpos)
1295         {
1296                 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1297                 if (di.HasChildren())
1298                 {
1299                         String relpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1300                         std::map<String, bool>::iterator p = pTreeState->find(relpath);
1301                         if (p != pTreeState->end())
1302                         {
1303                                 di.customFlags1 &= ~ViewCustomFlags::EXPANDED;
1304                                 di.customFlags1 |= (p->second ? ViewCustomFlags::EXPANDED : 0);
1305                         }
1306                 }
1307         }
1308 }
1309
1310 /**
1311  * @brief Tell if user may use ".." and move to parents directory.
1312  * This function checks if current compare's parent folders should
1313  * be allowed to open. I.e. if current compare folders are:
1314  * - C:\Work\Project1 and
1315  * - C:\Work\Project2
1316  * we check if C:\Work and C:\Work should be allowed to opened.
1317  * For regular folders we allow opening if both folders exist.
1318  * @param [out] leftParent Left parent folder to open.
1319  * @param [out] rightParent Right parent folder to open.
1320  * @return Info if opening parent folders should be enabled:
1321  * - No : upward RESTRICTED
1322  * - ParentIsRegularPath : upward ENABLED
1323  * - ParentIsTempPath : upward ENABLED
1324  */
1325 AllowUpwardDirectory::ReturnCode
1326 CheckAllowUpwardDirectory(const CDiffContext& ctxt, const CTempPathContext *pTempPathContext, PathContext &pathsParent)
1327 {
1328         const String & path0 = ctxt.GetNormalizedPath(0);
1329         const String & path1 = ctxt.GetNormalizedPath(1);
1330         const String & path2 = ctxt.GetCompareDirs() > 2 ? ctxt.GetNormalizedPath(2) : _T("");
1331
1332         // If we have temp context it means we are comparing archives
1333         if (pTempPathContext)
1334         {
1335                 String name0 = paths_FindFileName(path0);
1336                 String name1 = paths_FindFileName(path1);
1337                 String name2 = (ctxt.GetCompareDirs() > 2) ? paths_FindFileName(path2) : _T("");
1338
1339                 /* FIXME: for 3way diff*/
1340                 String::size_type cchLeftRoot = pTempPathContext->m_strRoot[0].length();
1341                 if (path0.length() <= cchLeftRoot)
1342                 {
1343                         pathsParent.SetSize(ctxt.GetCompareDirs());
1344                         if (pTempPathContext->m_pParent)
1345                         {
1346                                 pathsParent[0] = pTempPathContext->m_pParent->m_strRoot[0];
1347                                 pathsParent[1] = pTempPathContext->m_pParent->m_strRoot[1];
1348                                 if (GetPairComparability(PathContext(pathsParent[0], pathsParent[1])) != IS_EXISTING_DIR)
1349                                         return AllowUpwardDirectory::Never;
1350                                 return AllowUpwardDirectory::ParentIsTempPath;
1351                         }
1352                         pathsParent[0] = pTempPathContext->m_strDisplayRoot[0];
1353                         pathsParent[1] = pTempPathContext->m_strDisplayRoot[1];
1354                         if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1]))
1355                                 return AllowUpwardDirectory::Never;
1356                         if (string_compare_nocase(name0, _T("ORIGINAL")) == 0 && string_compare_nocase(name1, _T("ALTERED")) == 0)
1357                         {
1358                                 pathsParent[0] = paths_GetParentPath(pathsParent[0]);
1359                                 pathsParent[1] = paths_GetParentPath(pathsParent[1]);
1360                         }
1361                         name0 = paths_FindFileName(pathsParent[0]);
1362                         name1 = paths_FindFileName(pathsParent[1]);
1363                         if (string_compare_nocase(name0, name1) == 0)
1364                         {
1365                                 pathsParent[0] = paths_GetParentPath(pathsParent[0]);
1366                                 pathsParent[1] = paths_GetParentPath(pathsParent[1]);
1367                                 if (GetPairComparability(PathContext(pathsParent[0], pathsParent[1])) != IS_EXISTING_DIR)
1368                                         return AllowUpwardDirectory::Never;
1369                                 return AllowUpwardDirectory::ParentIsTempPath;
1370                         }
1371                         return AllowUpwardDirectory::No;
1372                 }
1373                 name1 = name0;
1374         }
1375
1376         // If regular parent folders exist, allow opening them
1377         pathsParent.SetSize(ctxt.GetCompareDirs());
1378         pathsParent[0] = paths_GetParentPath(path0);
1379         pathsParent[1] = paths_GetParentPath(path1);
1380         if (ctxt.GetCompareDirs() > 2)
1381                 pathsParent[2] = paths_GetParentPath(path2);
1382         if (GetPairComparability(pathsParent) != IS_EXISTING_DIR)
1383                 return AllowUpwardDirectory::Never;
1384         return AllowUpwardDirectory::ParentIsRegularPath;
1385 }