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