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, int count,
26 const String& src, const String& dest, bool destIsSide);
27 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, int count,
28 const String& src, const String& dest, bool destIsSide);
29 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
30 int origin, int destination, size_t count,
31 const String& src, const String& dest, bool destIsSide);
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 = sOrig;
147 exp.m_toText = sDest;
148 exp.m_fromPath = strSrc;
149 exp.m_toPath = 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:
207 if (actionList.GetActionItemCount() == 1)
209 ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
210 actionList.GetActionItemCount(), item.src, item.dest,
215 String src = ctxt.GetPath(item.UIOrigin);;
218 if (!actionList.m_destBase.empty())
219 dst = actionList.m_destBase;
223 ThrowConfirmMove(ctxt, item.UIOrigin, item.UIDestination,
224 actionList.GetActionItemCount(), src, dst, bDestIsSide);
230 LogErrorString(_T("Unknown fileoperation in CDirView::ConfirmActionList()"));
231 throw "Unknown fileoperation in ConfirmActionList()";
237 * @brief Update results for FileActionItem.
238 * This functions is called to update DIFFITEM after FileActionItem.
239 * @param [in] act Action that was done.
240 * @param [in] pos List position for DIFFITEM affected.
242 UPDATEITEM_TYPE UpdateDiffAfterOperation(const FileActionItem & act, CDiffContext& ctxt, DIFFITEM &di)
244 bool bUpdateSrc = false;
245 bool bUpdateDest = false;
246 bool bRemoveItem = false;
248 // Use FileActionItem types for simplicity for now.
249 // Better would be to use FileAction contained, since it is not
251 switch (act.UIResult)
253 case FileActionItem::UI_SYNC:
256 CopyDiffSide(di, act.UIOrigin, act.UIDestination);
257 if (ctxt.GetCompareDirs() > 2)
258 SetDiffCompare(di, DIFFCODE::NOCMP);
260 SetDiffCompare(di, DIFFCODE::SAME);
261 SetDiffCounts(di, 0, 0);
264 case FileActionItem::UI_DEL:
265 if (di.diffcode.isSideOnly(act.UIOrigin))
271 UnsetDiffSide(di, act.UIOrigin);
272 SetDiffCompare(di, DIFFCODE::NOCMP);
279 UpdateStatusFromDisk(ctxt, di, act.UIOrigin);
281 UpdateStatusFromDisk(ctxt, di, act.UIDestination);
284 return UPDATEITEM_REMOVE;
285 if (bUpdateSrc | bUpdateDest)
286 return UPDATEITEM_UPDATE;
287 return UPDATEITEM_NONE;
291 * @brief Find the CDiffContext diffpos of an item from its left & right paths
292 * @return POSITION to item, `nullptr` if not found.
293 * @note Filenames must be same, if they differ `nullptr` is returned.
295 DIFFITEM *FindItemFromPaths(const CDiffContext& ctxt, const PathContext& paths)
298 String file[3], path[3], base;
299 for (nBuffer = 0; nBuffer < paths.GetSize(); ++nBuffer)
301 String p = paths[nBuffer];
302 file[nBuffer] = paths::FindFileName(p);
303 if (file[nBuffer].empty())
305 // Path can contain (because of difftools?) '/' and '\'
306 // so for comparing purposes, convert whole path to use '\\'
307 path[nBuffer] = paths::ToWindowsPath(String(p, 0, p.length() - file[nBuffer].length())); // include trailing backslash
308 base = ctxt.GetPath(nBuffer); // include trailing backslash
309 if (path[nBuffer].compare(0, base.length(), base.c_str()) != 0)
311 path[nBuffer].erase(0, base.length()); // turn into relative path
312 if (String::size_type length = path[nBuffer].length())
313 path[nBuffer].resize(length - 1); // remove trailing backslash
316 // Filenames must be identical
317 if (std::any_of(file, file + paths.GetSize(), [&](auto& it) { return strutils::compare_nocase(it, file[0]) != 0; }))
320 DIFFITEM *pos = ctxt.GetFirstDiffPosition();
321 if (paths.GetSize() == 2)
323 while (DIFFITEM *currentPos = pos) // Save our current pos before getting next
325 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
326 if (di.diffFileInfo[0].path == path[0] &&
327 di.diffFileInfo[1].path == path[1] &&
328 di.diffFileInfo[0].filename == file[0] &&
329 di.diffFileInfo[1].filename == file[1])
337 while (DIFFITEM *currentPos = pos) // Save our current pos before getting next
339 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
340 if (di.diffFileInfo[0].path == path[0] &&
341 di.diffFileInfo[1].path == path[1] &&
342 di.diffFileInfo[2].path == path[2] &&
343 di.diffFileInfo[0].filename == file[0] &&
344 di.diffFileInfo[1].filename == file[1] &&
345 di.diffFileInfo[2].filename == file[2])
354 /// is it possible to copy item to left ?
355 bool IsItemCopyable(const DIFFITEM &di, int index)
357 // don't let them mess with error items
358 if (di.diffcode.isResultError()) return false;
359 // can't copy same items
360 if (di.diffcode.isResultSame()) return false;
361 // impossible if not existing
362 if (!di.diffcode.exists(index)) return false;
363 // everything else can be copied to other side
367 /// is it possible to delete item ?
368 bool IsItemDeletable(const DIFFITEM &di, int index)
370 // don't let them mess with error items
371 if (di.diffcode.isResultError()) return false;
372 // impossible if not existing
373 if (!di.diffcode.exists(index)) return false;
374 // everything else can be deleted
378 /// is it possible to delete both items ?
379 bool IsItemDeletableOnBoth(const CDiffContext& ctxt, const DIFFITEM &di)
381 // don't let them mess with error items
382 if (di.diffcode.isResultError()) return false;
383 // impossible if only on right or left
384 for (int i = 0; i < ctxt.GetCompareDirs(); ++i)
385 if (!di.diffcode.exists(i)) return false;
387 // everything else can be deleted on both
391 /// is it possible to compare these two items?
392 bool AreItemsOpenable(const CDiffContext& ctxt, SELECTIONTYPE selectionType, const DIFFITEM &di1, const DIFFITEM &di2, bool openableForDir /*= true*/)
394 String sLeftBasePath = ctxt.GetPath(0);
395 String sRightBasePath = ctxt.GetPath(1);
397 // Must be both directory or neither
398 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory()) return false;
400 if (!openableForDir && di1.diffcode.isDirectory()) return false;
402 switch (selectionType)
404 case SELECTIONTYPE_NORMAL:
405 // Must be on different sides, or one on one side & one on both
406 if (di1.diffcode.isSideFirstOnly() && (di2.diffcode.isSideSecondOnly() ||
407 di2.diffcode.isSideBoth()))
409 if (di1.diffcode.isSideSecondOnly() && (di2.diffcode.isSideFirstOnly() ||
410 di2.diffcode.isSideBoth()))
412 if (di1.diffcode.isSideBoth() && (di2.diffcode.isSideFirstOnly() ||
413 di2.diffcode.isSideSecondOnly()))
416 case SELECTIONTYPE_LEFT1LEFT2:
417 if (di1.diffcode.exists(0) && di2.diffcode.exists(0))
420 case SELECTIONTYPE_RIGHT1RIGHT2:
421 if (di1.diffcode.exists(1) && di2.diffcode.exists(1))
424 case SELECTIONTYPE_LEFT1RIGHT2:
425 if (di1.diffcode.exists(0) && di2.diffcode.exists(1))
428 case SELECTIONTYPE_LEFT2RIGHT1:
429 if (di1.diffcode.exists(1) && di2.diffcode.exists(0))
434 // Allow to compare items if left & right path refer to same directory
435 // (which means there is effectively two files involved). No need to check
436 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
437 if (strutils::compare_nocase(sLeftBasePath, sRightBasePath) == 0)
442 /// is it possible to compare these three items?
443 bool AreItemsOpenable(const CDiffContext& ctxt, const DIFFITEM &di1, const DIFFITEM &di2, const DIFFITEM &di3, bool openableForDir /*= true*/)
445 String sLeftBasePath = ctxt.GetPath(0);
446 String sMiddleBasePath = ctxt.GetPath(1);
447 String sRightBasePath = ctxt.GetPath(2);
448 String sLeftPath1 = paths::ConcatPath(di1.getFilepath(0, sLeftBasePath), di1.diffFileInfo[0].filename);
449 String sLeftPath2 = paths::ConcatPath(di2.getFilepath(0, sLeftBasePath), di2.diffFileInfo[0].filename);
450 String sLeftPath3 = paths::ConcatPath(di3.getFilepath(0, sLeftBasePath), di3.diffFileInfo[0].filename);
451 String sMiddlePath1 = paths::ConcatPath(di1.getFilepath(1, sMiddleBasePath), di1.diffFileInfo[1].filename);
452 String sMiddlePath2 = paths::ConcatPath(di2.getFilepath(1, sMiddleBasePath), di2.diffFileInfo[1].filename);
453 String sMiddlePath3 = paths::ConcatPath(di3.getFilepath(1, sMiddleBasePath), di3.diffFileInfo[1].filename);
454 String sRightPath1 = paths::ConcatPath(di1.getFilepath(2, sRightBasePath), di1.diffFileInfo[2].filename);
455 String sRightPath2 = paths::ConcatPath(di2.getFilepath(2, sRightBasePath), di2.diffFileInfo[2].filename);
456 String sRightPath3 = paths::ConcatPath(di3.getFilepath(2, sRightBasePath), di3.diffFileInfo[2].filename);
457 // Must not be binary (unless archive)
460 (di1.diffcode.isBin() || di2.diffcode.isBin() || di3.diffcode.isBin())
463 && (sLeftPath1.empty() || ArchiveGuessFormat(sLeftPath1))
464 && (sMiddlePath1.empty() || ArchiveGuessFormat(sMiddlePath1))
465 && (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
466 && (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2))
467 && (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
468 && (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2)) /* FIXME: */
475 // Must be both directory or neither
476 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory() && di1.diffcode.isDirectory() != di3.diffcode.isDirectory()) return false;
478 if (!openableForDir && di1.diffcode.isDirectory()) return false;
480 // Must be on different sides, or one on one side & one on both
481 if (di1.diffcode.exists(0) && di2.diffcode.exists(1) && di3.diffcode.exists(2))
483 if (di1.diffcode.exists(0) && di2.diffcode.exists(2) && di3.diffcode.exists(1))
485 if (di1.diffcode.exists(1) && di2.diffcode.exists(0) && di3.diffcode.exists(2))
487 if (di1.diffcode.exists(1) && di2.diffcode.exists(2) && di3.diffcode.exists(0))
489 if (di1.diffcode.exists(2) && di2.diffcode.exists(0) && di3.diffcode.exists(1))
491 if (di1.diffcode.exists(2) && di2.diffcode.exists(1) && di3.diffcode.exists(0))
494 // Allow to compare items if left & right path refer to same directory
495 // (which means there is effectively two files involved). No need to check
496 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
497 if (strutils::compare_nocase(sLeftBasePath, sMiddleBasePath) == 0 && strutils::compare_nocase(sLeftBasePath, sRightBasePath) == 0)
502 /// is it possible to open item ?
503 bool IsItemOpenableOn(const DIFFITEM &di, int index)
505 // impossible if not existing
506 if (!di.diffcode.exists(index)) return false;
508 // everything else can be opened on right
512 /// is it possible to open left ... item ?
513 bool IsItemOpenableOnWith(const DIFFITEM &di, int index)
515 return (!di.diffcode.isDirectory() && IsItemOpenableOn(di, index));
517 /// is it possible to copy to... left item?
518 bool IsItemCopyableToOn(const DIFFITEM &di, int index)
520 // impossible if only on right
521 if (!di.diffcode.exists(index)) return false;
523 // everything else can be copied to from left
527 // When navigating differences, do we stop at this one ?
528 bool IsItemNavigableDiff(const CDiffContext& ctxt, const DIFFITEM &di)
530 // Not a valid diffitem, one of special items (e.g "..")
531 if (di.diffcode.diffcode == 0)
533 if (di.diffcode.isResultFiltered() || di.diffcode.isResultError())
535 if (!di.diffcode.isResultDiff() && IsItemExistAll(ctxt, di))
540 bool IsItemExistAll(const CDiffContext& ctxt, const DIFFITEM &di)
542 // Not a valid diffitem, one of special items (e.g "..")
543 if (di.diffcode.diffcode == 0)
545 return di.diffcode.existAll();
550 * @brief Determines if the user wants to see given item.
551 * This function determines what items to show and what items to hide. There
552 * are lots of combinations, but basically we check if menuitem is enabled or
553 * disabled and show/hide matching items. For non-recursive compare we never
554 * hide folders as that would disable user browsing into them. And we even
555 * don't really know if folders are identical or different as we haven't
557 * @param [in] di Item to check.
558 * @return true if item should be shown, false if not.
559 * @sa CDirDoc::Redisplay()
561 bool IsShowable(const CDiffContext& ctxt, const DIFFITEM &di, const DirViewFilterSettings& filter)
563 if (di.customFlags & ViewCustomFlags::HIDDEN)
566 if (di.diffcode.isResultFiltered())
568 // Treat SKIPPED as a 'super'-flag. If item is skipped and user
569 // wants to see skipped items show item regardless of other flags
570 return filter.show_skipped;
573 if (di.diffcode.isDirectory())
575 // Subfolders in non-recursive compare can only be skipped or unique
576 if (!ctxt.m_bRecursive)
578 // left/right filters
579 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
581 if (ctxt.GetCompareDirs() < 3)
583 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
588 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
590 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
592 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
594 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
596 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
601 if (di.diffcode.isResultError() && false/* !GetMainFrame()->m_bShowErrors FIXME:*/)
604 else // recursive mode (including tree-mode)
606 // left/right filters
607 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
609 if (ctxt.GetCompareDirs() < 3)
611 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
616 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
618 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
620 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
622 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
624 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
628 // ONLY filter folders by result (identical/different) for tree-view.
629 // In the tree-view we show subfolders with identical/different
630 // status. The flat view only shows files inside folders. So if we
631 // filter by status the files inside folder are filtered too and
632 // users see files appearing/disappearing without clear logic.
633 if (filter.tree_mode)
636 if (di.diffcode.isResultError() && false/* !GetMainFrame()->m_bShowErrors FIXME:*/)
640 if (di.diffcode.isResultSame() && !filter.show_identical)
642 if (di.diffcode.isResultDiff() && !filter.show_different)
644 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(&di);
645 while (diffpos != nullptr)
647 const DIFFITEM &dic = ctxt.GetNextSiblingDiffPosition(diffpos);
648 if (IsShowable(ctxt, dic, filter))
658 // left/right filters
659 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
661 if (ctxt.GetCompareDirs() < 3)
663 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
668 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_middle)
670 if (di.diffcode.isSideThirdOnly() && !filter.show_unique_right)
672 if (di.diffcode.isMissingFirstOnly() && !filter.show_missing_left_only)
674 if (di.diffcode.isMissingSecondOnly() && !filter.show_missing_middle_only)
676 if (di.diffcode.isMissingThirdOnly() && !filter.show_missing_right_only)
681 if (di.diffcode.isBin() && !filter.show_binaries)
685 if (di.diffcode.isResultSame() && !filter.show_identical)
687 if (di.diffcode.isResultError() && false/* && !GetMainFrame()->m_bShowErrors FIXME:*/)
689 if (ctxt.GetCompareDirs() < 3)
691 if (di.diffcode.isResultDiff() && !filter.show_different)
696 if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF1STONLY)
698 if (!filter.show_different_left_only)
701 else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF2NDONLY)
703 if (!filter.show_different_middle_only)
706 else if ((di.diffcode.diffcode & DIFFCODE::COMPAREFLAGS3WAY) == DIFFCODE::DIFF3RDONLY)
708 if (!filter.show_different_right_only)
711 else if (di.diffcode.isResultDiff() && !filter.show_different)
719 * @brief Open one selected item.
720 * @param [in] pos1 Item position.
721 * @param [in,out] di1 Pointer to first diffitem.
722 * @param [in,out] di2 Pointer to second diffitem.
723 * @param [in,out] di3 Pointer to third diffitem.
724 * @param [out] paths First/Second/Third paths.
725 * @param [out] sel1 Item's selection index in listview.
726 * @param [in,out] isDir Is item folder?
727 * @param [in] openableForDir Are items openable if the items are directories?
728 * return false if there was error or item was completely processed.
730 bool GetOpenOneItem(const CDiffContext& ctxt, DIFFITEM *pos1, const DIFFITEM *pdi[3],
731 PathContext & paths, int & sel1, bool & isdir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
733 pdi[0] = &ctxt.GetDiffAt(pos1);
737 if (!openableForDir && pdi[0]->diffcode.isDirectory()) return false;
739 paths = GetItemFileNames(ctxt, *pdi[0]);
740 encoding[0] = pdi[0]->diffFileInfo[0].encoding;
741 encoding[1] = pdi[0]->diffFileInfo[1].encoding;
742 encoding[2] = pdi[0]->diffFileInfo[2].encoding;
744 for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
745 nPane[nIndex] = nIndex;
747 if (pdi[0]->diffcode.isDirectory())
750 if (isdir && (pdi[0]->diffcode.existsFirst() && pdi[1]->diffcode.existsSecond() && pdi[2]->diffcode.existsThird()))
752 // Check both folders exist. If either folder is missing that means
753 // folder has been changed behind our back, so we just tell user to
754 // refresh the compare.
755 paths::PATH_EXISTENCE path1Exists = paths::DoesPathExist(paths[0]);
756 paths::PATH_EXISTENCE path2Exists = paths::DoesPathExist(paths[1]);
757 if (path1Exists != paths::IS_EXISTING_DIR || path2Exists != paths::IS_EXISTING_DIR)
759 String invalid = path1Exists == paths::IS_EXISTING_DIR ? paths[0] : paths[1];
760 errmsg = strutils::format_string1(
761 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
771 * @brief Open two selected items.
772 * @param [in] pos1 First item position.
773 * @param [in] pos2 Second item position.
774 * @param [in,out] di1 Pointer to first diffitem.
775 * @param [in,out] di2 Pointer to second diffitem.
776 * @param [out] paths First/Second/Third paths.
777 * @param [out] sel1 First item's selection index in listview.
778 * @param [out] sel2 Second item's selection index in listview.
779 * @param [in,out] isDir Is item folder?
780 * @param [in] openableForDir Are items openable if the items are directories?
781 * return false if there was error or item was completely processed.
783 bool GetOpenTwoItems(const CDiffContext& ctxt, SELECTIONTYPE selectionType, DIFFITEM *pos1, DIFFITEM *pos2, const DIFFITEM *pdi[3],
784 PathContext & paths, int & sel1, int & sel2, bool & isDir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
786 // Two items selected, get their info
787 pdi[0] = &ctxt.GetDiffAt(pos1);
788 pdi[1] = &ctxt.GetDiffAt(pos2);
792 // Check for binary & side compatibility & file/dir compatibility
793 if (!AreItemsOpenable(ctxt, selectionType, *pdi[0], *pdi[1], openableForDir))
798 switch (selectionType)
800 case SELECTIONTYPE_NORMAL:
801 // Ensure that di1 is on left (swap if needed)
802 if (pdi[0]->diffcode.isSideSecondOnly() || (pdi[0]->diffcode.isSideBoth() &&
803 pdi[1]->diffcode.isSideFirstOnly()))
805 std::swap(pdi[0], pdi[1]);
806 std::swap(sel1, sel2);
809 case SELECTIONTYPE_LEFT1LEFT2:
810 nPane[0] = nPane[1] = 0;
812 case SELECTIONTYPE_RIGHT1RIGHT2:
813 nPane[0] = nPane[1] = 1;
815 case SELECTIONTYPE_LEFT1RIGHT2:
817 case SELECTIONTYPE_LEFT2RIGHT1:
818 std::swap(pdi[0], pdi[1]);
819 std::swap(sel1, sel2);
823 PathContext files1, files2;
824 files1 = GetItemFileNames(ctxt, *pdi[0]);
825 files2 = GetItemFileNames(ctxt, *pdi[1]);
826 paths.SetLeft(files1[nPane[0]]);
827 paths.SetRight(files2[nPane[1]]);
828 encoding[0] = pdi[0]->diffFileInfo[nPane[0]].encoding;
829 encoding[1] = pdi[1]->diffFileInfo[nPane[1]].encoding;
831 if (pdi[0]->diffcode.isDirectory())
834 if (paths::GetPairComparability(paths) != paths::IS_EXISTING_DIR)
836 errmsg = _("The selected folder is invalid.");
845 * @brief Open three selected items.
846 * @param [in] pos1 First item position.
847 * @param [in] pos2 Second item position.
848 * @param [in] pos3 Third item position.
849 * @param [in,out] di1 Pointer to first diffitem.
850 * @param [in,out] di2 Pointer to second diffitem.
851 * @param [in,out] di3 Pointer to third diffitem.
852 * @param [out] paths First/Second/Third paths.
853 * @param [out] sel1 First item's selection index in listview.
854 * @param [out] sel2 Second item's selection index in listview.
855 * @param [out] sel3 Third item's selection index in listview.
856 * @param [in,out] isDir Is item folder?
857 * @param [in] openableForDir Are items openable if the items are directories?
858 * return false if there was error or item was completely processed.
860 bool GetOpenThreeItems(const CDiffContext& ctxt, DIFFITEM *pos1, DIFFITEM *pos2, DIFFITEM *pos3, const DIFFITEM *pdi[3],
861 PathContext & paths, int & sel1, int & sel2, int & sel3, bool & isDir, int nPane[3], FileTextEncoding encoding[3], String& errmsg, bool openableForDir /*= true*/)
863 String pathLeft, pathMiddle, pathRight;
866 for (int nIndex = 0; nIndex < 3; ++nIndex)
867 nPane[nIndex] = nIndex;
870 // Two items selected, get their info
871 pdi[0] = &ctxt.GetDiffAt(pos1);
872 pdi[1] = &ctxt.GetDiffAt(pos2);
874 // Check for binary & side compatibility & file/dir compatibility
875 if (!::AreItemsOpenable(ctxt, *pdi[0], *pdi[1], *pdi[1], openableForDir) &&
876 !::AreItemsOpenable(ctxt, *pdi[0], *pdi[0], *pdi[1], openableForDir))
880 // Ensure that pdi[0] is on left (swap if needed)
881 if (pdi[0]->diffcode.exists(0) && pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(2))
888 else if (pdi[0]->diffcode.exists(0) && pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(1))
893 else if (pdi[0]->diffcode.exists(1) && pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(0))
895 std::swap(pdi[0], pdi[1]);
896 std::swap(sel1, sel2);
900 else if (pdi[1]->diffcode.exists(0) && pdi[1]->diffcode.exists(1) && pdi[0]->diffcode.exists(2))
902 std::swap(pdi[0], pdi[1]);
903 std::swap(sel1, sel2);
909 else if (pdi[1]->diffcode.exists(0) && pdi[1]->diffcode.exists(2) && pdi[0]->diffcode.exists(1))
911 std::swap(pdi[0], pdi[1]);
912 std::swap(sel1, sel2);
916 else if (pdi[1]->diffcode.exists(1) && pdi[1]->diffcode.exists(2) && pdi[0]->diffcode.exists(0))
924 // Three items selected, get their info
925 pdi[0] = &ctxt.GetDiffAt(pos1);
926 pdi[1] = &ctxt.GetDiffAt(pos2);
927 pdi[2] = &ctxt.GetDiffAt(pos3);
929 // Check for binary & side compatibility & file/dir compatibility
930 if (!::AreItemsOpenable(ctxt, *pdi[0], *pdi[1], *pdi[2], openableForDir))
934 // Ensure that pdi[0] is on left (swap if needed)
935 if (pdi[0]->diffcode.exists(0) && pdi[1]->diffcode.exists(1) && pdi[2]->diffcode.exists(2))
938 else if (pdi[0]->diffcode.exists(0) && pdi[1]->diffcode.exists(2) && pdi[2]->diffcode.exists(1))
940 std::swap(pdi[1], pdi[2]);
941 std::swap(sel2, sel3);
943 else if (pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(0) && pdi[2]->diffcode.exists(2))
945 std::swap(pdi[0], pdi[1]);
946 std::swap(sel1, sel2);
948 else if (pdi[0]->diffcode.exists(1) && pdi[1]->diffcode.exists(2) && pdi[2]->diffcode.exists(0))
950 std::swap(pdi[0], pdi[2]);
951 std::swap(sel1, sel3);
952 std::swap(pdi[1], pdi[2]);
953 std::swap(sel2, sel3);
955 else if (pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(0) && pdi[2]->diffcode.exists(1))
957 std::swap(pdi[0], pdi[1]);
958 std::swap(sel1, sel2);
959 std::swap(pdi[1], pdi[2]);
960 std::swap(sel2, sel3);
962 else if (pdi[0]->diffcode.exists(2) && pdi[1]->diffcode.exists(1) && pdi[2]->diffcode.exists(0))
964 std::swap(pdi[0], pdi[2]);
965 std::swap(sel1, sel3);
969 // Fill in pathLeft & & pathMiddle & pathRight
970 PathContext pathsTemp = GetItemFileNames(ctxt, *pdi[0]);
971 pathLeft = pathsTemp[0];
972 pathsTemp = GetItemFileNames(ctxt, *pdi[1]);
973 pathMiddle = pathsTemp[1];
974 pathsTemp = GetItemFileNames(ctxt, *pdi[2]);
975 pathRight = pathsTemp[2];
977 paths.SetLeft(pathLeft);
978 paths.SetMiddle(pathMiddle);
979 paths.SetRight(pathRight);
981 encoding[0] = pdi[0]->diffFileInfo[0].encoding;
982 encoding[1] = pdi[1]->diffFileInfo[1].encoding;
983 encoding[2] = pdi[2]->diffFileInfo[2].encoding;
985 if (pdi[0]->diffcode.isDirectory())
988 if (paths::GetPairComparability(paths) != paths::IS_EXISTING_DIR)
990 errmsg = _("The selected folder is invalid.");
999 * @brief Get the file names on both sides for specified item.
1000 * @note Return empty strings if item is special item.
1002 void GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM &di, String& strLeft, String& strRight)
1004 const String leftrelpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1005 const String rightrelpath = paths::ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename);
1006 const String & leftpath = ctxt.GetPath(0);
1007 const String & rightpath = ctxt.GetPath(1);
1008 strLeft = paths::ConcatPath(leftpath, leftrelpath);
1009 strRight = paths::ConcatPath(rightpath, rightrelpath);
1012 String GetItemFileName(const CDiffContext& ctxt, const DIFFITEM &di, int index)
1014 return paths::ConcatPath(ctxt.GetPath(index), paths::ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename));
1017 PathContext GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM &di)
1020 for (int nIndex = 0; nIndex < ctxt.GetCompareDirs(); nIndex++)
1022 const String relpath = paths::ConcatPath(di.diffFileInfo[nIndex].path, di.diffFileInfo[nIndex].filename);
1023 const String & path = ctxt.GetPath(nIndex);
1024 paths.SetPath(nIndex, paths::ConcatPath(path, relpath));
1030 * @brief Return image index appropriate for this row
1032 int GetColImage(const DIFFITEM &di)
1034 // Must return an image index into image list created above in OnInitDialog
1035 if (di.diffcode.isResultError())
1036 return DIFFIMG_ERROR;
1037 if (di.diffcode.isResultAbort())
1038 return DIFFIMG_ABORT;
1039 if (di.diffcode.isResultFiltered())
1040 return (di.diffcode.isDirectory() ? DIFFIMG_DIRSKIP : DIFFIMG_SKIP);
1041 if (di.diffcode.isSideFirstOnly())
1042 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
1043 if (di.diffcode.isSideSecondOnly())
1044 return ((di.diffcode.diffcode & DIFFCODE::THREEWAY) == 0 ?
1045 (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
1046 (di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
1047 if (di.diffcode.isSideThirdOnly())
1048 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
1049 if ((di.diffcode.diffcode & DIFFCODE::THREEWAY) != 0)
1051 if (!di.diffcode.exists(0))
1052 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
1053 if (!di.diffcode.exists(1))
1054 return (di.diffcode.isDirectory() ? DIFFIMG_MDIRMISSING : DIFFIMG_MMISSING);
1055 if (!di.diffcode.exists(2))
1056 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRMISSING : DIFFIMG_RMISSING);
1058 if (di.diffcode.isResultSame())
1060 if (di.diffcode.isDirectory())
1061 return DIFFIMG_DIRSAME;
1064 if (di.diffcode.isText())
1065 return DIFFIMG_TEXTSAME;
1066 else if (di.diffcode.isBin())
1067 return DIFFIMG_BINSAME;
1068 else if (di.diffcode.isImage())
1069 return DIFFIMG_IMAGESAME;
1071 return DIFFIMG_SAME;
1075 if (di.diffcode.isResultDiff())
1077 if (di.diffcode.isDirectory())
1078 return DIFFIMG_DIRDIFF;
1081 if (di.diffcode.isText())
1082 return DIFFIMG_TEXTDIFF;
1083 else if (di.diffcode.isBin())
1084 return DIFFIMG_BINDIFF;
1085 else if (di.diffcode.isImage())
1086 return DIFFIMG_IMAGEDIFF;
1088 return DIFFIMG_DIFF;
1091 return (di.diffcode.isDirectory() ? DIFFIMG_DIR : DIFFIMG_FILE );
1095 * @brief Copy side status of diffitem
1096 * @note This does not update UI - ReloadItemStatus() does
1097 * @sa CDirDoc::ReloadItemStatus()
1099 void CopyDiffSide(DIFFITEM& di, int src, int dst)
1101 if (di.diffcode.exists(src))
1102 di.diffcode.diffcode |= (DIFFCODE::FIRST << dst);
1103 if (di.HasChildren())
1105 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1106 CopyDiffSide(*pdic, src, dst);
1111 * @brief Unset side status of diffitem
1112 * @note This does not update UI - ReloadItemStatus() does
1113 * @sa CDirDoc::ReloadItemStatus()
1115 void UnsetDiffSide(DIFFITEM& di, int index)
1117 di.diffcode.diffcode &= ~(DIFFCODE::FIRST << index);
1118 if (di.HasChildren())
1120 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1121 UnsetDiffSide(*pdic, index);
1126 * @brief Set compare status of diffitem
1127 * @note This does not update UI - ReloadItemStatus() does
1128 * @sa CDirDoc::ReloadItemStatus()
1130 void SetDiffCompare(DIFFITEM& di, unsigned diffcode)
1132 SetDiffStatus(di, diffcode, DIFFCODE::COMPAREFLAGS);
1136 * @brief Set status for diffitem
1137 * @param diffcode New code
1138 * @param mask Defines allowed set of flags to change
1139 * @param idx Item's index to list in UI
1141 void SetDiffStatus(DIFFITEM& di, unsigned diffcode, unsigned mask)
1143 // TODO: Why is the update broken into these pieces ?
1144 // Someone could figure out these pieces and probably simplify this.
1146 // Update DIFFITEM code (comparison result)
1147 assert( ((~mask) & diffcode) == 0 ); // make sure they only set flags in their mask
1149 di.diffcode.diffcode &= (~mask); // remove current data
1150 di.diffcode.diffcode |= diffcode; // add new data
1151 if (di.HasChildren())
1153 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1154 SetDiffStatus(*pdic, diffcode, mask);
1156 // update DIFFITEM time (and other disk info), and tell views
1159 void UpdateStatusFromDisk(CDiffContext& ctxt, DIFFITEM& di, int index)
1161 ctxt.UpdateStatusFromDisk(&di, index);
1162 if (di.HasChildren())
1164 for (DIFFITEM* pdic = di.GetFirstChild(); pdic; pdic = pdic->GetFwdSiblingLink())
1165 UpdateStatusFromDisk(ctxt, *pdic, index);
1169 void SetDiffCounts(DIFFITEM& di, unsigned diffs, unsigned ignored)
1171 di.nidiffs = ignored; // see StoreDiffResult() in DirScan.cpp
1176 * @brief Set item's view-flag.
1177 * @param [in] key Item fow which flag is set.
1178 * @param [in] flag Flag value to set.
1179 * @param [in] mask Mask for possible flag values.
1181 void SetItemViewFlag(DIFFITEM& di, unsigned flag, unsigned mask)
1183 unsigned curFlags = di.customFlags;
1184 curFlags &= ~mask; // Zero bits masked
1186 di.customFlags = curFlags;
1190 * @brief Set all item's view-flag.
1191 * @param [in] flag Flag value to set.
1192 * @param [in] mask Mask for possible flag values.
1194 void SetItemViewFlag(CDiffContext& ctxt, unsigned flag, unsigned mask)
1196 DIFFITEM *pos = ctxt.GetFirstDiffPosition();
1198 while (pos != nullptr)
1200 unsigned curFlags = ctxt.GetCustomFlags1(pos);
1201 curFlags &= ~mask; // Zero bits masked
1203 ctxt.SetCustomFlags1(pos, curFlags);
1204 ctxt.GetNextDiffPosition(pos);
1209 * @brief Mark selected items as needing for rescan.
1210 * @return Count of items to rescan.
1212 void MarkForRescan(DIFFITEM &di)
1214 SetDiffStatus(di, 0, DIFFCODE::TEXTFLAGS | DIFFCODE::SIDEFLAGS | DIFFCODE::COMPAREFLAGS);
1215 SetDiffStatus(di, DIFFCODE::NEEDSCAN, DIFFCODE::SCANFLAGS);
1219 * @brief Return string such as "15 of 30 Files Affected" or "30 Files Affected"
1221 String FormatFilesAffectedString(int nFilesAffected, int nFilesTotal)
1223 if (nFilesAffected == nFilesTotal)
1224 return strutils::format_string1(_("(%1 Files Affected)"), NumToStr(nFilesTotal));
1226 return strutils::format_string2(_("(%1 of %2 Files Affected)"), NumToStr(nFilesAffected), NumToStr(nFilesTotal));
1229 String FormatMenuItemString(const String& fmt1, const String& fmt2, int count, int total)
1232 return strutils::format_string1(fmt1, NumToStr(total));
1234 return strutils::format_string2(fmt2, NumToStr(count), NumToStr(total));
1237 String FormatMenuItemString(SIDE_TYPE src, SIDE_TYPE dst, int count, int total)
1240 if (src == SIDE_LEFT && dst == SIDE_RIGHT)
1242 fmt1 = _("Left to Right (%1)");
1243 fmt2 = _("Left to Right (%1 of %2)");
1245 else if (src == SIDE_LEFT && dst == SIDE_MIDDLE)
1247 fmt1 = _("Left to Middle (%1)");
1248 fmt2 = _("Left to Middle (%1 of %2)");
1250 else if (src == SIDE_MIDDLE && dst == SIDE_LEFT)
1252 fmt1 = _("Middle to Left (%1)");
1253 fmt2 = _("Middle to Left (%1 of %2)");
1255 else if (src == SIDE_MIDDLE && dst == SIDE_RIGHT)
1257 fmt1 = _("Middle to Right (%1)");
1258 fmt2 = _("Middle to Right (%1 of %2)");
1260 else if (src == SIDE_RIGHT && dst == SIDE_LEFT)
1262 fmt1 = _("Right to Left (%1)");
1263 fmt2 = _("Right to Left (%1 of %2)");
1265 else if (src == SIDE_RIGHT && dst == SIDE_MIDDLE)
1267 fmt1 = _("Right to Middle (%1)");
1268 fmt2 = _("Right to Middle (%1 of %2)");
1270 return FormatMenuItemString(fmt1, fmt2, count, total);
1273 String FormatMenuItemString(SIDE_TYPE src, int count, int total)
1276 if (src == SIDE_LEFT)
1278 fmt1 = _("Left (%1)");
1279 fmt2 = _("Left (%1 of %2)");
1281 else if (src == SIDE_MIDDLE)
1283 fmt1 = _("Middle (%1)");
1284 fmt2 = _("Middle (%1 of %2)");
1286 else if (src == SIDE_RIGHT)
1288 fmt1 = _("Right (%1)");
1289 fmt2 = _("Right (%1 of %2)");
1291 return FormatMenuItemString(fmt1, fmt2, count, total);
1294 String FormatMenuItemStringAll(int nDirs, int count, int total)
1297 return FormatMenuItemString(_("Both (%1)"), _("Both (%1 of %2)"), count, total);
1299 return FormatMenuItemString(_("All (%1)"), _("All (%1 of %2)"), count, total);
1302 String FormatMenuItemStringTo(SIDE_TYPE src, int count, int total)
1305 if (src == SIDE_LEFT)
1307 fmt1 = _("Left to... (%1)");
1308 fmt2 = _("Left to... (%1 of %2)");
1310 else if (src == SIDE_MIDDLE)
1312 fmt1 = _("Middle to... (%1)");
1313 fmt2 = _("Middle to... (%1 of %2)");
1315 else if (src == SIDE_RIGHT)
1317 fmt1 = _("Right to... (%1)");
1318 fmt2 = _("Right to... (%1 of %2)");
1320 return FormatMenuItemString(fmt1, fmt2, count, total);
1323 String FormatMenuItemStringAllTo(int nDirs, int count, int total)
1326 return FormatMenuItemString(_("Both to... (%1)"), _("Both to... (%1 of %2)"), count, total);
1328 return FormatMenuItemString(_("All to... (%1)"), _("All to... (%1 of %2)"), count, total);
1331 String FormatMenuItemStringDifferencesTo(int count, int total)
1333 return FormatMenuItemString(_("Differences to... (%1)"), _("Differences to... (%1 of %2)"), count, total);
1337 * @brief Rename a file without moving it to different directory.
1339 * @param szOldFileName [in] Full path of file to rename.
1340 * @param szNewFileName [in] New file name (without the path).
1342 * @return true if file was renamed successfully.
1344 bool RenameOnSameDir(const String& szOldFileName, const String& szNewFileName)
1346 bool bSuccess = false;
1348 if (paths::DOES_NOT_EXIST != paths::DoesPathExist(szOldFileName))
1350 String sFullName = paths::ConcatPath(paths::GetPathOnly(szOldFileName), szNewFileName);
1352 // No need to rename if new file already exist.
1353 if ((sFullName != szOldFileName) ||
1354 (paths::DOES_NOT_EXIST == paths::DoesPathExist(sFullName)))
1356 ShellFileOperations fileOp;
1357 fileOp.SetOperation(FO_RENAME, 0);
1358 fileOp.AddSourceAndDestination(szOldFileName, sFullName);
1359 bSuccess = fileOp.Run();
1371 * @brief Convert number to string.
1372 * Converts number to string, with commas between digits in
1373 * locale-appropriate manner.
1375 String NumToStr(int n)
1377 return locality::NumToLocaleStr(n);
1380 void ExpandSubdirs(CDiffContext& ctxt, DIFFITEM& dip)
1382 dip.customFlags |= ViewCustomFlags::EXPANDED;
1383 DIFFITEM *diffpos = ctxt.GetFirstChildDiffPosition(&dip);
1384 while (diffpos != nullptr)
1386 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1387 if (!di.IsAncestor(&dip))
1389 if (di.HasChildren())
1390 di.customFlags |= ViewCustomFlags::EXPANDED;
1394 void ExpandAllSubdirs(CDiffContext& ctxt)
1396 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1397 while (diffpos != nullptr)
1399 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1400 di.customFlags |= ViewCustomFlags::EXPANDED;
1404 void CollapseAllSubdirs(CDiffContext& ctxt)
1406 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1407 while (diffpos != nullptr)
1409 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1410 di.customFlags &= ~ViewCustomFlags::EXPANDED;
1414 DirViewTreeState *SaveTreeState(const CDiffContext& ctxt)
1416 DirViewTreeState *pTreeState = new DirViewTreeState();
1417 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1418 while (diffpos != nullptr)
1420 const DIFFITEM &di = ctxt.GetNextDiffPosition(diffpos);
1421 if (di.HasChildren())
1423 String relpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1424 pTreeState->insert(std::pair<String, bool>(relpath, !!(di.customFlags & ViewCustomFlags::EXPANDED)));
1430 void RestoreTreeState(CDiffContext& ctxt, DirViewTreeState *pTreeState)
1432 DIFFITEM *diffpos = ctxt.GetFirstDiffPosition();
1433 while (diffpos != nullptr)
1435 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1436 if (di.HasChildren())
1438 String relpath = paths::ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1439 std::map<String, bool>::iterator p = pTreeState->find(relpath);
1440 if (p != pTreeState->end())
1442 di.customFlags &= ~ViewCustomFlags::EXPANDED;
1443 di.customFlags |= (p->second ? ViewCustomFlags::EXPANDED : 0);
1450 * @brief Tell if user may use ".." and move to parents directory.
1451 * This function checks if current compare's parent folders should
1452 * be allowed to open. I.e. if current compare folders are:
1453 * - C:\Work\Project1 and
1454 * - C:\Work\Project2
1455 * we check if C:\Work and C:\Work should be allowed to opened.
1456 * For regular folders we allow opening if both folders exist.
1457 * @param [out] leftParent Left parent folder to open.
1458 * @param [out] rightParent Right parent folder to open.
1459 * @return Info if opening parent folders should be enabled:
1460 * - No : upward RESTRICTED
1461 * - ParentIsRegularPath : upward ENABLED
1462 * - ParentIsTempPath : upward ENABLED
1464 AllowUpwardDirectory::ReturnCode
1465 CheckAllowUpwardDirectory(const CDiffContext& ctxt, const CTempPathContext *pTempPathContext, PathContext &pathsParent)
1467 std::vector<String> path(ctxt.GetCompareDirs());
1468 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1469 path[i] = ctxt.GetNormalizedPath(i);
1471 // If we have temp context it means we are comparing archives
1472 if (pTempPathContext != nullptr)
1474 std::vector<String> name(path.size());
1475 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1476 name[i] = paths::FindFileName(path[i]);
1478 String::size_type cchLeftRoot = pTempPathContext->m_strRoot[0].length();
1479 if (path[0].length() <= cchLeftRoot)
1481 pathsParent.SetSize(ctxt.GetCompareDirs());
1482 if (pTempPathContext->m_pParent)
1484 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1485 pathsParent[i] = pTempPathContext->m_pParent->m_strRoot[i];
1486 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1487 return AllowUpwardDirectory::Never;
1488 return AllowUpwardDirectory::ParentIsTempPath;
1490 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1491 pathsParent[i] = pTempPathContext->m_strDisplayRoot[i];
1492 if (pathsParent.GetSize() < 3)
1494 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1]))
1495 return AllowUpwardDirectory::Never;
1499 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1], pathsParent[2]))
1500 return AllowUpwardDirectory::Never;
1502 if (path.size() == 2 &&
1503 strutils::compare_nocase(name[0], _T("ORIGINAL")) == 0 &&
1504 strutils::compare_nocase(name[1], _T("ALTERED")) == 0)
1506 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1507 pathsParent[i] = paths::GetParentPath(pathsParent[i]);
1508 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1509 name[i] = paths::FindFileName(pathsParent[i]);
1510 if (strutils::compare_nocase(name[0], name[1]) == 0)
1512 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1513 return AllowUpwardDirectory::Never;
1514 return AllowUpwardDirectory::ParentIsTempPath;
1517 return AllowUpwardDirectory::No;
1521 // If regular parent folders exist, allow opening them
1522 pathsParent.SetSize(ctxt.GetCompareDirs());
1523 for (int i = 0; i < static_cast<int>(path.size()); ++i)
1524 pathsParent[i] = paths::GetParentPath(path[i]);
1525 if (paths::GetPairComparability(pathsParent) != paths::IS_EXISTING_DIR)
1526 return AllowUpwardDirectory::Never;
1527 return AllowUpwardDirectory::ParentIsRegularPath;