1 /////////////////////////////////////////////////////////////////////////////
2 // see Merge.cpp for license (GPLv2+) statement
4 /////////////////////////////////////////////////////////////////////////////
8 * @brief Implementation of methods of CDirView that copy/move/delete files
10 // RCS ID line follows -- this is updated by CVS
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.
23 #include "coretools.h"
24 #include "OutputDlg.h"
26 #include "CShellFileOp.h"
27 #include "OptionsDef.h"
32 static char THIS_FILE[] = __FILE__;
37 // Prompt user to confirm a multiple item copy
38 static BOOL ConfirmMultipleCopy(int count, int total)
42 AfxFormatString2(s, IDS_CONFIRM_COPY2DIR, NumToStr(count), NumToStr(total));
43 int rtn = AfxMessageBox(s, MB_YESNO|MB_ICONQUESTION|MB_DONT_ASK_AGAIN, IDS_CONFIRM_COPY2DIR);
47 // Prompt user to confirm a single item copy
48 static BOOL ConfirmSingleCopy(LPCTSTR src, LPCTSTR dest)
51 AfxFormatString2(s, IDS_CONFIRM_COPY_SINGLE, src, dest);
52 int rtn = AfxMessageBox(s, MB_YESNO|MB_ICONQUESTION|MB_DONT_ASK_AGAIN, IDS_CONFIRM_COPY_SINGLE);
56 // Prompt user to confirm a multiple item delete
57 static BOOL ConfirmMultipleDelete(int count, int total)
60 AfxFormatString2(s, IDS_CONFIRM_DELETE_ITEMS, NumToStr(count), NumToStr(total));
61 int rtn = AfxMessageBox(s, MB_YESNO|MB_ICONQUESTION|MB_DONT_ASK_AGAIN, IDS_CONFIRM_DELETE_ITEMS);
65 // Prompt user to confirm a single item delete
66 static BOOL ConfirmSingleDelete(LPCTSTR filepath)
69 AfxFormatString1(s, IDS_CONFIRM_DELETE_SINGLE, filepath);
70 int rtn = AfxMessageBox(s, MB_YESNO|MB_ICONQUESTION|MB_DONT_ASK_AGAIN, IDS_CONFIRM_DELETE_SINGLE);
74 // Prompt & copy item from right to left, if legal
75 void CDirView::DoCopyRightToLeft()
77 // First we build a list of desired actions
78 ActionList actionList(ACT_COPY);
80 CString slFile, srFile;
81 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
83 const DIFFITEM& di = GetDiffItem(sel);
84 if (IsItemCopyableToLeft(di))
86 GetItemFileNames(sel, slFile, srFile);
91 act.code = di.diffcode;
92 act.dirflag = di.isDirectory();
93 actionList.actions.AddTail(act);
95 ++actionList.selcount;
98 // Now we prompt, and execute actions
99 ConfirmAndPerformActions(actionList);
101 // Prompt & copy item from left to right, if legal
102 void CDirView::DoCopyLeftToRight()
104 // First we build a list of desired actions
105 ActionList actionList(ACT_COPY);
107 CString slFile, srFile;
108 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
110 const DIFFITEM& di = GetDiffItem(sel);
111 if (IsItemCopyableToRight(di))
113 GetItemFileNames(sel, slFile, srFile);
117 act.dirflag = di.isDirectory();
119 act.code = di.diffcode;
120 actionList.actions.AddTail(act);
122 ++actionList.selcount;
125 // Now we prompt, and execute actions
126 ConfirmAndPerformActions(actionList);
129 // Prompt & delete left, if legal
130 void CDirView::DoDelLeft()
132 // First we build a list of desired actions
133 ActionList actionList(ACT_DEL_LEFT);
135 CString slFile, srFile;
136 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
138 const DIFFITEM& di = GetDiffItem(sel);
139 if (IsItemDeletableOnLeft(di))
141 GetItemFileNames(sel, slFile, srFile);
144 act.dirflag = di.isDirectory();
146 act.code = di.diffcode;
147 actionList.actions.AddTail(act);
149 ++actionList.selcount;
152 // Now we prompt, and execute actions
153 ConfirmAndPerformActions(actionList);
155 // Prompt & delete right, if legal
156 void CDirView::DoDelRight()
158 // First we build a list of desired actions
159 ActionList actionList(ACT_DEL_RIGHT);
161 CString slFile, srFile;
162 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
164 const DIFFITEM& di = GetDiffItem(sel);
166 if (IsItemDeletableOnRight(di))
168 GetItemFileNames(sel, slFile, srFile);
171 act.dirflag = di.isDirectory();
173 act.code = di.diffcode;
174 actionList.actions.AddTail(act);
176 ++actionList.selcount;
179 // Now we prompt, and execute actions
180 ConfirmAndPerformActions(actionList);
182 // Prompt & delete both, if legal
183 void CDirView::DoDelBoth()
185 // First we build a list of desired actions
186 ActionList actionList(ACT_DEL_BOTH);
188 CString slFile, srFile;
189 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
191 const DIFFITEM& di = GetDiffItem(sel);
193 if (IsItemDeletableOnBoth(di))
195 GetItemFileNames(sel, slFile, srFile);
199 act.dirflag = di.isDirectory();
201 act.code = di.diffcode;
202 actionList.actions.AddTail(act);
204 ++actionList.selcount;
207 // Now we prompt, and execute actions
208 ConfirmAndPerformActions(actionList);
212 * @brief Copy selected left-side files to user-specified directory
213 * @note CShellFileOp takes care of much of error handling
215 void CDirView::DoCopyLeftTo()
222 VERIFY(msg.LoadString(IDS_SELECT_DESTFOLDER));
223 if (!SelectFolder(destPath, startPath, msg))
226 fileOp.SetOperationFlags(FO_COPY, this, FOF_NOCONFIRMMKDIR);
227 fileOp.AddDestFile(destPath);
230 CString slFile, srFile;
231 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
233 const DIFFITEM& di = GetDiffItem(sel);
235 if (IsItemCopyableToOnLeft(di))
237 GetItemFileNames(sel, slFile, srFile);
238 fileOp.AddSourceFile(slFile);
242 BOOL bSuccess = FALSE;
243 BOOL bAPICalled = FALSE;
244 BOOL bAborted = FALSE;
246 bSuccess = fileOp.Go(&bAPICalled, &nAPIReturn, &bAborted);
250 * @brief Copy selected righ-side files to user-specified directory
251 * @note CShellFileOp takes care of much of error handling
253 void CDirView::DoCopyRightTo()
260 VERIFY(msg.LoadString(IDS_SELECT_DESTFOLDER));
261 if (!SelectFolder(destPath, startPath, msg))
264 fileOp.SetOperationFlags(FO_COPY, this, FOF_NOCONFIRMMKDIR);
265 fileOp.AddDestFile(destPath);
268 CString slFile, srFile;
269 while ((sel = m_pList->GetNextItem(sel, LVNI_SELECTED)) != -1)
271 const DIFFITEM& di = GetDiffItem(sel);
273 if (IsItemCopyableToOnRight(di))
275 GetItemFileNames(sel, slFile, srFile);
276 fileOp.AddSourceFile(srFile);
280 BOOL bSuccess = FALSE;
281 BOOL bAPICalled = FALSE;
282 BOOL bAborted = FALSE;
284 bSuccess = fileOp.Go( &bAPICalled, &nAPIReturn, &bAborted );
287 // Confirm with user, then perform the action list
288 void CDirView::ConfirmAndPerformActions(ActionList & actionList)
290 if (!actionList.selcount) // Not sure it is possible to get right-click menu without
291 return; // any selected items, but may as well be safe
293 ASSERT(actionList.actions.GetCount()>0); // Or else the update handler got it wrong
295 if (!ConfirmActionList(actionList))
298 PerformActionList(actionList);
302 * @brief Confirm actions with user as appropriate
303 * (type, whether single or multiple).
305 BOOL CDirView::ConfirmActionList(const ActionList & actionList)
307 // special handling for the single item case, because it is probably the most common,
308 // and we can give the user exact details easily for it
309 switch(actionList.atype)
312 if (actionList.GetCount()==1)
314 const action & act = actionList.actions.GetHead();
315 if (!ConfirmSingleCopy(act.src, act.dest))
320 if (!ConfirmMultipleCopy(actionList.GetCount(), actionList.selcount))
325 // Deleting does not need confirmation, CShellFileOp takes care of it
333 LogErrorString(_T("Unknown fileoperation in CDirView::ConfirmActionList()"));
334 _RPTF0(_CRT_ERROR, "Unknown fileoperation in CDirView::ConfirmActionList()");
341 * @brief Perform an array of actions
342 * @note There can be only COPY or DELETE actions, not both!
344 void CDirView::PerformActionList(ActionList & actionList)
351 // Set mainframe variable (VSS):
352 mf->m_CheckOutMulti = FALSE;
353 mf->m_bVCProjSync = TRUE;
355 switch (actionList.atype)
361 operation = FO_DELETE;
364 operation = FO_DELETE;
367 operation = FO_DELETE;
370 LogErrorString(_T("Unknown fileoperation in CDirView::PerformActionList()"));
371 _RPTF0(_CRT_ERROR, "Unknown fileoperation in CDirView::PerformActionList()");
375 int operFlags = FOF_NOCONFIRMMKDIR | FOF_MULTIDESTFILES;
377 // Check option and enable putting deleted items to Recycle Bin
378 if (mf->m_options.GetInt(OPT_USE_RECYCLE_BIN) == TRUE)
379 operFlags |= FOF_ALLOWUNDO;
380 fileOp.SetOperationFlags(operation, this, operFlags);
382 // Add files/directories
383 BOOL bSucceed = TRUE;
384 POSITION pos = actionList.actions.GetHeadPosition();
385 while (bSucceed && pos != NULL)
387 const action act = actionList.actions.GetNext(pos);
389 // If copying files, try to sync files to VCS too
390 if (actionList.atype == ACT_COPY && !act.dirflag)
393 bSucceed = mf->SyncFilesToVCS(act.src, act.dest, &strErr);
395 AfxMessageBox(strErr, MB_OK | MB_ICONERROR);
398 if (bSucceed) // No error from VCS sync (propably just not called)
402 switch (actionList.atype)
405 fileOp.AddSourceFile(act.src);
406 fileOp.AddDestFile(act.dest);
407 gLog.Write(_T("Copy file(s) from: %s\n\tto: %s"), act.src, act.dest);
410 fileOp.AddSourceFile(act.src);
411 gLog.Write(_T("Delete file(s) from LEFT: %s"), act.src);
414 fileOp.AddSourceFile(act.src);
415 gLog.Write(_T("Delete file(s) from RIGHT: %s"), act.src);
418 fileOp.AddSourceFile(act.src);
419 fileOp.AddSourceFile(act.dest);
420 gLog.Write(_T("Delete BOTH file(s) from: %s\n\tto: %s"), act.src, act.dest);
424 catch (CMemoryException *ex)
427 LogErrorString(_T("CDirView::PerformActionList(): ")
428 _T("Adding files to buffer failed!"));
435 // Now process files/directories that got added to list
436 BOOL bOpStarted = FALSE;
438 BOOL bUserCancelled = FALSE;
439 BOOL bFileOpSucceed = fileOp.Go(&bOpStarted, &apiRetVal, &bUserCancelled);
442 if (bFileOpSucceed && !bUserCancelled)
444 gLog.Write(_T("Fileoperation succeeded."));
445 UpdateCopiedItems(actionList);
446 UpdateDeletedItems(actionList);
448 else if (!bOpStarted)
450 // Invalid parameters - is this programmer error only?
451 LogErrorString(_T("Invalid usage of CShellFileOp in ")
452 _T("CDirView::PerformActionList()"));
453 _RPTF0(_CRT_ERROR, "Invalid usage of CShellFileOp in "
454 "CDirView::PerformActionList()");
456 else if (bUserCancelled)
458 // User cancelled, we have a problem as we don't know which
459 // items were processed!
460 // User could cancel operation before it was done or during operation
461 gLog.Write(LOGLEVEL::LWARNING, _T("User cancelled fileoperation!"));
465 // CShellFileOp shows errors to user, so just write log
466 LogErrorString(Fmt(_T("File operation failed: %s"),
467 GetSysError(GetLastError())));
472 * @brief Update copied items after fileactions
474 void CDirView::UpdateCopiedItems(ActionList & actionList)
476 while (actionList.GetCount()>0)
478 action act = actionList.actions.RemoveHead();
479 POSITION diffpos = GetItemKey(act.idx);
480 const DIFFITEM & di = GetDiffContext()->GetDiffAt(diffpos);
482 if (actionList.atype == ACT_COPY)
484 // Copy files and folders
485 CDirDoc *pDoc = GetDocument();
486 pDoc->SetDiffSide(DIFFCODE::BOTH, act.idx);
488 // Folders don't have compare flag set!!
490 pDoc->SetDiffCompare(DIFFCODE::NOCMP, act.idx);
492 pDoc->SetDiffCompare(DIFFCODE::SAME, act.idx);
493 pDoc->ReloadItemStatus(act.idx);
497 // Delete files and folders
498 // If both items or unique item is deleted, don't bother updating
499 // statuses, just remove from list
500 CDirDoc *pDoc = GetDocument();
501 if (actionList.atype == ACT_DEL_LEFT)
505 actionList.deletedItems.AddTail(act.idx);
509 pDoc->SetDiffSide(DIFFCODE::RIGHT, act.idx);
510 pDoc->SetDiffCompare(DIFFCODE::NOCMP, act.idx);
511 pDoc->ReloadItemStatus(act.idx);
515 if (actionList.atype == ACT_DEL_RIGHT)
517 if (di.isSideRight())
519 actionList.deletedItems.AddTail(act.idx);
523 pDoc->SetDiffSide(DIFFCODE::LEFT, act.idx);
524 pDoc->SetDiffCompare(DIFFCODE::NOCMP, act.idx);
525 pDoc->ReloadItemStatus(act.idx);
529 if (actionList.atype == ACT_DEL_BOTH)
531 actionList.deletedItems.AddTail(act.idx);
538 * @brief Update deleted items after fileactions
540 void CDirView::UpdateDeletedItems(ActionList & actionList)
542 while (!actionList.deletedItems.IsEmpty())
544 int idx = actionList.deletedItems.RemoveTail();
545 POSITION diffpos = GetItemKey(idx);
546 GetDiffContext()->RemoveDiff(diffpos);
547 m_pList->DeleteItem(idx);
552 /// Get directories of first selected item
553 BOOL CDirView::GetSelectedDirNames(CString& strLeft, CString& strRight) const
555 BOOL bResult = GetSelectedFileNames(strLeft, strRight);
559 strLeft = GetPathOnly(strLeft);
560 strRight = GetPathOnly(strRight);
565 /// is it possible to copy item to left ?
566 BOOL CDirView::IsItemCopyableToLeft(const DIFFITEM & di)
568 // don't let them mess with error items
569 if (di.isResultError()) return FALSE;
570 // can't copy same items
571 if (di.isResultSame()) return FALSE;
572 // impossible if only on left
573 if (di.isSideLeft()) return FALSE;
575 // everything else can be copied to left
578 /// is it possible to copy item to right ?
579 BOOL CDirView::IsItemCopyableToRight(const DIFFITEM & di)
581 // don't let them mess with error items
582 if (di.isResultError()) return FALSE;
583 // can't copy same items
584 if (di.isResultSame()) return FALSE;
585 // impossible if only on right
586 if (di.isSideRight()) return FALSE;
588 // everything else can be copied to right
591 /// is it possible to delete left item ?
592 BOOL CDirView::IsItemDeletableOnLeft(const DIFFITEM & di)
594 // don't let them mess with error items
595 if (di.isResultError()) return FALSE;
596 // impossible if only on right
597 if (di.isSideRight()) return FALSE;
598 // everything else can be deleted on left
601 /// is it possible to delete right item ?
602 BOOL CDirView::IsItemDeletableOnRight(const DIFFITEM & di)
604 // don't let them mess with error items
605 if (di.isResultError()) return FALSE;
606 // impossible if only on right
607 if (di.isSideLeft()) return FALSE;
609 // everything else can be deleted on right
612 /// is it possible to delete both items ?
613 BOOL CDirView::IsItemDeletableOnBoth(const DIFFITEM & di)
615 // don't let them mess with error items
616 if (di.isResultError()) return FALSE;
617 // impossible if only on right or left
618 if (di.isSideLeft() || di.isSideRight()) return FALSE;
620 // everything else can be deleted on both
624 /// is it possible to open left item ?
625 BOOL CDirView::IsItemOpenableOnLeft(const DIFFITEM & di)
627 // impossible if only on right
628 if (di.isSideRight()) return FALSE;
630 // everything else can be opened on right
633 /// is it possible to open right item ?
634 BOOL CDirView::IsItemOpenableOnRight(const DIFFITEM & di)
636 // impossible if only on left
637 if (di.isSideLeft()) return FALSE;
639 // everything else can be opened on left
642 /// is it possible to open left ... item ?
643 BOOL CDirView::IsItemOpenableOnLeftWith(const DIFFITEM & di)
645 return (!di.isDirectory() && IsItemOpenableOnLeft(di));
647 /// is it possible to open with ... right item ?
648 BOOL CDirView::IsItemOpenableOnRightWith(const DIFFITEM & di)
650 return (!di.isDirectory() && IsItemOpenableOnRight(di));
652 /// is it possible to copy to... left item?
653 BOOL CDirView::IsItemCopyableToOnLeft(const DIFFITEM & di)
655 // no directory copying right now
656 if (di.isDirectory()) return FALSE;
657 // impossible if only on right
658 if (di.isSideRight()) return FALSE;
660 // everything else can be copied to from left
663 /// is it possible to copy to... right item?
664 BOOL CDirView::IsItemCopyableToOnRight(const DIFFITEM & di)
666 // no directory copying right now
667 if (di.isDirectory()) return FALSE;
668 // impossible if only on left
669 if (di.isSideLeft()) return FALSE;
671 // everything else can be copied to from right
675 /// get the file names on both sides for first selected item
676 BOOL CDirView::GetSelectedFileNames(CString& strLeft, CString& strRight) const
678 int sel = m_pList->GetNextItem(-1, LVNI_SELECTED);
681 GetItemFileNames(sel, strLeft, strRight);
684 /// get file name on specified side for first selected item
685 CString CDirView::GetSelectedFileName(SIDE_TYPE stype) const
688 if (!GetSelectedFileNames(left, right)) return _T("");
689 return stype==SIDE_LEFT ? left : right;
692 /// get the file names on both sides for specified item
693 void CDirView::GetItemFileNames(int sel, CString& strLeft, CString& strRight) const
695 CString name, pathex;
697 POSITION diffpos = GetItemKey(sel);
698 const CDiffContext * ctxt = GetDiffContext();
699 const DIFFITEM & di = ctxt->GetDiffAt(diffpos);
701 CString relpath = paths_ConcatPath(di.sSubdir, di.sfilename);
702 strLeft = paths_ConcatPath(ctxt->m_strLeft, relpath);
703 strRight = paths_ConcatPath(ctxt->m_strRight, relpath);
706 /// Open selected file on specified side
707 void CDirView::DoOpen(SIDE_TYPE stype)
709 int sel = GetSingleSelectedItem();
710 if (sel == -1) return;
711 CString file = GetSelectedFileName(stype);
712 if (file.IsEmpty()) return;
713 int rtn = (int)ShellExecute(::GetDesktopWindow(), _T("open"), file, 0, 0, SW_SHOWNORMAL);
714 if (rtn==SE_ERR_NOASSOC)
719 /// Open with dialog for file on selected side
720 void CDirView::DoOpenWith(SIDE_TYPE stype)
722 int sel = GetSingleSelectedItem();
723 if (sel == -1) return;
724 CString file = GetSelectedFileName(stype);
725 if (file.IsEmpty()) return;
727 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH)) return;
728 sysdir.ReleaseBuffer();
729 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + file;
730 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg, sysdir, SW_SHOWNORMAL);
733 /// Open selected file on specified side to external editor
734 void CDirView::DoOpenWithEditor(SIDE_TYPE stype)
736 int sel = GetSingleSelectedItem();
737 if (sel == -1) return;
738 CString file = GetSelectedFileName(stype);
739 if (file.IsEmpty()) return;
741 mf->OpenFileToExternalEditor(file);