1 /////////////////////////////////////////////////////////////////////////////
2 // see Merge.cpp for license (GPLv2+) statement
4 /////////////////////////////////////////////////////////////////////////////
8 * @brief Implementation of methods of CDirView that copy/move/delete files
10 // ID line follows -- this is updated by SVN
11 // $Id: DirActions.cpp 6572 2009-03-18 18:51:20Z kimmov $
13 // It would be nice to make this independent of the UI (CDirView)
14 // but it needs access to the list of selected items.
15 // One idea would be to provide an iterator over them.
18 #include "DirActions.h"
20 #include "UnicodeString.h"
22 #include "ShellFileOperations.h"
24 #include "FileActionScript.h"
26 #include "FileFilterHelper.h"
27 #include "coretools.h"
31 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, int count,
32 const String& src, const String& dest, bool destIsSide);
33 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, int count,
34 const String& src, const String& dest, bool destIsSide);
35 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
36 int origin, int destination, size_t count,
37 const String& src, const String& dest, bool destIsSide);
39 ContentsChangedException::ContentsChangedException(const String& failpath)
41 m_msg = string_format_string1(
42 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
47 * @brief Ask user a confirmation for copying item(s).
48 * Shows a confirmation dialog for copy operation. Depending ont item count
49 * dialog shows full paths to items (single item) or base paths of compare
51 * @param [in] origin Origin side of the item(s).
52 * @param [in] destination Destination side of the item(s).
53 * @param [in] count Number of items.
54 * @param [in] src Source path.
55 * @param [in] dest Destination path.
56 * @param [in] destIsSide Is destination path either of compare sides?
57 * @return true if copy should proceed, false if aborted.
59 static void ThrowConfirmCopy(const CDiffContext& ctxt, int origin, int destination, size_t count,
60 const String& src, const String& dest, bool destIsSide)
62 String caption = _("Confirm Copy");
63 String strQuestion = count == 1 ? _("Are you sure you want to copy:") :
64 string_format(_("Are you sure you want to copy %d items:").c_str(), count);
66 ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
67 destination, count, src, dest, destIsSide);
71 * @brief Ask user a confirmation for moving item(s).
72 * Shows a confirmation dialog for move operation. Depending ont item count
73 * dialog shows full paths to items (single item) or base paths of compare
75 * @param [in] origin Origin side of the item(s).
76 * @param [in] destination Destination side of the item(s).
77 * @param [in] count Number of items.
78 * @param [in] src Source path.
79 * @param [in] dest Destination path.
80 * @param [in] destIsSide Is destination path either of compare sides?
81 * @return true if copy should proceed, false if aborted.
83 static void ThrowConfirmMove(const CDiffContext& ctxt, int origin, int destination, size_t count,
84 const String& src, const String& dest, bool destIsSide)
86 String caption = _("Confirm Move");
87 String strQuestion = count == 1 ? _("Are you sure you want to move:") :
88 string_format(_("Are you sure you want to move %d items:").c_str(), count);
90 ThrowConfirmationNeededException(ctxt, caption, strQuestion, origin,
91 destination, count, src, dest, destIsSide);
95 * @brief Show a (copy/move) confirmation dialog.
96 * @param [in] caption Caption of the dialog.
97 * @param [in] question Guestion to ask from user.
98 * @param [in] origin Origin side of the item(s).
99 * @param [in] destination Destination side of the item(s).
100 * @param [in] count Number of items.
101 * @param [in] src Source path.
102 * @param [in] dest Destination path.
103 * @param [in] destIsSide Is destination path either of compare sides?
104 * @return true if copy should proceed, false if aborted.
106 static void ThrowConfirmationNeededException(const CDiffContext& ctxt, const String &caption, const String &question,
107 int origin, int destination, size_t count,
108 const String& src, const String& dest, bool destIsSide)
110 ConfirmationNeededException exp;
114 exp.m_caption = caption.c_str();
117 sOrig = _("From left:");
118 else if (origin == ctxt.GetCompareDirs() - 1)
119 sOrig = _("From right:");
121 sOrig = _("From middle:");
125 // Copy to left / right
126 if (destination == 0)
127 sDest = _("To left:");
128 else if (destination == ctxt.GetCompareDirs() - 1)
129 sDest = _("To right:");
131 sDest = _("To middle:");
135 // Copy left/right to..
140 if (paths_DoesPathExist(src) == IS_EXISTING_DIR)
141 strSrc = paths_AddTrailingSlash(src);
142 String strDest(dest);
143 if (paths_DoesPathExist(dest) == IS_EXISTING_DIR)
144 strDest = paths_AddTrailingSlash(dest);
146 exp.m_question = question;
147 exp.m_fromText = sOrig;
148 exp.m_toText = sDest;
149 exp.m_fromPath = strSrc;
150 exp.m_toPath = strDest;
156 * @brief Confirm actions with user as appropriate
157 * (type, whether single or multiple).
159 void ConfirmActionList(const CDiffContext& ctxt, const FileActionScript & actionList)
161 // TODO: We need better confirmation for file actions.
162 // Maybe we should show a list of files with actions done..
163 FileActionItem item = actionList.GetHeadActionItem();
165 bool bDestIsSide = true;
167 // special handling for the single item case, because it is probably the most common,
168 // and we can give the user exact details easily for it
171 case FileAction::ACT_COPY:
172 if (item.UIResult == FileActionItem::UI_DONT_CARE)
175 if (actionList.GetActionItemCount() == 1)
177 ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
178 actionList.GetActionItemCount(), item.src, item.dest,
183 String src = ctxt.GetPath(item.UIOrigin);
188 dst = ctxt.GetPath(item.UIDestination);
192 if (!actionList.m_destBase.empty())
193 dst = actionList.m_destBase;
198 ThrowConfirmCopy(ctxt, item.UIOrigin, item.UIDestination,
199 actionList.GetActionItemCount(), src, dst, bDestIsSide);
203 case FileAction::ACT_DEL:
206 case FileAction::ACT_MOVE:
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] pos List position for DIFFITEM affected.
243 UPDATEITEM_TYPE UpdateDiffAfterOperation(const FileActionItem & act, CDiffContext& ctxt, DIFFITEM &di)
245 bool bUpdateSrc = false;
246 bool bUpdateDest = false;
247 bool bRemoveItem = false;
249 // Use FileActionItem types for simplicity for now.
250 // Better would be to use FileAction contained, since it is not
252 switch (act.UIResult)
254 case FileActionItem::UI_SYNC:
257 di.diffcode.setSideFlag(act.UIDestination);
259 SetDiffCompare(di, DIFFCODE::NOCMP);
261 SetDiffCompare(di, DIFFCODE::SAME);
262 SetDiffCounts(di, 0, 0);
265 case FileActionItem::UI_DEL:
266 if (di.diffcode.isSideOnly(act.UIOrigin))
268 ctxt.RemoveDiff(reinterpret_cast<UIntPtr>(&di));
273 di.diffcode.unsetSideFlag(act.UIOrigin);
274 SetDiffCompare(di, DIFFCODE::NOCMP);
281 ctxt.UpdateStatusFromDisk(reinterpret_cast<UIntPtr>(&di), act.UIOrigin);
283 ctxt.UpdateStatusFromDisk(reinterpret_cast<UIntPtr>(&di), act.UIDestination);
286 return UPDATEITEM_REMOVE;
287 if (bUpdateSrc | bUpdateDest)
288 return UPDATEITEM_UPDATE;
289 return UPDATEITEM_NONE;
293 * @brief Find the CDiffContext diffpos of an item from its left & right paths
294 * @return POSITION to item, NULL if not found.
295 * @note Filenames must be same, if they differ NULL is returned.
297 UIntPtr FindItemFromPaths(const CDiffContext& ctxt, const String& pathLeft, const String& pathRight)
299 String file1 = paths_FindFileName(pathLeft);
300 String file2 = paths_FindFileName(pathRight);
302 // Filenames must be identical
303 if (string_compare_nocase(file1, file2) != 0)
306 String path1(pathLeft, 0, pathLeft.length() - file1.length()); // include trailing backslash
307 String path2(pathRight, 0, pathRight.length() - file2.length()); // include trailing backslash
309 // Path can contain (because of difftools?) '/' and '\'
310 // so for comparing purposes, convert whole path to use '\\'
311 replace_char(&*path1.begin(), '/', '\\');
312 replace_char(&*path2.begin(), '/', '\\');
314 String base1 = ctxt.GetLeftPath(); // include trailing backslash
315 if (path1.compare(0, base1.length(), base1.c_str()) != 0)
317 path1.erase(0, base1.length()); // turn into relative path
318 if (String::size_type length = path1.length())
319 path1.resize(length - 1); // remove trailing backslash
321 String base2 = ctxt.GetRightPath(); // include trailing backslash
322 if (path2.compare(0, base2.length(), base2.c_str()) != 0)
324 path2.erase(0, base2.length()); // turn into relative path
325 if (String::size_type length = path2.length())
326 path2.resize(length - 1); // remove trailing backslash
328 UIntPtr pos = ctxt.GetFirstDiffPosition();
329 while (UIntPtr currentPos = pos) // Save our current pos before getting next
331 const DIFFITEM &di = ctxt.GetNextDiffPosition(pos);
332 if (di.diffFileInfo[0].path == path1 &&
333 di.diffFileInfo[1].path == path2 &&
334 di.diffFileInfo[0].filename == file1 &&
335 di.diffFileInfo[1].filename == file2)
343 /// is it possible to copy item to left ?
344 bool IsItemCopyable(const DIFFITEM & di, int index)
346 // don't let them mess with error items
347 if (di.diffcode.isResultError()) return false;
348 // can't copy same items
349 if (di.diffcode.isResultSame()) return false;
350 // impossible if not existing
351 if (!di.diffcode.isExists(index)) return false;
352 // everything else can be copied to other side
356 /// is it possible to delete item ?
357 bool IsItemDeletable(const DIFFITEM & di, int index)
359 // don't let them mess with error items
360 if (di.diffcode.isResultError()) return false;
361 // impossible if not existing
362 if (!di.diffcode.isExists(index)) return false;
363 // everything else can be deleted
367 /// is it possible to delete both items ?
368 bool IsItemDeletableOnBoth(const CDiffContext& ctxt, const DIFFITEM & di)
370 // don't let them mess with error items
371 if (di.diffcode.isResultError()) return false;
372 // impossible if only on right or left
373 for (int i = 0; i < ctxt.GetCompareDirs(); ++i)
374 if (!di.diffcode.isExists(i)) return false;
376 // everything else can be deleted on both
381 * @brief Determine if item can be opened.
382 * Basically we only disable opening unique files at the moment.
383 * Unique folders can be opened since we ask for creating matching folder
385 * @param [in] di DIFFITEM for item to check.
386 * @return true if the item can be opened, false otherwise.
388 bool IsItemOpenable(const CDiffContext& ctxt, const DIFFITEM & di, bool treemode)
390 if (treemode && ctxt.m_bRecursive)
392 if (di.diffcode.isDirectory() || !IsItemExistAll(ctxt, di))
397 if (!di.diffcode.isDirectory() && !IsItemExistAll(ctxt, di))
402 /// is it possible to compare these two items?
403 bool AreItemsOpenable(const CDiffContext& ctxt, SELECTIONTYPE selectionType, const DIFFITEM & di1, const DIFFITEM & di2)
405 String sLeftBasePath = ctxt.GetPath(0);
406 String sRightBasePath = ctxt.GetPath(1);
408 // Must be both directory or neither
409 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory()) return false;
411 switch (selectionType)
413 case SELECTIONTYPE_NORMAL:
414 // Must be on different sides, or one on one side & one on both
415 if (di1.diffcode.isSideFirstOnly() && (di2.diffcode.isSideSecondOnly() ||
416 di2.diffcode.isSideBoth()))
418 if (di1.diffcode.isSideSecondOnly() && (di2.diffcode.isSideFirstOnly() ||
419 di2.diffcode.isSideBoth()))
421 if (di1.diffcode.isSideBoth() && (di2.diffcode.isSideFirstOnly() ||
422 di2.diffcode.isSideSecondOnly()))
425 case SELECTIONTYPE_LEFT1LEFT2:
426 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(0))
429 case SELECTIONTYPE_RIGHT1RIGHT2:
430 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(1))
433 case SELECTIONTYPE_LEFT1RIGHT2:
434 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(1))
437 case SELECTIONTYPE_LEFT2RIGHT1:
438 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(0))
443 // Allow to compare items if left & right path refer to same directory
444 // (which means there is effectively two files involved). No need to check
445 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
446 if (string_compare_nocase(sLeftBasePath, sRightBasePath) == 0)
451 /// is it possible to compare these three items?
452 bool AreItemsOpenable(const CDiffContext& ctxt, const DIFFITEM & di1, const DIFFITEM & di2, const DIFFITEM & di3)
454 String sLeftBasePath = ctxt.GetPath(0);
455 String sMiddleBasePath = ctxt.GetPath(1);
456 String sRightBasePath = ctxt.GetPath(2);
457 String sLeftPath1 = paths_ConcatPath(di1.getFilepath(0, sLeftBasePath), di1.diffFileInfo[0].filename);
458 String sLeftPath2 = paths_ConcatPath(di2.getFilepath(0, sLeftBasePath), di2.diffFileInfo[0].filename);
459 String sLeftPath3 = paths_ConcatPath(di3.getFilepath(0, sLeftBasePath), di3.diffFileInfo[0].filename);
460 String sMiddlePath1 = paths_ConcatPath(di1.getFilepath(1, sMiddleBasePath), di1.diffFileInfo[1].filename);
461 String sMiddlePath2 = paths_ConcatPath(di2.getFilepath(1, sMiddleBasePath), di2.diffFileInfo[1].filename);
462 String sMiddlePath3 = paths_ConcatPath(di3.getFilepath(1, sMiddleBasePath), di3.diffFileInfo[1].filename);
463 String sRightPath1 = paths_ConcatPath(di1.getFilepath(2, sRightBasePath), di1.diffFileInfo[2].filename);
464 String sRightPath2 = paths_ConcatPath(di2.getFilepath(2, sRightBasePath), di2.diffFileInfo[2].filename);
465 String sRightPath3 = paths_ConcatPath(di3.getFilepath(2, sRightBasePath), di3.diffFileInfo[2].filename);
466 // Must not be binary (unless archive)
469 (di1.diffcode.isBin() || di2.diffcode.isBin() || di3.diffcode.isBin())
472 && (sLeftPath1.empty() || ArchiveGuessFormat(sLeftPath1))
473 && (sMiddlePath1.empty() || ArchiveGuessFormat(sMiddlePath1))
474 && (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
475 && (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2))
476 && (sLeftPath2.empty() || ArchiveGuessFormat(sLeftPath2))
477 && (sMiddlePath2.empty() || ArchiveGuessFormat(sMiddlePath2)) /* FIXME: */
484 // Must be both directory or neither
485 if (di1.diffcode.isDirectory() != di2.diffcode.isDirectory() && di1.diffcode.isDirectory() != di3.diffcode.isDirectory()) return false;
487 // Must be on different sides, or one on one side & one on both
488 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(1) && di3.diffcode.isExists(2))
490 if (di1.diffcode.isExists(0) && di2.diffcode.isExists(2) && di3.diffcode.isExists(1))
492 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(0) && di3.diffcode.isExists(2))
494 if (di1.diffcode.isExists(1) && di2.diffcode.isExists(2) && di3.diffcode.isExists(0))
496 if (di1.diffcode.isExists(2) && di2.diffcode.isExists(0) && di3.diffcode.isExists(1))
498 if (di1.diffcode.isExists(2) && di2.diffcode.isExists(1) && di3.diffcode.isExists(0))
501 // Allow to compare items if left & right path refer to same directory
502 // (which means there is effectively two files involved). No need to check
503 // side flags. If files weren't on both sides, we'd have no DIFFITEMs.
504 if (string_compare_nocase(sLeftBasePath, sMiddleBasePath) == 0 && string_compare_nocase(sLeftBasePath, sRightBasePath) == 0)
509 /// is it possible to open item ?
510 bool IsItemOpenableOn(const DIFFITEM & di, int index)
512 // impossible if not existing
513 if (!di.diffcode.isExists(index)) return false;
515 // everything else can be opened on right
519 /// is it possible to open left ... item ?
520 bool IsItemOpenableOnWith(const DIFFITEM & di, int index)
522 return (!di.diffcode.isDirectory() && IsItemOpenableOn(di, index));
524 /// is it possible to copy to... left item?
525 bool IsItemCopyableToOn(const DIFFITEM & di, int index)
527 // impossible if only on right
528 if (!di.diffcode.isExists(index)) return false;
530 // everything else can be copied to from left
534 // When navigating differences, do we stop at this one ?
535 bool IsItemNavigableDiff(const CDiffContext& ctxt, const DIFFITEM & di)
537 // Not a valid diffitem, one of special items (e.g "..")
538 if (di.diffcode.diffcode == 0)
540 if (di.diffcode.isResultFiltered() || di.diffcode.isResultError())
542 if (!di.diffcode.isResultDiff() && IsItemExistAll(ctxt, di))
547 bool IsItemExistAll(const CDiffContext& ctxt, const DIFFITEM & di)
549 // Not a valid diffitem, one of special items (e.g "..")
550 if (di.diffcode.diffcode == 0)
552 if (ctxt.GetCompareDirs() == 2)
553 return di.diffcode.isSideBoth();
555 return di.diffcode.isSideAll();
560 * @brief Determines if the user wants to see given item.
561 * This function determines what items to show and what items to hide. There
562 * are lots of combinations, but basically we check if menuitem is enabled or
563 * disabled and show/hide matching items. For non-recursive compare we never
564 * hide folders as that would disable user browsing into them. And we even
565 * don't really know if folders are identical or different as we haven't
567 * @param [in] di Item to check.
568 * @return true if item should be shown, false if not.
569 * @sa CDirDoc::Redisplay()
571 bool IsShowable(const CDiffContext& ctxt, const DIFFITEM & di, const DirViewFilterSettings& filter)
573 if (di.customFlags1 & ViewCustomFlags::HIDDEN)
576 if (di.diffcode.isResultFiltered())
578 // Treat SKIPPED as a 'super'-flag. If item is skipped and user
579 // wants to see skipped items show item regardless of other flags
580 return filter.show_skipped;
583 if (di.diffcode.isDirectory())
585 // Subfolders in non-recursive compare can only be skipped or unique
586 if (!ctxt.m_bRecursive)
588 // left/right filters
589 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
591 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
595 if (di.diffcode.isResultError() /*&& !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 (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
606 // ONLY filter folders by result (identical/different) for tree-view.
607 // In the tree-view we show subfolders with identical/different
608 // status. The flat view only shows files inside folders. So if we
609 // filter by status the files inside folder are filtered too and
610 // users see files appearing/disappearing without clear logic.
611 if (filter.tree_mode)
614 if (di.diffcode.isResultError()/* && !GetMainFrame()->m_bShowErrors FIXME:*/)
618 if (di.diffcode.isResultSame() && !filter.show_identical)
620 if (di.diffcode.isResultDiff() && !filter.show_different)
627 // left/right filters
628 if (di.diffcode.isSideFirstOnly() && !filter.show_unique_left)
630 if (di.diffcode.isSideSecondOnly() && !filter.show_unique_right)
634 if (di.diffcode.isBin() && !filter.show_binaries)
638 if (di.diffcode.isResultSame() && !filter.show_identical)
640 if (di.diffcode.isResultError() /* && !GetMainFrame()->m_bShowErrors FIXME:*/)
642 if (di.diffcode.isResultDiff() && !filter.show_different)
649 * @brief Open one selected item.
650 * @param [in] pos1 Item position.
651 * @param [in,out] di1 Pointer to first diffitem.
652 * @param [in,out] di2 Pointer to second diffitem.
653 * @param [in,out] di3 Pointer to third diffitem.
654 * @param [out] paths First/Second/Third paths.
655 * @param [out] sel1 Item's selection index in listview.
656 * @param [in,out] isDir Is item folder?
657 * return false if there was error or item was completely processed.
659 bool GetOpenOneItem(const CDiffContext& ctxt, UIntPtr pos1, const DIFFITEM **di1, const DIFFITEM **di2, const DIFFITEM **di3,
660 PathContext & paths, int & sel1, bool & isdir, String& errmsg)
662 *di1 = &ctxt.GetDiffAt(pos1);
666 paths = GetItemFileNames(ctxt, **di1);
668 if ((*di1)->diffcode.isDirectory())
671 if (isdir && ((*di1)->diffcode.isExistsFirst() && (*di1)->diffcode.isExistsSecond() && (*di1)->diffcode.isExistsThird()))
673 // Check both folders exist. If either folder is missing that means
674 // folder has been changed behind our back, so we just tell user to
675 // refresh the compare.
676 PATH_EXISTENCE path1Exists = paths_DoesPathExist(paths[0]);
677 PATH_EXISTENCE path2Exists = paths_DoesPathExist(paths[1]);
678 if (path1Exists != IS_EXISTING_DIR || path2Exists != IS_EXISTING_DIR)
680 String invalid = path1Exists == IS_EXISTING_DIR ? paths[0] : paths[1];
681 errmsg = string_format_string1(
682 _("Operation aborted!\n\nFolder contents at disks has changed, path\n%1\nwas not found.\n\nPlease refresh the compare."),
692 * @brief Open two selected items.
693 * @param [in] pos1 First item position.
694 * @param [in] pos2 Second item position.
695 * @param [in,out] di1 Pointer to first diffitem.
696 * @param [in,out] di2 Pointer to second diffitem.
697 * @param [out] paths First/Second/Third paths.
698 * @param [out] sel1 First item's selection index in listview.
699 * @param [out] sel2 Second item's selection index in listview.
700 * @param [in,out] isDir Is item folder?
701 * return false if there was error or item was completely processed.
703 bool GetOpenTwoItems(const CDiffContext& ctxt, SELECTIONTYPE selectionType, UIntPtr pos1, UIntPtr pos2, const DIFFITEM **di1, const DIFFITEM **di2,
704 PathContext & paths, int & sel1, int & sel2, bool & isDir, String& errmsg)
706 String pathLeft, pathRight;
708 // Two items selected, get their info
709 *di1 = &ctxt.GetDiffAt(pos1);
710 *di2 = &ctxt.GetDiffAt(pos2);
712 // Check for binary & side compatibility & file/dir compatibility
713 if (!AreItemsOpenable(ctxt, selectionType, **di1, **di2))
719 switch (selectionType)
721 case SELECTIONTYPE_NORMAL:
722 // Ensure that di1 is on left (swap if needed)
723 if ((*di1)->diffcode.isSideSecondOnly() || ((*di1)->diffcode.isSideBoth() &&
724 (*di2)->diffcode.isSideFirstOnly()))
726 const DIFFITEM * temp = *di1;
733 // Fill in pathLeft & pathRight
734 GetItemFileNames(ctxt, **di1, pathLeft, temp);
735 GetItemFileNames(ctxt, **di2, temp, pathRight);
737 case SELECTIONTYPE_LEFT1LEFT2:
738 GetItemFileNames(ctxt, **di1, pathLeft, temp);
739 GetItemFileNames(ctxt, **di2, pathRight, temp);
741 case SELECTIONTYPE_RIGHT1RIGHT2:
742 GetItemFileNames(ctxt, **di1, temp, pathLeft);
743 GetItemFileNames(ctxt, **di2, temp, pathRight);
745 case SELECTIONTYPE_LEFT1RIGHT2:
746 GetItemFileNames(ctxt, **di1, pathLeft, temp);
747 GetItemFileNames(ctxt, **di2, temp, pathRight);
749 case SELECTIONTYPE_LEFT2RIGHT1:
750 GetItemFileNames(ctxt, **di1, temp, pathRight);
751 GetItemFileNames(ctxt, **di2, pathLeft, temp);
755 if ((*di1)->diffcode.isDirectory())
758 if (GetPairComparability(PathContext(pathLeft, pathRight)) != IS_EXISTING_DIR)
760 errmsg = _("The selected folder is invalid.");
765 paths.SetLeft(pathLeft);
766 paths.SetRight(pathRight);
772 * @brief Open three selected items.
773 * @param [in] pos1 First item position.
774 * @param [in] pos2 Second item position.
775 * @param [in] pos3 Third item position.
776 * @param [in,out] di1 Pointer to first diffitem.
777 * @param [in,out] di2 Pointer to second diffitem.
778 * @param [in,out] di3 Pointer to third diffitem.
779 * @param [out] paths First/Second/Third paths.
780 * @param [out] sel1 First item's selection index in listview.
781 * @param [out] sel2 Second item's selection index in listview.
782 * @param [out] sel3 Third item's selection index in listview.
783 * @param [in,out] isDir Is item folder?
784 * return false if there was error or item was completely processed.
786 bool GetOpenThreeItems(const CDiffContext& ctxt, UIntPtr pos1, UIntPtr pos2, UIntPtr pos3, const DIFFITEM **di1, const DIFFITEM **di2, const DIFFITEM **di3,
787 PathContext & paths, int & sel1, int & sel2, int & sel3, bool & isDir, String& errmsg)
789 String pathLeft, pathMiddle, pathRight;
793 // Two items selected, get their info
794 *di1 = &ctxt.GetDiffAt(pos1);
795 *di2 = &ctxt.GetDiffAt(pos2);
797 // Check for binary & side compatibility & file/dir compatibility
798 if (!::AreItemsOpenable(ctxt, **di1, **di2, **di2) &&
799 !::AreItemsOpenable(ctxt, **di1, **di1, **di2))
803 // Ensure that di1 is on left (swap if needed)
804 if ((*di1)->diffcode.isExists(0) && (*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2))
811 else if ((*di1)->diffcode.isExists(0) && (*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(1))
816 else if ((*di1)->diffcode.isExists(1) && (*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(0))
818 std::swap(*di1, *di2);
819 std::swap(sel1, sel2);
823 else if ((*di2)->diffcode.isExists(0) && (*di2)->diffcode.isExists(1) && (*di1)->diffcode.isExists(2))
825 std::swap(*di1, *di2);
826 std::swap(sel1, sel2);
832 else if ((*di2)->diffcode.isExists(0) && (*di2)->diffcode.isExists(2) && (*di1)->diffcode.isExists(1))
834 std::swap(*di1, *di2);
835 std::swap(sel1, sel2);
839 else if ((*di2)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2) && (*di1)->diffcode.isExists(0))
847 // Three items selected, get their info
848 *di1 = &ctxt.GetDiffAt(pos1);
849 *di2 = &ctxt.GetDiffAt(pos2);
850 *di3 = &ctxt.GetDiffAt(pos3);
852 // Check for binary & side compatibility & file/dir compatibility
853 if (!::AreItemsOpenable(ctxt, **di1, **di2, **di3))
857 // Ensure that di1 is on left (swap if needed)
858 if ((*di1)->diffcode.isExists(0) && (*di2)->diffcode.isExists(1) && (*di3)->diffcode.isExists(2))
861 else if ((*di1)->diffcode.isExists(0) && (*di2)->diffcode.isExists(2) && (*di3)->diffcode.isExists(1))
863 std::swap(*di2, *di3);
864 std::swap(sel2, sel3);
866 else if ((*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(0) && (*di3)->diffcode.isExists(2))
868 std::swap(*di1, *di2);
869 std::swap(sel1, sel2);
871 else if ((*di1)->diffcode.isExists(1) && (*di2)->diffcode.isExists(2) && (*di3)->diffcode.isExists(0))
873 std::swap(*di1, *di3);
874 std::swap(sel1, sel3);
875 std::swap(*di2, *di3);
876 std::swap(sel2, sel3);
878 else if ((*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(0) && (*di3)->diffcode.isExists(1))
880 std::swap(*di1, *di2);
881 std::swap(sel1, sel2);
882 std::swap(*di2, *di3);
883 std::swap(sel2, sel3);
885 else if ((*di1)->diffcode.isExists(2) && (*di2)->diffcode.isExists(1) && (*di3)->diffcode.isExists(0))
887 std::swap(*di1, *di3);
888 std::swap(sel1, sel3);
892 // Fill in pathLeft & & pathMiddle & pathRight
893 PathContext pathsTemp = GetItemFileNames(ctxt, **di1);
894 pathLeft = pathsTemp[0];
895 pathsTemp = GetItemFileNames(ctxt, **di2);
896 pathMiddle = pathsTemp[1];
897 pathsTemp = GetItemFileNames(ctxt, **di3);
898 pathRight = pathsTemp[2];
900 if ((*di1)->diffcode.isDirectory())
903 if (GetPairComparability(PathContext(pathLeft, pathMiddle, pathRight)) != IS_EXISTING_DIR)
905 errmsg = _("The selected folder is invalid.");
910 paths.SetLeft(pathLeft.c_str());
911 paths.SetRight(pathRight.c_str());
917 * @brief Get the file names on both sides for specified item.
918 * @note Return empty strings if item is special item.
920 void GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM & di, String& strLeft, String& strRight)
922 const String leftrelpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
923 const String rightrelpath = paths_ConcatPath(di.diffFileInfo[1].path, di.diffFileInfo[1].filename);
924 const String & leftpath = ctxt.GetPath(0);
925 const String & rightpath = ctxt.GetPath(1);
926 strLeft = paths_ConcatPath(leftpath, leftrelpath);
927 strRight = paths_ConcatPath(rightpath, rightrelpath);
930 String GetItemFileName(const CDiffContext& ctxt, const DIFFITEM & di, int index)
932 return paths_ConcatPath(ctxt.GetPath(index), paths_ConcatPath(di.diffFileInfo[index].path, di.diffFileInfo[index].filename));
935 PathContext GetItemFileNames(const CDiffContext& ctxt, const DIFFITEM & di)
938 for (int nIndex = 0; nIndex < ctxt.GetCompareDirs(); nIndex++)
940 const String relpath = paths_ConcatPath(di.diffFileInfo[nIndex].path, di.diffFileInfo[nIndex].filename);
941 const String & path = ctxt.GetPath(nIndex);
942 paths.SetPath(nIndex, paths_ConcatPath(path, relpath));
948 * @brief Return image index appropriate for this row
950 int GetColImage(const CDiffContext&ctxt, const DIFFITEM & di)
952 // Must return an image index into image list created above in OnInitDialog
953 if (di.diffcode.isResultError())
954 return DIFFIMG_ERROR;
955 if (di.diffcode.isResultAbort())
956 return DIFFIMG_ABORT;
957 if (di.diffcode.isResultFiltered())
958 return (di.diffcode.isDirectory() ? DIFFIMG_DIRSKIP : DIFFIMG_SKIP);
959 if (di.diffcode.isSideFirstOnly())
960 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRUNIQUE : DIFFIMG_LUNIQUE);
961 if (di.diffcode.isSideSecondOnly())
962 return (ctxt.GetCompareDirs() < 3 ?
963 (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE) :
964 (di.diffcode.isDirectory() ? DIFFIMG_MDIRUNIQUE : DIFFIMG_MUNIQUE));
965 if (di.diffcode.isSideThirdOnly())
966 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRUNIQUE : DIFFIMG_RUNIQUE);
967 if (ctxt.GetCompareDirs() == 3)
969 if (!di.diffcode.isExists(0))
970 return (di.diffcode.isDirectory() ? DIFFIMG_LDIRMISSING : DIFFIMG_LMISSING);
971 if (!di.diffcode.isExists(1))
972 return (di.diffcode.isDirectory() ? DIFFIMG_MDIRMISSING : DIFFIMG_MMISSING);
973 if (!di.diffcode.isExists(2))
974 return (di.diffcode.isDirectory() ? DIFFIMG_RDIRMISSING : DIFFIMG_RMISSING);
976 if (di.diffcode.isResultSame())
978 if (di.diffcode.isDirectory())
979 return DIFFIMG_DIRSAME;
982 if (di.diffcode.isText())
983 return DIFFIMG_TEXTSAME;
984 else if (di.diffcode.isBin())
985 return DIFFIMG_BINSAME;
991 if (di.diffcode.isResultDiff())
993 if (di.diffcode.isDirectory())
994 return DIFFIMG_DIRDIFF;
997 if (di.diffcode.isText())
998 return DIFFIMG_TEXTDIFF;
999 else if (di.diffcode.isBin())
1000 return DIFFIMG_BINDIFF;
1002 return DIFFIMG_DIFF;
1005 return (di.diffcode.isDirectory() ? DIFFIMG_DIR : DIFFIMG_ABORT);
1009 * @brief Set side status of diffitem
1010 * @note This does not update UI - ReloadItemStatus() does
1011 * @sa CDirDoc::ReloadItemStatus()
1013 void SetDiffSide(DIFFITEM& di, unsigned diffcode)
1015 SetDiffStatus(di, diffcode, DIFFCODE::SIDEFLAGS);
1019 * @brief Set compare status of diffitem
1020 * @note This does not update UI - ReloadItemStatus() does
1021 * @sa CDirDoc::ReloadItemStatus()
1023 void SetDiffCompare(DIFFITEM& di, unsigned diffcode)
1025 SetDiffStatus(di, diffcode, DIFFCODE::COMPAREFLAGS);
1029 * @brief Set status for diffitem
1030 * @param diffcode New code
1031 * @param mask Defines allowed set of flags to change
1032 * @param idx Item's index to list in UI
1034 void SetDiffStatus(DIFFITEM& di, unsigned diffcode, unsigned mask)
1036 // TODO: Why is the update broken into these pieces ?
1037 // Someone could figure out these pieces and probably simplify this.
1039 // Update DIFFITEM code (comparison result)
1040 assert(! ((~mask) & diffcode) ); // make sure they only set flags in their mask
1041 di.diffcode.diffcode &= (~mask); // remove current data
1042 di.diffcode.diffcode |= diffcode; // add new data
1044 // update DIFFITEM time (and other disk info), and tell views
1047 void SetDiffCounts(DIFFITEM& di, unsigned diffs, unsigned ignored)
1049 di.nidiffs = ignored; // see StoreDiffResult() in DirScan.cpp
1054 * @brief Set item's view-flag.
1055 * @param [in] key Item fow which flag is set.
1056 * @param [in] flag Flag value to set.
1057 * @param [in] mask Mask for possible flag values.
1059 void SetItemViewFlag(DIFFITEM& di, unsigned flag, unsigned mask)
1061 unsigned curFlags = di.customFlags1;
1062 curFlags &= ~mask; // Zero bits masked
1064 di.customFlags1 = curFlags;
1068 * @brief Set all item's view-flag.
1069 * @param [in] flag Flag value to set.
1070 * @param [in] mask Mask for possible flag values.
1072 void SetItemViewFlag(CDiffContext& ctxt, unsigned flag, unsigned mask)
1074 UIntPtr pos = ctxt.GetFirstDiffPosition();
1078 UINT curFlags = ctxt.GetCustomFlags1(pos);
1079 curFlags &= ~mask; // Zero bits masked
1081 ctxt.SetCustomFlags1(pos, curFlags);
1082 ctxt.GetNextDiffPosition(pos);
1087 * @brief Mark selected items as needing for rescan.
1088 * @return Count of items to rescan.
1090 void MarkForRescan(DIFFITEM &di)
1092 SetDiffStatus(di, 0, DIFFCODE::TEXTFLAGS | DIFFCODE::SIDEFLAGS | DIFFCODE::COMPAREFLAGS);
1093 SetDiffStatus(di, DIFFCODE::NEEDSCAN, DIFFCODE::SCANFLAGS);
1097 * @brief Return string such as "15 of 30 Files Affected" or "30 Files Affected"
1099 String FormatFilesAffectedString(int nFilesAffected, int nFilesTotal)
1101 if (nFilesAffected == nFilesTotal)
1102 return string_format_string1(_("(%1 Files Affected)"), NumToStr(nFilesTotal));
1104 return string_format_string2(_("(%1 of %2 Files Affected)"), NumToStr(nFilesAffected), NumToStr(nFilesTotal));
1107 String FormatMenuItemString(const String& fmt1, const String& fmt2, int count, int total)
1110 return string_format_string1(fmt1, NumToStr(total));
1112 return string_format_string2(fmt2, NumToStr(count), NumToStr(total));
1115 String FormatMenuItemString(SIDE_TYPE src, SIDE_TYPE dst, int count, int total)
1118 if (src == SIDE_LEFT && dst == SIDE_RIGHT)
1120 fmt1 = _("Left to Right (%1)");
1121 fmt2 = _("Left to Right (%1 of %2)");
1123 else if (src == SIDE_LEFT && dst == SIDE_MIDDLE)
1125 fmt1 = _("Left to Middle (%1)");
1126 fmt2 = _("Left to middle (%1 of %2)");
1128 else if (src == SIDE_MIDDLE && dst == SIDE_LEFT)
1130 fmt1 = _("Middle to Left (%1)");
1131 fmt2 = _("Middle to Left (%1 of %2)");
1133 else if (src == SIDE_MIDDLE && dst == SIDE_RIGHT)
1135 fmt1 = _("Middle to Right (%1)");
1136 fmt2 = _("Middle to Right (%1 of %2)");
1138 else if (src == SIDE_RIGHT && dst == SIDE_LEFT)
1140 fmt1 = _("Right to Left (%1)");
1141 fmt2 = _("Right to Left (%1 of %2)");
1143 else if (src == SIDE_RIGHT && dst == SIDE_MIDDLE)
1145 fmt1 = _("Right to Middle (%1)");
1146 fmt2 = _("Right to Middle (%1 of %2)");
1148 return FormatMenuItemString(fmt1, fmt2, count, total);
1151 String FormatMenuItemString(SIDE_TYPE src, int count, int total)
1154 if (src == SIDE_LEFT)
1156 fmt1 = _("Left (%1)");
1157 fmt2 = _("Left (%1 of %2)");
1159 else if (src == SIDE_MIDDLE)
1161 fmt1 = _("Middle (%1)");
1162 fmt2 = _("Middle (%1 of %2)");
1164 else if (src == SIDE_RIGHT)
1166 fmt1 = _("Right (%1)");
1167 fmt2 = _("Right (%1 of %2)");
1169 return FormatMenuItemString(fmt1, fmt2, count, total);
1172 String FormatMenuItemStringBoth(int count, int total)
1174 return FormatMenuItemString(_("Both (%1)"), _("Both (%1 of %2)"), count, total);
1177 String FormatMenuItemStringTo(SIDE_TYPE src, int count, int total)
1180 if (src == SIDE_LEFT)
1182 fmt1 = _("Left to... (%1)");
1183 fmt2 = _("Left to... (%1 of %2)");
1185 else if (src == SIDE_MIDDLE)
1187 fmt1 = _("Middle to... (%1)");
1188 fmt2 = _("Middle to... (%1 of %2)");
1190 else if (src == SIDE_RIGHT)
1192 fmt1 = _("Right to... (%1)");
1193 fmt2 = _("Right to... (%1 of %2)");
1195 return FormatMenuItemString(fmt1, fmt2, count, total);
1199 * @brief Rename a file without moving it to different directory.
1201 * @param szOldFileName [in] Full path of file to rename.
1202 * @param szNewFileName [in] New file name (without the path).
1204 * @return true if file was renamed successfully.
1206 bool RenameOnSameDir(const String& szOldFileName, const String& szNewFileName)
1208 bool bSuccess = false;
1210 if (DOES_NOT_EXIST != paths_DoesPathExist(szOldFileName))
1212 String sFullName = paths_ConcatPath(paths_GetPathOnly(szOldFileName), szNewFileName);
1214 // No need to rename if new file already exist.
1215 if ((sFullName != szOldFileName) ||
1216 (DOES_NOT_EXIST == paths_DoesPathExist(sFullName)))
1218 ShellFileOperations fileOp;
1219 fileOp.SetOperation(FO_RENAME, 0);
1220 fileOp.AddSourceAndDestination(szOldFileName, sFullName);
1221 bSuccess = fileOp.Run();
1233 * @brief Convert number to string.
1234 * Converts number to string, with commas between digits in
1235 * locale-appropriate manner.
1237 String NumToStr(int n)
1239 return locality::NumToLocaleStr(n);
1242 void ExpandSubdirs(CDiffContext& ctxt, DIFFITEM& dip)
1244 dip.customFlags1 |= ViewCustomFlags::EXPANDED;
1245 UIntPtr diffpos = ctxt.GetFirstChildDiffPosition(reinterpret_cast<UIntPtr>(&dip));
1248 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1249 if (!di.IsAncestor(&dip))
1251 if (di.HasChildren())
1252 di.customFlags1 |= ViewCustomFlags::EXPANDED;
1256 void ExpandAllSubdirs(CDiffContext& ctxt)
1258 UIntPtr diffpos = ctxt.GetFirstDiffPosition();
1261 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1262 di.customFlags1 |= ViewCustomFlags::EXPANDED;
1266 void CollapseAllSubdirs(CDiffContext& ctxt)
1268 UIntPtr diffpos = ctxt.GetFirstDiffPosition();
1271 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1272 di.customFlags1 &= ~ViewCustomFlags::EXPANDED;
1276 DirViewTreeState *SaveTreeState(const CDiffContext& ctxt)
1278 DirViewTreeState *pTreeState = new DirViewTreeState();
1279 UIntPtr diffpos = ctxt.GetFirstDiffPosition();
1282 const DIFFITEM &di = ctxt.GetNextDiffPosition(diffpos);
1283 if (di.HasChildren())
1285 String relpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1286 pTreeState->insert(std::pair<String, bool>(relpath, !!(di.customFlags1 & ViewCustomFlags::EXPANDED)));
1292 void RestoreTreeState(CDiffContext& ctxt, DirViewTreeState *pTreeState)
1294 UIntPtr diffpos = ctxt.GetFirstDiffPosition();
1297 DIFFITEM &di = ctxt.GetNextDiffRefPosition(diffpos);
1298 if (di.HasChildren())
1300 String relpath = paths_ConcatPath(di.diffFileInfo[0].path, di.diffFileInfo[0].filename);
1301 std::map<String, bool>::iterator p = pTreeState->find(relpath);
1302 if (p != pTreeState->end())
1304 di.customFlags1 &= ~ViewCustomFlags::EXPANDED;
1305 di.customFlags1 |= (p->second ? ViewCustomFlags::EXPANDED : 0);
1312 * @brief Tell if user may use ".." and move to parents directory.
1313 * This function checks if current compare's parent folders should
1314 * be allowed to open. I.e. if current compare folders are:
1315 * - C:\Work\Project1 and
1316 * - C:\Work\Project2
1317 * we check if C:\Work and C:\Work should be allowed to opened.
1318 * For regular folders we allow opening if both folders exist.
1319 * @param [out] leftParent Left parent folder to open.
1320 * @param [out] rightParent Right parent folder to open.
1321 * @return Info if opening parent folders should be enabled:
1322 * - No : upward RESTRICTED
1323 * - ParentIsRegularPath : upward ENABLED
1324 * - ParentIsTempPath : upward ENABLED
1326 AllowUpwardDirectory::ReturnCode
1327 CheckAllowUpwardDirectory(const CDiffContext& ctxt, const CTempPathContext *pTempPathContext, PathContext &pathsParent)
1329 const String & path0 = ctxt.GetNormalizedPath(0);
1330 const String & path1 = ctxt.GetNormalizedPath(1);
1331 const String & path2 = ctxt.GetCompareDirs() > 2 ? ctxt.GetNormalizedPath(2) : _T("");
1333 // If we have temp context it means we are comparing archives
1334 if (pTempPathContext)
1336 String name0 = paths_FindFileName(path0);
1337 String name1 = paths_FindFileName(path1);
1338 String name2 = (ctxt.GetCompareDirs() > 2) ? paths_FindFileName(path2) : _T("");
1340 /* FIXME: for 3way diff*/
1341 String::size_type cchLeftRoot = pTempPathContext->m_strRoot[0].length();
1342 if (path0.length() <= cchLeftRoot)
1344 pathsParent.SetSize(ctxt.GetCompareDirs());
1345 if (pTempPathContext->m_pParent)
1347 pathsParent[0] = pTempPathContext->m_pParent->m_strRoot[0];
1348 pathsParent[1] = pTempPathContext->m_pParent->m_strRoot[1];
1349 if (GetPairComparability(PathContext(pathsParent[0], pathsParent[1])) != IS_EXISTING_DIR)
1350 return AllowUpwardDirectory::Never;
1351 return AllowUpwardDirectory::ParentIsTempPath;
1353 pathsParent[0] = pTempPathContext->m_strDisplayRoot[0];
1354 pathsParent[1] = pTempPathContext->m_strDisplayRoot[1];
1355 if (!ctxt.m_piFilterGlobal->includeFile(pathsParent[0], pathsParent[1]))
1356 return AllowUpwardDirectory::Never;
1357 if (string_compare_nocase(name0, _T("ORIGINAL")) == 0 && string_compare_nocase(name1, _T("ALTERED")) == 0)
1359 pathsParent[0] = paths_GetParentPath(pathsParent[0]);
1360 pathsParent[1] = paths_GetParentPath(pathsParent[1]);
1362 name0 = paths_FindFileName(pathsParent[0]);
1363 name1 = paths_FindFileName(pathsParent[1]);
1364 if (string_compare_nocase(name0, name1) == 0)
1366 pathsParent[0] = paths_GetParentPath(pathsParent[0]);
1367 pathsParent[1] = paths_GetParentPath(pathsParent[1]);
1368 if (GetPairComparability(PathContext(pathsParent[0], pathsParent[1])) != IS_EXISTING_DIR)
1369 return AllowUpwardDirectory::Never;
1370 return AllowUpwardDirectory::ParentIsTempPath;
1372 return AllowUpwardDirectory::No;
1377 // If regular parent folders exist, allow opening them
1378 pathsParent.SetSize(ctxt.GetCompareDirs());
1379 pathsParent[0] = paths_GetParentPath(path0);
1380 pathsParent[1] = paths_GetParentPath(path1);
1381 if (ctxt.GetCompareDirs() > 2)
1382 pathsParent[2] = paths_GetParentPath(path2);
1383 if (GetPairComparability(pathsParent) != IS_EXISTING_DIR)
1384 return AllowUpwardDirectory::Never;
1385 return AllowUpwardDirectory::ParentIsRegularPath;