1 // SPDX-License-Identifier: GPL-2.0-or-later
5 * @brief Implementation of methods of CDirView that copy/move/delete files
8 // It would be nice to make this independent of the UI (CDirView)
9 // but it needs access to the list of selected items.
10 // One idea would be to provide an iterator over them.
14 #include "DirActions.h"
16 #include "UnicodeString.h"
18 #include "ShellFileOperations.h"
20 #include "FileActionScript.h"
22 #include "FileFilterHelper.h"
25 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, size_t count,
26 const String& src, const String& dest, bool destIsSide);
27 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, size_t count,
28 const String& src, const String& dest, bool destIsSide);
29 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
30 int origin, int destination, size_t count,
31 const String& src, const String& dest, bool destIsSide);
33 ContentsChangedException::ContentsChangedException(const String& failpath)
34 : m_msg(strutils::format_string1(
35 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
40 FileOperationException::FileOperationException(const String& msg)
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
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.
58 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, size_t count,
59 const String& src, const String& dest, bool destIsSide)
61 String caption = _("Confirm Copy");
62 String strQuestion = count == 1 ? _("Are you sure you want to copy?") :
63 strutils::format(_("Are you sure you want to copy %d items?").c_str(), count);
65 ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
66 destination, count, src, dest, destIsSide);
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
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.
82 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, size_t count,
83 const String& src, const String& dest, bool destIsSide)
85 String caption = _("Confirm Move");
86 String strQuestion = count == 1 ? _("Are you sure you want to move?") :
87 strutils::format(_("Are you sure you want to move %d items?").c_str(), count);
89 ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
90 destination, count, src, dest, destIsSide);
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.
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)
109 ConfirmationNeededException exp;
113 exp.m_caption = caption;
116 sOrig = _("From left:");
117 else if (origin == ctxt.GetCompareDirs() - 1)
118 sOrig = _("From right:");
120 sOrig = _("From middle:");
124 // Copy to left / right
125 if (destination == 0)
126 sDest = _("To left:");
127 else if (destination == ctxt.GetCompareDirs() - 1)
128 sDest = _("To right:");
130 sDest = _("To middle:");
134 // Copy left/right to..
139 if (paths::DoesPathExist(src) == paths::IS_EXISTING_DIR)
140 strSrc = paths::AddTrailingSlash(src);
141 String strDest(dest);
142 if (paths::DoesPathExist(dest) == paths::IS_EXISTING_DIR)
143 strDest = paths::AddTrailingSlash(dest);
145 exp.m_question = question;
146 exp.m_fromText = std::move(sOrig);
147 exp.m_toText = std::move(sDest);
148 exp.m_fromPath = std::move(strSrc);
149 exp.m_toPath = std::move(strDest);
155 * @brief Confirm actions with user as appropriate
156 * (type, whether single or multiple).
158 void ConfirmActionList(const CDiffContext& ctxt, const FileActionScript & actionList)
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();
164 bool bDestIsSide = true;
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
170 case FileAction::ACT_COPY:
171 if (item.UIResult == FileActionItem::UI_DONT_CARE)
174 if (actionList.GetActionItemCount() == 1)
176 ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
177 actionList.GetActionItemCount(), item.src, item.dest,
182 String src = ctxt.GetPath(item.UIOrigin);
187 dst = ctxt.GetPath(item.UIDestination);
191 if (!actionList.m_destBase.empty())
192 dst = actionList.m_destBase;
197 ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
198 actionList.GetActionItemCount(), src, dst, bDestIsSide);
202 case FileAction::ACT_DEL:
205 case FileAction::ACT_MOVE:
206 if (item.UIResult == FileActionItem::UI_DEL)
208 if (actionList.GetActionItemCount() == 1)
210 ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
211 actionList.GetActionItemCount(), item.src, item.dest,
216 String src = ctxt.GetPath(item.UIOrigin);;
219 if (!actionList.m_destBase.empty())
220 dst = actionList.m_destBase;
224 ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
225 actionList.GetActionItemCount(), src, dst, bDestIsSide);
231 LogErrorString(_T("Unknown fileoperation in CDirView::ConfirmActionList()"));
232 throw "Unknown fileoperation in ConfirmActionList()";
238 * @brief Update results for FileActionItem.
239 * This functions is called to update DIFFITEM after FileActionItem.
240 * @param [in] act Action that was done.
241 * @param [in] ctxt Compare context: contains difflist, encoding info etc.
242 * @param [in,out] di Item to update the results.
244 UPDATEITEM_TYPE UpdateDiffAfterOperation(const FileActionItem & act, CDiffContext& ctxt, DIFFITEM &di)
246 bool bUpdateSrc = false;
247 bool bUpdateDest = false;
248 bool bRemoveItem = false;
250 // Use FileActionItem types for simplicity for now.
251 // Better would be to use FileAction contained, since it is not
253 switch (act.UIResult)
255 case FileActionItem::UI_SYNC:
258 CopyDiffSideAndProperties(di, act.UIOrigin, act.UIDestination);
259 if (ctxt.GetCompareDirs() > 2)
260 SetDiffCompare(di, DIFFCODE::NOCMP);
262 UpdateCompareFlagsAfterSync(di, ctxt.m_bRecursive);
263 SetDiffCounts(di, 0, 0);
266 case FileActionItem::UI_MOVE:
269 CopyDiffSideAndProperties(di, act.UIOrigin, act.UIDestination);
270 UnsetDiffSide(di, act.UIOrigin);
271 SetDiffCompare(di, DIFFCODE::NOCMP);
274 case FileActionItem::UI_DEL:
275 if (di.diffcode.isSideOnly(act.UIOrigin))
281 UnsetDiffSide(di, act.UIOrigin);
282 SetDiffCompare(di, DIFFCODE::NOCMP);
289 return UPDATEITEM_REMOVE;
290 if (bUpdateSrc || bUpdateDest)
291 return UPDATEITEM_UPDATE;
292 return UPDATEITEM_NONE;
296 * @brief Find the CDiffContext diffpos of an item from its left & right paths
297 * @return POSITION to item, `nullptr` if not found.
298 * @note Filenames must be same, if they differ `nullptr` is returned.
300 DIFFITEM *FindItemFromPaths(const CDiffContext& ctxt, const PathContext& paths)
303 String file[3], path[3], base;
304 for (nBuffer = 0; nBuffer < paths.GetSize(); ++nBuffer)
306 String p = paths[nBuffer];
307 file[nBuffer] = paths::FindFileName(p);
308 if (file[nBuffer].empty())
310 // Path can contain (because of difftools?) '/' and '\'
311 // so for comparing purposes, convert whole path to use '\\'
312 path[nBuffer] = paths::ToWindowsPath(String(p, 0, p.length() - file[nBuffer].length())); // include trailing backslash
313 base = ctxt.GetPath(nBuffer); // include trailing backslash
314 if (path[nBuffer].compare(0, base.length(), base.c_str()) != 0)
316 path[nBuffer].erase(0, base.length()); // turn into relative path
317 if (String::size_type length = path[nBuffer].length())
318 path[nBuffer].resize(length - 1); // remove trailing backslash
321 // Filenames must be identical
322 if (std::any_of(file, file + paths.GetSize(), [&](auto& it) { return strutils::compare_nocase(it, file[0]) != 0; }))
325 DIFFITEM *pos = ctxt.GetFirstDiffPosition();
326 if (paths.GetSize() == 2)
328 while (DIFFITEM *currentPos = pos) // Save our current pos before getting next
330 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
331 if (di.diffFileInfo[0].path == path[0] &&
332 di.diffFileInfo[1].path == path[1] &&
333 di.diffFileInfo[0].filename == file[0] &&
334 di.diffFileInfo[1].filename == file[1])
342 while (DIFFITEM *currentPos = pos) // Save our current pos before getting next
344 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
345 if (di.diffFileInfo[0].path == path[0] &&
346 di.diffFileInfo[1].path == path[1] &&
347 di.diffFileInfo[2].path == path[2] &&
348 di.diffFileInfo[0].filename == file[0] &&
349 di.diffFileInfo[1].filename == file[1] &&
350 di.diffFileInfo[2].filename == file[2])
359 /// is it possible to copy item to left ?
360 bool IsItemCopyable(const DIFFITEM &di, int index)
362 // don't let them mess with error items
363 if (di.diffcode.isResultError()) return false;
364 // can't copy same items
365 if (di.diffcode.isResultSame()) return false;
366 // impossible if not existing
367 if (!di.diffcode.exists(index)) return false;
368 // everything else can be copied to other side
372 /// is it possible to move item to left ?
373 bool IsItemMovable(const DIFFITEM &di, int index)
375 // don't let them mess with error items
376 if (di.diffcode.isResultError()) return false;
377 // impossible if not existing
378 if (!di.diffcode.exists(index)) return false;
379 // everything else can be copied to other side
383 /// is it possible to delete item ?
384 bool IsItemDeletable(const DIFFITEM &di, int index)
386 // don't let them mess with error items
387 if (di.diffcode.isResultError()) return false;
388 // impossible if not existing
389 if (!di.diffcode.exists(index)) return false;
390 // everything else can be deleted
394 /// is it possible to delete both items ?
395 bool IsItemDeletableOnBoth(const CDiffContext& ctxt, const DIFFITEM &di)
397 // don't let them mess with error items
398 if (di.diffcode.isResultError()) return false;
399 // impossible if only on right or left
400 for (int i = 0; i < ctxt.GetCompareDirs(); ++i)
401 if (!di.diffcode.exists(i)) return false;
403 // everything else can be deleted on both
407 /// is it possible to compare these two items?
408 bool AreItemsOpenable(const CDiffContext& ctxt, SELECTIONTYPE selectionType, const DIFFITEM &di1, const DIFFITEM &di2, bool openableForDir /*= true*/)
410 String sLeftBasePath = ctxt.GetPath(0);
411 String sRightBasePath = ctxt.GetPath(1);
413 // Must be both directory or neither
414 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory()) return false;
416 if (!openableForDir && di1.diffcode.isDirectory()) return false;
418 switch (selectionType)
420 case SELECTIONTYPE_NORMAL:
421 // Must be on different sides, or one on one side & one on both
422 if (di1.diffcode.isSideFirstOnly() && (di2.diffcode.isSideSecondOnly() ||
423 di2.diffcode.isSideBoth()))
425 if (di1.diffcode.isSideSecondOnly() && (di2.diffcode.isSideFirstOnly() ||
426 di2.diffcode.isSideBoth()))
428 if (di1.diffcode.isSideBoth() && (di2.diffcode.isSideFirstOnly() ||
429 di2.diffcode.isSideSecondOnly()))
432 case SELECTIONTYPE_LEFT1LEFT2:
433 if (di1.diffcode.exists(0) && di2.diffcode.exists(0))
436 case SELECTIONTYPE_RIGHT1RIGHT2:
437 if (di1.diffcode.exists(1) && di2.diffcode.exists(1))
440 case SELECTIONTYPE_LEFT1RIGHT2:
441 if (di1.diffcode.exists(0) && di2.diffcode.exists(1))
444 case SELECTIONTYPE_LEFT2RIGHT1:
445 if (di1.diffcode.exists(1) && di2.diffcode.exists(0))
450 // Allow to compare items if left & right path refer to same directory
451 // (which means there is effectively two files involved). No need to check
452 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
453 if (strutils::compare_nocase(sLeftBasePath, sRightBasePath) == 0)
458 /// is it possible to compare these three items?
459 bool AreItemsOpenable(const CDiffContext& ctxt, const DIFFITEM &di1, const DIFFITEM &di2, const DIFFITEM &di3, bool openableForDir /*= true*/)
461 if (ctxt.GetCompareDirs() < 3)
464 String sLeftBasePath = ctxt.GetPath(0);
465 String sMiddleBasePath = ctxt.GetPath(1);
466 String sRightBasePath = ctxt.GetPath(2);
468 // Must be both directory or neither
469 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory() || di1.diffcode.isDirectory() != di3.diffcode.isDirectory()) return false;
471 if (!openableForDir && di1.diffcode.isDirectory()) return false;
473 // Must be on different sides, or one on one side & one on both
474 if (di1.diffcode.isSideFirstOnly() && di2.diffcode.isSideSecondOnly() && di3.diffcode.isSideThirdOnly())
476 if (di1.diffcode.isSideFirstOnly() && di2.diffcode.isSideThirdOnly() && di3.diffcode.isSideSecondOnly())
478 if (di1.diffcode.isSideSecondOnly() && di2.diffcode.isSideFirstOnly() && di3.diffcode.isSideThirdOnly())
480 if (di1.diffcode.isSideSecondOnly() && di2.diffcode.isSideThirdOnly() && di3.diffcode.isSideFirstOnly())
482 if (di1.diffcode.isSideThirdOnly() && di2.diffcode.isSideFirstOnly() && di3.diffcode.isSideSecondOnly())
484 if (di1.diffcode.isSideThirdOnly() && di2.diffcode.isSideSecondOnly() && di3.diffcode.isSideFirstOnly())
487 // Allow to compare items if left & right path refer to same directory
488 // (which means there is effectively two files involved). No need to check
489 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
490 if (strutils::compare_nocase(sLeftBasePath, sMiddleBasePath) == 0 && strutils::compare_nocase(sLeftBasePath, sRightBasePath) == 0)
496 /// is it possible to open item ?
497 bool IsItemOpenableOn(const DIFFITEM &di, int index)
499 // impossible if not existing
500 if (!di.diffcode.exists(index)) return false;
502 // everything else can be opened on right
506 /// is it possible to open left ... item ?
507 bool IsItemOpenableOnWith(const DIFFITEM &di, int index)
509 return (!di.diffcode.isDirectory() && IsItemOpenableOn(di, index));
511 /// is it possible to copy to... left item?
512 bool IsItemCopyableToOn(const DIFFITEM &di, int index)
514 // impossible if only on right
515 if (!di.diffcode.exists(index)) return false;
517 // everything else can be copied to from left
521 // When navigating differences, do we stop at this one ?
522 bool IsItemNavigableDiff(const CDiffContext& ctxt, const DIFFITEM &di)
524 // Not a valid diffitem, one of special items (e.g "..")
525 if (di.diffcode.diffcode == 0)
527 if (di.diffcode.isResultFiltered() || di.diffcode.isResultError())
529 if (!di.diffcode.isResultDiff() && IsItemExistAll(ctxt, di))
534 bool IsItemExistAll(const CDiffContext& ctxt, const DIFFITEM &di)
536 // Not a valid diffitem, one of special items (e.g "..")
537 if (di.diffcode.diffcode == 0)
539 return di.diffcode.existAll();
544 * @brief Determines if the user wants to see given item.
545 * This function determines what items to show and what items to hide. There
546 * are lots of combinations, but basically we check if menuitem is enabled or
547 * disabled and show/hide matching items. For non-recursive compare we never
548 * hide folders as that would disable user browsing into them. And we even
549 * don't really know if folders are identical or different as we haven't
551 * @param [in] di Item to check.
552 * @return true if item should be shown, false if not.
553 * @sa CDirDoc::Redisplay()
555 bool IsShowable(const CDiffContext& ctxt, const DIFFITEM &di, const DirViewFilterSettings& filter)
557 if (di.customFlags & ViewCustomFlags::HIDDEN)
560 if (di.diffcode.isResultFiltered())
562 // Treat SKIPPED as a 'super'-flag. If item is skipped and user
563 // wants to see skipped items show item regardless of other flags
564 return filter.show_skipped;
567 if (di.diffcode.isDirectory())
569 // Subfolders in non-recursive compare can only be skipped or unique
570 if (!ctxt.m_bRecursive)
572 // left/right filters
573 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
575 if (ctxt.GetCompareDirs() < 3)
577 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
582 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
584 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
586 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
588 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
590 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
595 if (di.diffcode.isResultError() && false/* !GetMainFrame()->m_bShowErrors FIXME:*/)
598 else // recursive mode (including tree-mode)
600 // left/right filters
601 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
603 if (ctxt.GetCompareDirs() < 3)
605 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
610 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
612 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
614 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
616 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
618 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
622 // ONLY filter folders by result (identical/different) for tree-view.
623 // In the tree-view we show subfolders with identical/different
624 // status. The flat view only shows files inside folders. So if we
625 // filter by status the files inside folder are filtered too and
626 // users see files appearing/disappearing without clear logic.
627 if (filter.tree_mode)
630 if (di.diffcode.isResultError() && false/* !GetMainFrame()->m_bShowErrors FIXME:*/)
634 if (di.diffcode.isResultSame() && !filter.show_identical)
636 bool bShowable = true;
637 if (ctxt.GetCompareDirs() < 3)
639 if (di.diffcode.isResultDiff() && !filter.show_different)
644 if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF1STONLY)
646 if (!filter.show_different_left_only)
649 else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF2NDONLY)
651 if (!filter.show_different_middle_only)
654 else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF3RDONLY)
656 if (!filter.show_different_right_only)
659 else if (di.diffcode.isResultDiff() && !filter.show_different)
664 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(&di);
665 while (diffpos != nullptr)
667 const DIFFITEM &dic = ctxt.GetNextSiblingDiffPosition(diffpos);
668 if (IsShowable(ctxt, dic, filter))
678 // left/right filters
679 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
681 if (ctxt.GetCompareDirs() < 3)
683 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
688 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
690 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
692 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
694 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
696 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
701 if (di.diffcode.isBin() && !filter.show_binaries)
705 if (di.diffcode.isResultSame() && !filter.show_identical)
707 if (di.diffcode.isResultError() && false/* && !GetMainFrame()->m_bShowErrors FIXME:*/)
709 if (ctxt.GetCompareDirs() < 3)
711 if (di.diffcode.isResultDiff() && !filter.show_different)
716 if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF1STONLY)
718 if (!filter.show_different_left_only)
721 else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF2NDONLY)
723 if (!filter.show_different_middle_only)
726 else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF3RDONLY)
728 if (!filter.show_different_right_only)
731 else if (di.diffcode.isResultDiff() && !filter.show_different)
739 * @brief Open one selected item.
740 * @param [in] pos1 Item position.
741 * @param [in,out] di1 Pointer to first diffitem.
742 * @param [in,out] di2 Pointer to second diffitem.
743 * @param [in,out] di3 Pointer to third diffitem.
744 * @param [out] paths First/Second/Third paths.
745 * @param [out] sel1 Item's selection index in listview.
746 * @param [in,out] isDir Is item folder?
747 * @param [in] openableForDir Are items openable if the items are directories?
748 * return false if there was error or item was completely processed.
750 bool GetOpenOneItem(const CDiffContext& ctxt, DIFFITEM *pos1, const DIFFITEM *pdi[3],
751 PathContext & paths, int & sel1, bool & isdir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
753 pdi[0] = &ctxt.GetDiffAt(pos1);
757 if (!openableForDir && pdi[0]->diffcode.isDirectory()) return false;
759 paths = GetItemFileNames(ctxt, *pdi[0]);
760 encoding[0] = pdi[0]->diffFileInfo[0].encoding;
761 encoding[1] = pdi[0]->diffFileInfo[1].encoding;
762 encoding[2] = pdi[0]->diffFileInfo[2].encoding;
764 for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
765 nPane[nIndex] = nIndex;
767 if (pdi[0]->diffcode.isDirectory())
770 if (isdir && (pdi[0]->diffcode.existsFirst() && pdi[1]->diffcode.existsSecond() && pdi[2]->diffcode.existsThird()))
772 // Check both folders exist. If either folder is missing that means
773 // folder has been changed behind our back, so we just tell user to
774 // refresh the compare.
775 paths::PATH_EXISTENCE path1Exists = paths::DoesPathExist(paths[0]);
776 paths::PATH_EXISTENCE path2Exists = paths::DoesPathExist(paths[1]);
777 if (path1Exists != paths::IS_EXISTING_DIR || path2Exists != paths::IS_EXISTING_DIR)
779 String invalid = path1Exists == paths::IS_EXISTING_DIR ? paths[0] : paths[1];
780 errmsg = strutils::format_string1(
781 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
791 * @brief Open two selected items.
792 * @param [in] pos1 First item position.
793 * @param [in] pos2 Second item position.
794 * @param [in,out] di1 Pointer to first diffitem.
795 * @param [in,out] di2 Pointer to second diffitem.
796 * @param [out] paths First/Second/Third paths.
797 * @param [out] sel1 First item's selection index in listview.
798 * @param [out] sel2 Second item's selection index in listview.
799 * @param [in,out] isDir Is item folder?
800 * @param [in] openableForDir Are items openable if the items are directories?
801 * return false if there was error or item was completely processed.
803 bool GetOpenTwoItems(const CDiffContext& ctxt, SELECTIONTYPE selectionType, DIFFITEM *pos1, DIFFITEM *pos2, const DIFFITEM *pdi[3],
804 PathContext & paths, int & sel1, int & sel2, bool & isDir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
806 // Two items selected, get their info
807 pdi[0] = &ctxt.GetDiffAt(pos1);
808 pdi[1] = &ctxt.GetDiffAt(pos2);
812 // Check for binary & side compatibility & file/dir compatibility
813 if (!AreItemsOpenable(ctxt, selectionType, *pdi[0], *pdi[1], openableForDir))
818 switch (selectionType)
820 case SELECTIONTYPE_NORMAL:
821 // Ensure that di1 is on left (swap if needed)
822 if (pdi[0]->diffcode.isSideSecondOnly() || (pdi[0]->diffcode.isSideBoth() &&
823 pdi[1]->diffcode.isSideFirstOnly()))
825 std::swap(pdi[0], pdi[1]);
826 std::swap(sel1, sel2);
829 case SELECTIONTYPE_LEFT1LEFT2:
830 nPane[0] = nPane[1] = 0;
832 case SELECTIONTYPE_RIGHT1RIGHT2:
833 nPane[0] = nPane[1] = 1;
835 case SELECTIONTYPE_LEFT1RIGHT2:
837 case SELECTIONTYPE_LEFT2RIGHT1:
838 std::swap(pdi[0], pdi[1]);
839 std::swap(sel1, sel2);
843 PathContext files1, files2;
844 files1 = GetItemFileNames(ctxt, *pdi[0]);
845 files2 = GetItemFileNames(ctxt, *pdi[1]);
846 paths.SetLeft(files1[nPane[0]]);
847 paths.SetRight(files2[nPane[1]]);
848 encoding[0] = pdi[0]->diffFileInfo[nPane[0]].encoding;
849 encoding[1] = pdi[1]->diffFileInfo[nPane[1]].encoding;
851 if (pdi[0]->diffcode.isDirectory())
854 if (paths::GetPairComparability(paths) != paths::IS_EXISTING_DIR)
856 errmsg = _("The selected folder is invalid.");
865 * @brief Open three selected items.
866 * @param [in] pos1 First item position.
867 * @param [in] pos2 Second item position.
868 * @param [in] pos3 Third item position.
869 * @param [in,out] di1 Pointer to first diffitem.
870 * @param [in,out] di2 Pointer to second diffitem.
871 * @param [in,out] di3 Pointer to third diffitem.
872 * @param [out] paths First/Second/Third paths.
873 * @param [out] sel1 First item's selection index in listview.
874 * @param [out] sel2 Second item's selection index in listview.
875 * @param [out] sel3 Third item's selection index in listview.
876 * @param [in,out] isDir Is item folder?
877 * @param [in] openableForDir Are items openable if the items are directories?
878 * return false if there was error or item was completely processed.
880 bool GetOpenThreeItems(const CDiffContext& ctxt, DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3, const DIFFITEM *pdi[3],
881 PathContext & paths, int & sel1, int & sel2, int & sel3, bool & isDir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
883 assert(pos1 && pos2 && pos3 && ctxt.GetCompareDirs() > 2);
885 String pathLeft, pathMiddle, pathRight;
887 for (int nIndex = 0; nIndex < 3; ++nIndex)
888 nPane[nIndex] = nIndex;
890 // Three items selected, get their info
891 pdi[0] = &ctxt.GetDiffAt(pos1);
892 pdi[1] = &ctxt.GetDiffAt(pos2);
893 pdi[2] = &ctxt.GetDiffAt(pos3);
895 // Check for binary & side compatibility & file/dir compatibility
896 if (!::AreItemsOpenable(ctxt, *pdi[0], *pdi[1], *pdi[2], openableForDir))
900 // Ensure that pdi[0] is on left (swap if needed)
901 if (pdi[0]->diffcode.exists(0) && pdi[1]->diffcode.exists(1) && pdi[2]->diffcode.exists(2))
904 else if (pdi[0]->diffcode.exists(0) && pdi[1]->diffcode.exists(2) && pdi[2]->diffcode.exists(1))
906 std::swap(pdi[1], pdi[2]);
907 std::swap(nPane[1], nPane[2]);
908 std::swap(sel2, sel3);
910 else if (pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(0) && pdi[2]->diffcode.exists(2))
912 std::swap(pdi[0], pdi[1]);
913 std::swap(nPane[0], nPane[1]);
914 std::swap(sel1, sel2);
916 else if (pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(2) && pdi[2]->diffcode.exists(0))
918 std::swap(pdi[0], pdi[2]);
919 std::swap(nPane[0], nPane[2]);
920 std::swap(sel1, sel3);
921 std::swap(pdi[1], pdi[2]);
922 std::swap(nPane[1], nPane[2]);
923 std::swap(sel2, sel3);
925 else if (pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(0) && pdi[2]->diffcode.exists(1))
927 std::swap(pdi[0], pdi[1]);
928 std::swap(nPane[0], nPane[1]);
929 std::swap(sel1, sel2);
930 std::swap(pdi[1], pdi[2]);
931 std::swap(nPane[1], nPane[2]);
932 std::swap(sel2, sel3);
934 else if (pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(1) && pdi[2]->diffcode.exists(0))
936 std::swap(pdi[0], pdi[2]);
937 std::swap(nPane[0], nPane[2]);
938 std::swap(sel1, sel3);
941 // Fill in pathLeft & & pathMiddle & pathRight
942 PathContext pathsTemp = GetItemFileNames(ctxt, *pdi[0]);
943 pathLeft = pathsTemp[0];
944 pathsTemp = GetItemFileNames(ctxt, *pdi[1]);
945 pathMiddle = pathsTemp[1];
946 pathsTemp = GetItemFileNames(ctxt, *pdi[2]);
947 pathRight = pathsTemp[2];
949 paths.SetLeft(pathLeft);
950 paths.SetMiddle(pathMiddle);
951 paths.SetRight(pathRight);
953 encoding[0] = pdi[0]->diffFileInfo[0].encoding;
954 encoding[1] = pdi[1]->diffFileInfo[1].encoding;
955 encoding[2] = pdi[2]->diffFileInfo[2].encoding;
957 if (pdi[0]->diffcode.isDirectory())
960 if (paths::GetPairComparability(paths) != paths::IS_EXISTING_DIR)
962 errmsg = _("The selected folder is invalid.");
971 * @brief Get the file names on both sides for specified item.
972 * @note Return empty strings if item is special item.
974 void GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM &di, String& strLeft, String& strRight)
976 const String leftrelpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
977 const String rightrelpath = paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename);
978 const String & leftpath = ctxt.GetPath(0);
979 const String & rightpath = ctxt.GetPath(1);
980 strLeft = paths::ConcatPath(leftpath, leftrelpath);
981 strRight = paths::ConcatPath(rightpath, rightrelpath);
984 String GetItemFileName(const CDiffContext& ctxt, const DIFFITEM &di, int index)
986 return paths::ConcatPath(ctxt.GetPath(index), paths::ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename));
989 PathContext GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM &di)
992 for (int nIndex = 0; nIndex < ctxt.GetCompareDirs(); nIndex++)
994 const String relpath = paths::ConcatPath(di.diffFileInfo[nIndex].path, di.diffFileInfo[nIndex].filename);
995 const String & path = ctxt.GetPath(nIndex);
996 paths.SetPath(nIndex, paths::ConcatPath(path, relpath));
1002 * @brief Return image index appropriate for this row
1004 int GetColImage(const DIFFITEM &di)
1006 // Must return an image index into image list created above in OnInitDialog
1007 if (di.diffcode.isResultError())
1008 return DIFFIMG_ERROR;
1009 if (di.diffcode.isResultAbort())
1010 return DIFFIMG_ABORT;
1011 if (di.diffcode.isResultFiltered())
1012 return (di.diffcode.isDirectory() ? DIFFIMG_DIRSKIP : DIFFIMG_SKIP);
1013 if (di.diffcode.isSideFirstOnly())
1014 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
1015 if (di.diffcode.isSideSecondOnly())
1016 return ((di.diffcode.diffcode & DIFFCODE::THREEWAY) == 0 ?
1017 (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
1018 (di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
1019 if (di.diffcode.isSideThirdOnly())
1020 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
1021 if ((di.diffcode.diffcode & DIFFCODE::THREEWAY) != 0)
1023 if (!di.diffcode.exists(0))
1024 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
1025 if (!di.diffcode.exists(1))
1026 return (di.diffcode.isDirectory() ? DIFFIMG_MDIRMISSING : DIFFIMG_MMISSING);
1027 if (!di.diffcode.exists(2))
1028 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRMISSING : DIFFIMG_RMISSING);
1030 if (di.diffcode.isResultSame())
1032 if (di.diffcode.isDirectory())
1033 return DIFFIMG_DIRSAME;
1036 if (di.diffcode.isText())
1037 return DIFFIMG_TEXTSAME;
1038 else if (di.diffcode.isBin())
1039 return DIFFIMG_BINSAME;
1040 else if (di.diffcode.isImage())
1041 return DIFFIMG_IMAGESAME;
1043 return DIFFIMG_SAME;
1047 if (di.diffcode.isResultDiff())
1049 if (di.diffcode.isDirectory())
1050 return DIFFIMG_DIRDIFF;
1053 if (di.diffcode.isText())
1054 return DIFFIMG_TEXTDIFF;
1055 else if (di.diffcode.isBin())
1056 return DIFFIMG_BINDIFF;
1057 else if (di.diffcode.isImage())
1058 return DIFFIMG_IMAGEDIFF;
1060 return DIFFIMG_DIFF;
1063 return (di.diffcode.isDirectory() ? DIFFIMG_DIR : DIFFIMG_FILE );
1067 * @brief Copy side status of diffitem
1068 * @note This does not update UI - ReloadItemStatus() does
1069 * @sa CDirDoc::ReloadItemStatus()
1071 void CopyDiffSideAndProperties(DIFFITEM& di, int src, int dst)
1073 if (di.diffcode.exists(src))
1075 di.diffcode.diffcode |= (DIFFCODE::FIRST << dst);
1076 // copy file properties other than ctime
1077 di.diffFileInfo[dst].encoding = di.diffFileInfo[src].encoding;
1078 di.diffFileInfo[dst].m_textStats = di.diffFileInfo[src].m_textStats;
1079 di.diffFileInfo[dst].version = di.diffFileInfo[src].version;
1080 di.diffFileInfo[dst].size = di.diffFileInfo[src].size;
1081 di.diffFileInfo[dst].mtime = di.diffFileInfo[src].mtime;
1082 di.diffFileInfo[dst].flags = di.diffFileInfo[src].flags;
1084 if (di.HasChildren())
1086 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1087 CopyDiffSideAndProperties(*pdic, src, dst);
1092 * @brief Unset side status of diffitem
1093 * @note This does not update UI - ReloadItemStatus() does
1094 * @sa CDirDoc::ReloadItemStatus()
1096 void UnsetDiffSide(DIFFITEM& di, int index)
1098 di.diffcode.diffcode &= ~(DIFFCODE::FIRST << index);
1099 di.diffFileInfo[index].ClearPartial();
1100 di.nidiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
1101 di.nsdiffs = CDiffContext::DIFFS_UNKNOWN_QUICKCOMPARE;
1102 if (di.HasChildren())
1104 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1105 UnsetDiffSide(*pdic, index);
1110 * @brief Set compare status of diffitem
1111 * @note This does not update UI - ReloadItemStatus() does
1112 * @sa CDirDoc::ReloadItemStatus()
1114 void SetDiffCompare(DIFFITEM& di, unsigned diffcode)
1116 SetDiffStatus(di, diffcode, DIFFCODE::COMPAREFLAGS);
1120 * @brief Set status for diffitem
1121 * @param diffcode New code
1122 * @param mask Defines allowed set of flags to change
1123 * @param idx Item's index to list in UI
1125 void SetDiffStatus(DIFFITEM& di, unsigned diffcode, unsigned mask)
1127 // TODO: Why is the update broken into these pieces ?
1128 // Someone could figure out these pieces and probably simplify this.
1130 // Update DIFFITEM code (comparison result)
1131 assert( ((~mask) & diffcode) == 0 ); // make sure they only set flags in their mask
1133 di.diffcode.diffcode &= (~mask); // remove current data
1134 di.diffcode.diffcode |= diffcode; // add new data
1135 if (di.HasChildren())
1137 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1138 SetDiffStatus(*pdic, diffcode, mask);
1140 // update DIFFITEM time (and other disk info), and tell views
1143 void UpdateStatusFromDisk(CDiffContext& ctxt, DIFFITEM& di, int index)
1145 ctxt.UpdateStatusFromDisk(&di, index);
1146 if (di.HasChildren())
1148 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1149 UpdateStatusFromDisk(ctxt, *pdic, index);
1154 * @brief Update compare flags recursively after sync.
1155 * @param [in,out] di Item to update the compare flag.
1156 * @param [in] bRecursive `true` if tree mode is on
1157 * @return number of diff items
1159 int UpdateCompareFlagsAfterSync(DIFFITEM& di, bool bRecursive)
1161 // Do not update compare flags for filtered items.
1162 if (di.diffcode.isResultFiltered())
1166 if (di.HasChildren())
1168 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1170 int ndiff = UpdateCompareFlagsAfterSync(*pdic, bRecursive);
1177 // Update compare flags for items that exist on both sides.
1178 // (Do not update compare flags for items that exist on only one side.)
1179 if (di.diffcode.existAll())
1181 di.diffcode.diffcode &= (~DIFFCODE::COMPAREFLAGS);
1182 unsigned flag = (res > 0) ? DIFFCODE::DIFF : DIFFCODE::SAME;
1183 di.diffcode.diffcode |= flag;
1187 // Update compare flags for files and directories in tree mode.
1188 // (Do not update directory compare flags when not in tree mode.)
1189 if (!di.diffcode.isDirectory() || bRecursive)
1191 if (di.diffcode.existAll())
1193 di.diffcode.diffcode &= (~DIFFCODE::COMPAREFLAGS);
1194 di.diffcode.diffcode |= DIFFCODE::SAME;
1207 * @brief Update the paths of the diff items recursively.
1208 * @param[in] nDirs Number of directories to compare.
1209 * @param[in,out] di
\81@Item to update the path.
1211 void UpdatePaths(int nDirs, DIFFITEM& di)
1213 assert(nDirs == 2 || nDirs == 3);
1215 if (di.HasChildren())
1217 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1219 for (int i = 0; i < nDirs; i++)
1220 pdic->diffFileInfo[i].path = paths::ConcatPath(di.diffFileInfo[i].path, di.diffFileInfo[i].filename);
1221 UpdatePaths(nDirs, *pdic);
1226 void SetDiffCounts(DIFFITEM& di, unsigned diffs, unsigned ignored)
1228 di.nidiffs = ignored; // see StoreDiffResult() in DirScan.cpp
1233 * @brief Set item's view-flag.
1234 * @param [in] key Item fow which flag is set.
1235 * @param [in] flag Flag value to set.
1236 * @param [in] mask Mask for possible flag values.
1238 void SetItemViewFlag(DIFFITEM& di, unsigned flag, unsigned mask)
1240 unsigned curFlags = di.customFlags;
1241 curFlags &= ~mask; // Zero bits masked
1243 di.customFlags = curFlags;
1247 * @brief Set all item's view-flag.
1248 * @param [in] flag Flag value to set.
1249 * @param [in] mask Mask for possible flag values.
1251 void SetItemViewFlag(CDiffContext& ctxt, unsigned flag, unsigned mask)
1253 DIFFITEM *pos = ctxt.GetFirstDiffPosition();
1255 while (pos != nullptr)
1257 unsigned curFlags = ctxt.GetCustomFlags1(pos);
1258 curFlags &= ~mask; // Zero bits masked
1260 ctxt.SetCustomFlags1(pos, curFlags);
1261 ctxt.GetNextDiffPosition(pos);
1266 * @brief Mark selected items as needing for rescan.
1267 * @return Count of items to rescan.
1269 void MarkForRescan(DIFFITEM &di)
1271 SetDiffStatus(di, 0, DIFFCODE::TEXTFLAGS | DIFFCODE::SIDEFLAGS | DIFFCODE::COMPAREFLAGS);
1272 SetDiffStatus(di, DIFFCODE::NEEDSCAN, DIFFCODE::SCANFLAGS);
1276 * @brief Return string such as "15 of 30 Files Affected" or "30 Files Affected"
1278 String FormatFilesAffectedString(int nFilesAffected, int nFilesTotal)
1280 if (nFilesAffected == nFilesTotal)
1281 return strutils::format_string1(_("(%1 Files Affected)"), NumToStr(nFilesTotal));
1283 return strutils::format_string2(_("(%1 of %2 Files Affected)"), NumToStr(nFilesAffected), NumToStr(nFilesTotal));
1286 String FormatMenuItemString(const String& fmt1, const String& fmt2, int count, int total)
1289 return strutils::format_string1(fmt1, NumToStr(total));
1291 return strutils::format_string2(fmt2, NumToStr(count), NumToStr(total));
1294 String FormatMenuItemString(SIDE_TYPE src, SIDE_TYPE dst, int count, int total)
1297 if (src == SIDE_LEFT && dst == SIDE_RIGHT)
1299 fmt1 = _("Left to Right (%1)");
1300 fmt2 = _("Left to Right (%1 of %2)");
1302 else if (src == SIDE_LEFT && dst == SIDE_MIDDLE)
1304 fmt1 = _("Left to Middle (%1)");
1305 fmt2 = _("Left to Middle (%1 of %2)");
1307 else if (src == SIDE_MIDDLE && dst == SIDE_LEFT)
1309 fmt1 = _("Middle to Left (%1)");
1310 fmt2 = _("Middle to Left (%1 of %2)");
1312 else if (src == SIDE_MIDDLE && dst == SIDE_RIGHT)
1314 fmt1 = _("Middle to Right (%1)");
1315 fmt2 = _("Middle to Right (%1 of %2)");
1317 else if (src == SIDE_RIGHT && dst == SIDE_LEFT)
1319 fmt1 = _("Right to Left (%1)");
1320 fmt2 = _("Right to Left (%1 of %2)");
1322 else if (src == SIDE_RIGHT && dst == SIDE_MIDDLE)
1324 fmt1 = _("Right to Middle (%1)");
1325 fmt2 = _("Right to Middle (%1 of %2)");
1327 return FormatMenuItemString(fmt1, fmt2, count, total);
1330 String FormatMenuItemString(SIDE_TYPE src, int count, int total)
1333 if (src == SIDE_LEFT)
1335 fmt1 = _("Left (%1)");
1336 fmt2 = _("Left (%1 of %2)");
1338 else if (src == SIDE_MIDDLE)
1340 fmt1 = _("Middle (%1)");
1341 fmt2 = _("Middle (%1 of %2)");
1343 else if (src == SIDE_RIGHT)
1345 fmt1 = _("Right (%1)");
1346 fmt2 = _("Right (%1 of %2)");
1348 return FormatMenuItemString(fmt1, fmt2, count, total);
1351 String FormatMenuItemStringAll(int nDirs, int count, int total)
1354 return FormatMenuItemString(_("Both (%1)"), _("Both (%1 of %2)"), count, total);
1356 return FormatMenuItemString(_("All (%1)"), _("All (%1 of %2)"), count, total);
1359 String FormatMenuItemStringTo(SIDE_TYPE src, int count, int total)
1362 if (src == SIDE_LEFT)
1364 fmt1 = _("Left to... (%1)");
1365 fmt2 = _("Left to... (%1 of %2)");
1367 else if (src == SIDE_MIDDLE)
1369 fmt1 = _("Middle to... (%1)");
1370 fmt2 = _("Middle to... (%1 of %2)");
1372 else if (src == SIDE_RIGHT)
1374 fmt1 = _("Right to... (%1)");
1375 fmt2 = _("Right to... (%1 of %2)");
1377 return FormatMenuItemString(fmt1, fmt2, count, total);
1380 String FormatMenuItemStringAllTo(int nDirs, int count, int total)
1383 return FormatMenuItemString(_("Both to... (%1)"), _("Both to... (%1 of %2)"), count, total);
1385 return FormatMenuItemString(_("All to... (%1)"), _("All to... (%1 of %2)"), count, total);
1388 String FormatMenuItemStringDifferencesTo(int count, int total)
1390 return FormatMenuItemString(_("Differences to... (%1)"), _("Differences to... (%1 of %2)"), count, total);
1394 * @brief Rename a file without moving it to different directory.
1396 * @param szOldFileName [in] Full path of file to rename.
1397 * @param szNewFileName [in] New file name (without the path).
1399 * @return true if file was renamed successfully.
1401 bool RenameOnSameDir(const String& szOldFileName, const String& szNewFileName)
1403 bool bSuccess = false;
1405 if (paths::DOES_NOT_EXIST != paths::DoesPathExist(szOldFileName))
1407 String sFullName = paths::ConcatPath(paths::GetPathOnly(szOldFileName), szNewFileName);
1409 // No need to rename if new file already exist.
1410 if ((sFullName != szOldFileName) ||
1411 (paths::DOES_NOT_EXIST == paths::DoesPathExist(sFullName)))
1413 ShellFileOperations fileOp;
1414 fileOp.SetOperation(FO_RENAME, 0);
1415 fileOp.AddSourceAndDestination(szOldFileName, sFullName);
1416 bSuccess = fileOp.Run();
1428 * @brief Convert number to string.
1429 * Converts number to string, with commas between digits in
1430 * locale-appropriate manner.
1432 String NumToStr(int n)
1434 return locality::NumToLocaleStr(n);
1437 void ExpandSubdirs(CDiffContext& ctxt, DIFFITEM& dip)
1439 dip.customFlags |= ViewCustomFlags::EXPANDED;
1440 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(&dip);
1441 while (diffpos != nullptr)
1443 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1444 if (!di.IsAncestor(&dip))
1446 if (di.HasChildren())
1447 di.customFlags |= ViewCustomFlags::EXPANDED;
1451 void ExpandAllSubdirs(CDiffContext& ctxt)
1453 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1454 while (diffpos != nullptr)
1456 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1457 if (di.diffcode.isDirectory())
1458 di.customFlags |= ViewCustomFlags::EXPANDED;
1462 void ExpandDifferentSubdirs(CDiffContext& ctxt)
1464 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1465 while (diffpos != nullptr)
1467 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1468 if (di.diffcode.isDirectory() && (di.diffcode.isResultDiff() || !di.diffcode.existAll()))
1469 di.customFlags |= ViewCustomFlags::EXPANDED;
1473 void ExpandIdenticalSubdirs(CDiffContext& ctxt)
1475 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1476 while (diffpos != nullptr)
1478 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1479 if (di.diffcode.isDirectory() && di.diffcode.isResultSame())
1480 di.customFlags |= ViewCustomFlags::EXPANDED;
1484 void CollapseAllSubdirs(CDiffContext& ctxt)
1486 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1487 while (diffpos != nullptr)
1489 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1490 di.customFlags &= ~ViewCustomFlags::EXPANDED;
1494 DirViewTreeState *SaveTreeState(const CDiffContext& ctxt)
1496 DirViewTreeState *pTreeState = new DirViewTreeState();
1497 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1498 while (diffpos != nullptr)
1500 const DIFFITEM &di = ctxt.GetNextDiffPosition(diffpos);
1501 if (di.HasChildren())
1503 String relpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1504 pTreeState->insert(std::pair<String, bool>(relpath, !!(di.customFlags & ViewCustomFlags::EXPANDED)));
1510 void RestoreTreeState(CDiffContext& ctxt, DirViewTreeState *pTreeState)
1512 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1513 while (diffpos != nullptr)
1515 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1516 if (di.HasChildren())
1518 String relpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1519 std::map<String, bool>::iterator p = pTreeState->find(relpath);
1520 if (p != pTreeState->end())
1522 di.customFlags &= ~ViewCustomFlags::EXPANDED;
1523 di.customFlags |= (p->second ? ViewCustomFlags::EXPANDED : 0);
1530 * @brief Tell if user may use ".." and move to parents directory.
1531 * This function checks if current compare's parent folders should
1532 * be allowed to open. I.e. if current compare folders are:
1533 * - C:\Work\Project1 and
1534 * - C:\Work\Project2
1535 * we check if C:\Work and C:\Work should be allowed to opened.
1536 * For regular folders we allow opening if both folders exist.
1537 * @param [out] leftParent Left parent folder to open.
1538 * @param [out] rightParent Right parent folder to open.
1539 * @return Info if opening parent folders should be enabled:
1540 * - No : upward RESTRICTED
1541 * - ParentIsRegularPath : upward ENABLED
1542 * - ParentIsTempPath : upward ENABLED
1544 AllowUpwardDirectory::ReturnCode
1545 CheckAllowUpwardDirectory(const CDiffContext& ctxt, const CTempPathContext *pTempPathContext, PathContext &pathsParent)
1547 std::vector<String> path(ctxt.GetCompareDirs());
1548 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1549 path[i] = ctxt.GetNormalizedPath(i);
1551 // If we have temp context it means we are comparing archives
1552 if (pTempPathContext != nullptr)
1554 std::vector<String> name(path.size());
1555 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1556 name[i] = paths::FindFileName(path[i]);
1558 String::size_type cchLeftRoot = pTempPathContext->m_strRoot[0].length();
1559 if (path[0].length() <= cchLeftRoot)
1561 pathsParent.SetSize(ctxt.GetCompareDirs());
1562 if (pTempPathContext->m_pParent)
1564 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1565 pathsParent[i] = pTempPathContext->m_pParent->m_strRoot[i];
1566 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1567 return AllowUpwardDirectory::Never;
1568 return AllowUpwardDirectory::ParentIsTempPath;
1570 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1571 pathsParent[i] = pTempPathContext->m_strDisplayRoot[i];
1572 if (pathsParent.GetSize() < 3)
1574 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1]))
1575 return AllowUpwardDirectory::Never;
1579 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1], pathsParent[2]))
1580 return AllowUpwardDirectory::Never;
1582 if (path.size() == 2 &&
1583 strutils::compare_nocase(name[0], _T("ORIGINAL")) == 0 &&
1584 strutils::compare_nocase(name[1], _T("ALTERED")) == 0)
1586 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1587 pathsParent[i] = paths::GetParentPath(pathsParent[i]);
1588 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1589 name[i] = paths::FindFileName(pathsParent[i]);
1590 if (strutils::compare_nocase(name[0], name[1]) == 0)
1592 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1593 return AllowUpwardDirectory::Never;
1594 return AllowUpwardDirectory::ParentIsTempPath;
1597 return AllowUpwardDirectory::No;
1601 // If regular parent folders exist, allow opening them
1602 pathsParent.SetSize(ctxt.GetCompareDirs());
1603 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1604 pathsParent[i] = paths::GetParentPath(path[i]);
1605 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1606 return AllowUpwardDirectory::Never;
1607 return AllowUpwardDirectory::ParentIsRegularPath;