1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 /////////////////////////////////////////////////////////////////////////////
22 * @file HexMergeDoc.cpp
24 * @brief Implementation file for CHexMergeDoc
27 // ID line follows -- this is updated by SVN
28 // $Id: HexMergeDoc.cpp 7166 2010-05-16 12:05:13Z jtuc $
31 #include "HexMergeDoc.h"
33 #include "UnicodeString.h"
34 #include "FileTextEncoding.h"
36 #include "HexMergeFrm.h"
37 #include "HexMergeView.h"
39 #include "FolderCmp.h"
40 #include "Environment.h"
41 #include "DiffContext.h" // FILE_SAME
43 #include "DirActions.h"
44 #include "OptionsDef.h"
45 #include "DiffFileInfo.h"
46 #include "SaveClosingDlg.h"
49 #include "OptionsMgr.h"
50 #include "FileOrFolderSelect.h"
51 #include "DiffWrapper.h"
56 static char THIS_FILE[] = __FILE__;
59 int CHexMergeDoc::m_nBuffersTemp = 2;
61 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt);
62 static int Try(HRESULT hr, UINT type = MB_OKCANCEL|MB_ICONSTOP);
65 * @brief Update diff item
67 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt)
69 di.diffcode.diffcode |= DIFFCODE::SIDEFLAGS;
70 for (int nBuffer = 0; nBuffer < nBuffers; nBuffer++)
72 di.diffFileInfo[nBuffer].ClearPartial();
73 di.diffFileInfo[nBuffer].ClearPartial();
74 if (!pCtxt->UpdateInfoFromDiskHalf(di, nBuffer))
77 di.diffcode.diffcode &= ~DIFFCODE::FIRST;
78 else if (nBuffer == 1)
79 di.diffcode.diffcode &= ~DIFFCODE::SECOND;
81 di.diffcode.diffcode &= ~DIFFCODE::THIRD;
85 di.diffcode.diffcode &= ~(DIFFCODE::TEXTFLAGS | DIFFCODE::COMPAREFLAGS);
86 // 2. Process unique files
87 // We must compare unique files to itself to detect encoding
88 if (!di.diffcode.existAll(nBuffers))
90 int compareMethod = pCtxt->GetCompareMethod();
91 if (compareMethod != CMP_DATE && compareMethod != CMP_DATE_SIZE &&
92 compareMethod != CMP_SIZE)
94 di.diffcode.diffcode |= DIFFCODE::SAME;
96 int diffCode = folderCmp.prepAndCompareFiles(pCtxt, di);
97 // Add possible binary flag for unique items
98 if (diffCode & DIFFCODE::BIN)
99 di.diffcode.diffcode |= DIFFCODE::BIN;
102 // 3. Compare two files
107 di.diffcode.diffcode |= folderCmp.prepAndCompareFiles(pCtxt, di);
112 * @brief Issue an error popup if passed in HRESULT is nonzero
114 static int Try(HRESULT hr, UINT type)
116 return hr ? CInternetException(hr).ReportError(type) : 0;
119 /////////////////////////////////////////////////////////////////////////////
122 IMPLEMENT_DYNCREATE(CHexMergeDoc, CDocument)
124 BEGIN_MESSAGE_MAP(CHexMergeDoc, CDocument)
125 //{{AFX_MSG_MAP(CHexMergeDoc)
126 ON_COMMAND(ID_FILE_SAVE, OnFileSave)
127 ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
128 ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
129 ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
130 ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
131 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
132 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
133 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
134 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
135 ON_COMMAND(ID_RESCAN, OnFileReload)
136 ON_COMMAND(ID_L2R, OnL2r)
137 ON_COMMAND(ID_R2L, OnR2l)
138 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
139 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
140 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
141 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
142 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
143 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
144 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
148 /////////////////////////////////////////////////////////////////////////////
149 // CHexMergeDoc construction/destruction
152 * @brief Constructor.
154 CHexMergeDoc::CHexMergeDoc()
157 m_nBuffers = m_nBuffersTemp;
158 std::fill_n(m_pView, m_nBuffers, static_cast<CHexMergeView *>(NULL));
159 std::fill_n(m_nBufferType, m_nBuffers, BUFFER_NORMAL);
165 * Informs associated dirdoc that mergedoc is closing.
167 CHexMergeDoc::~CHexMergeDoc()
170 m_pDirDoc->MergeDocClosing(this);
174 * @brief Return active merge edit view (or left one if neither active)
176 CHexMergeView * CHexMergeDoc::GetActiveMergeView() const
178 CView * pActiveView = GetParentFrame()->GetActiveView();
179 CHexMergeView * pHexMergeView = dynamic_cast<CHexMergeView *>(pActiveView);
181 pHexMergeView = m_pView[0]; // default to left view (in case some location or detail view active)
182 return pHexMergeView;
186 * @brief Update associated diff item
188 void CHexMergeDoc::UpdateDiffItem(CDirDoc *pDirDoc)
190 // If directory compare has results
191 if (pDirDoc && pDirDoc->HasDiffs())
193 const String &pathLeft = m_filePaths.GetLeft();
194 const String &pathRight = m_filePaths.GetRight();
195 CDiffContext &ctxt = pDirDoc->GetDiffContext();
196 if (UINT_PTR pos = FindItemFromPaths(ctxt, pathLeft, pathRight))
198 DIFFITEM &di = ctxt.GetDiffRefAt(pos);
199 ::UpdateDiffItem(m_nBuffers, di, &ctxt);
203 int lengthFirst = m_pView[0]->GetLength();
204 void *bufferFirst = m_pView[0]->GetBuffer(lengthFirst);
205 for (int nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
207 int length = m_pView[nBuffer]->GetLength();
208 if (lengthFirst != length)
212 void *buffer = m_pView[nBuffer]->GetBuffer(length);
213 bDiff = (memcmp(bufferFirst, buffer, lengthFirst) != 0);
218 GetParentFrame()->SetLastCompareResult(bDiff);
222 * @brief Asks and then saves modified files
224 BOOL CHexMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
226 bool bLModified = false, bMModified = false, bRModified = false;
230 bLModified = !!m_pView[0]->GetModified();
231 bMModified = !!m_pView[1]->GetModified();
232 bRModified = !!m_pView[2]->GetModified();
236 bLModified = !!m_pView[0]->GetModified();
237 bRModified = !!m_pView[1]->GetModified();
239 if (!bLModified && !bMModified && !bRModified)
242 const String &pathLeft = m_filePaths.GetLeft();
243 const String &pathMiddle = m_filePaths.GetMiddle();
244 const String &pathRight = m_filePaths.GetRight();
247 bool bLSaveSuccess = false, bMSaveSuccess = false, bRSaveSuccess = false;
250 dlg.DoAskFor(bLModified, bMModified, bRModified);
252 dlg.m_bDisableCancel = true;
253 if (!pathLeft.empty())
254 dlg.m_sLeftFile = pathLeft;
256 dlg.m_sLeftFile = m_strDesc[0];
259 if (!pathMiddle.empty())
260 dlg.m_sMiddleFile = pathMiddle;
262 dlg.m_sMiddleFile = m_strDesc[1];
264 if (!pathRight.empty())
265 dlg.m_sRightFile = pathRight;
267 dlg.m_sRightFile = m_strDesc[1];
269 if (dlg.DoModal() == IDOK)
273 if (dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
275 switch (Try(m_pView[0]->SaveFile(pathLeft.c_str())))
278 bLSaveSuccess = TRUE;
287 m_pView[0]->SetModified(FALSE);
292 if (dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
294 switch (Try(m_pView[1]->SaveFile(pathMiddle.c_str())))
297 bMSaveSuccess = TRUE;
306 m_pView[1]->SetModified(FALSE);
311 if (dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
313 switch (Try(m_pView[m_nBuffers - 1]->SaveFile(pathRight.c_str())))
316 bRSaveSuccess = TRUE;
325 m_pView[m_nBuffers - 1]->SetModified(FALSE);
334 // If file were modified and saving was successfull,
335 // update status on dir view
336 if (bLSaveSuccess || bMSaveSuccess || bRSaveSuccess)
338 UpdateDiffItem(m_pDirDoc);
345 * @brief Save modified documents
347 BOOL CHexMergeDoc::SaveModified()
349 return PromptAndSaveIfNeeded(TRUE);
353 * @brief Saves both files
355 void CHexMergeDoc::OnFileSave()
357 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
361 void CHexMergeDoc::DoFileSave(int nBuffer)
363 if (m_pView[nBuffer]->GetModified())
365 if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
366 DoFileSaveAs(nBuffer);
369 const String &path = m_filePaths.GetPath(nBuffer);
370 if (Try(m_pView[nBuffer]->SaveFile(path.c_str())) == IDCANCEL)
373 UpdateDiffItem(m_pDirDoc);
377 void CHexMergeDoc::DoFileSaveAs(int nBuffer)
379 const String &path = m_filePaths.GetPath(nBuffer);
383 title = _("Save Left File As");
384 else if (nBuffer == m_nBuffers - 1)
385 title = _("Save Right File As");
387 title = _("Save Middle File As");
388 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, path.c_str(), title, _T(""), FALSE))
390 if (Try(m_pView[nBuffer]->SaveFile(strPath.c_str())) == IDCANCEL)
394 // We are saving scratchpad (unnamed file)
395 m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
396 m_strDesc[nBuffer].erase();
399 m_filePaths.SetPath(nBuffer, strPath);
400 UpdateDiffItem(m_pDirDoc);
401 UpdateHeaderPath(nBuffer);
406 * @brief Saves left-side file
408 void CHexMergeDoc::OnFileSaveLeft()
414 * @brief Saves middle-side file
416 void CHexMergeDoc::OnFileSaveMiddle()
422 * @brief Saves right-side file
424 void CHexMergeDoc::OnFileSaveRight()
426 DoFileSave(m_nBuffers - 1);
430 * @brief Saves left-side file with name asked
432 void CHexMergeDoc::OnFileSaveAsLeft()
438 * @brief Saves right-side file with name asked
440 void CHexMergeDoc::OnFileSaveAsMiddle()
446 * @brief Saves right-side file with name asked
448 void CHexMergeDoc::OnFileSaveAsRight()
450 DoFileSaveAs(m_nBuffers - 1);
454 * @brief Update diff-number pane text
456 void CHexMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI)
459 pCmdUI->SetText(s.c_str());
463 * @brief DirDoc gives us its identity just after it creates us
465 void CHexMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
467 ASSERT(pDirDoc && !m_pDirDoc);
472 * @brief Return pointer to parent frame
474 CHexMergeFrame * CHexMergeDoc::GetParentFrame() const
476 return static_cast<CHexMergeFrame *>(m_pView[0]->GetParentFrame());
480 * @brief DirDoc is closing
482 void CHexMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
484 ASSERT(m_pDirDoc == pDirDoc);
489 * @brief DirDoc commanding us to close
491 bool CHexMergeDoc::CloseNow()
493 // Allow user to cancel closing
494 if (!PromptAndSaveIfNeeded(TRUE))
497 GetParentFrame()->CloseNow();
502 * @brief Load one file
504 HRESULT CHexMergeDoc::LoadOneFile(int index, LPCTSTR filename, BOOL readOnly)
508 if (Try(m_pView[index]->LoadFile(filename), MB_ICONSTOP) != 0)
510 m_pView[index]->SetReadOnly(readOnly);
511 m_filePaths.SetPath(index, filename);
512 ASSERT(m_nBufferType[index] == BUFFER_NORMAL); // should have been initialized to BUFFER_NORMAL in constructor
513 String strDesc = theApp.m_strDescriptions[index];
514 if (!strDesc.empty())
516 m_strDesc[index] = strDesc;
517 m_nBufferType[index] = BUFFER_NORMAL_NAMED;
522 m_nBufferType[index] = BUFFER_UNNAMED;
523 m_strDesc[index] = theApp.m_strDescriptions[index];
526 UpdateHeaderPath(index);
527 m_pView[index]->ResizeWindow();
532 * @brief Load files and initialize frame's compare result icon
534 HRESULT CHexMergeDoc::OpenDocs(const PathContext &paths, const bool bRO[])
536 CHexMergeFrame *pf = GetParentFrame();
540 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
542 if (FAILED(hr = LoadOneFile(nBuffer, paths.GetPath(nBuffer).c_str(), bRO[nBuffer])))
545 if (nBuffer == m_nBuffers)
548 // An extra ResizeWindow() on the left view aligns scroll ranges, and
549 // also triggers initial diff coloring by invalidating the client area.
550 m_pView[0]->ResizeWindow();
551 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
552 m_pView[0]->SendMessage(WM_COMMAND, ID_FIRSTDIFF);
556 // Use verify macro to trap possible error in debug.
557 VERIFY(pf->DestroyWindow());
562 void CHexMergeDoc::CheckFileChanged(void)
564 for (int pane = 0; pane < m_nBuffers; ++pane)
566 if (m_pView[pane]->IsFileChangedOnDisk(m_filePaths[pane].c_str()))
568 String msg = string_format_string1(_("Another application has updated file\n%1\nsince WinMerge scanned it last time.\n\nDo you want to reload the file?"), m_filePaths[pane]);
569 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING) == IDYES)
579 * @brief Write path and filename to headerbar
580 * @note SetText() does not repaint unchanged text
582 void CHexMergeDoc::UpdateHeaderPath(int pane)
584 CHexMergeFrame *pf = GetParentFrame();
588 if (m_nBufferType[pane] == BUFFER_UNNAMED ||
589 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
591 sText = m_strDesc[pane];
595 sText = m_filePaths.GetPath(pane);
597 m_pDirDoc->ApplyDisplayRoot(pane, sText);
599 if (m_pView[pane]->GetModified())
600 sText.insert(0, _T("* "));
601 pf->GetHeaderInterface()->SetText(pane, sText);
607 * @brief Update document filenames to title
609 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
618 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
619 sFileName[nBuffer] = !m_strDesc[nBuffer].empty() ? m_strDesc[nBuffer] : paths_FindFileName(m_filePaths[nBuffer]);
620 if (std::count(&sFileName[0], &sFileName[0] + m_nBuffers, sFileName[0]) == m_nBuffers)
621 sTitle = sFileName[0] + string_format(_T(" x %d"), m_nBuffers);
623 sTitle = string_join(&sFileName[0], &sFileName[0] + m_nBuffers, _T(" - "));
625 CDocument::SetTitle(sTitle.c_str());
629 * @brief We have two child views (left & right), so we keep pointers directly
630 * at them (the MFC view list doesn't have them both)
632 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
634 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
636 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
637 m_pView[nBuffer] = pView[nBuffer];
638 m_pView[nBuffer]->m_nThisPane = nBuffer;
643 * @brief Called when "Save left" item is updated
645 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
647 pCmdUI->Enable(m_pView[0]->GetModified());
651 * @brief Called when "Save middle" item is updated
653 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
655 pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
659 * @brief Called when "Save right" item is updated
661 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
663 pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
667 * @brief Called when "Save" item is updated
669 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
671 BOOL bModified = FALSE;
672 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
673 bModified |= m_pView[nBuffer]->GetModified();
674 pCmdUI->Enable(bModified);
678 * @brief Reloads the opened files
680 void CHexMergeDoc::OnFileReload()
682 if (!PromptAndSaveIfNeeded(true))
686 for (int pane = 0; pane < m_nBuffers; pane++)
688 bRO[pane] = !!m_pView[pane]->GetReadOnly();
689 theApp.m_strDescriptions[pane] = m_strDesc[pane];
691 int nActivePane = GetActiveMergeView()->m_nThisPane;
692 OpenDocs(m_filePaths, bRO);
696 * @brief Copy selected bytes from source view to destination view
697 * @note Grows destination buffer as appropriate
699 void CHexMergeDoc::CopySel(CHexMergeView *pViewSrc, CHexMergeView *pViewDst)
701 const IHexEditorWindow::Status *pStatSrc = pViewSrc->GetStatus();
702 int i = min(pStatSrc->iStartOfSelection, pStatSrc->iEndOfSelection);
703 int j = max(pStatSrc->iStartOfSelection, pStatSrc->iEndOfSelection);
704 int u = pViewSrc->GetLength();
705 int v = pViewDst->GetLength();
706 if (pStatSrc->bSelected && i <= v)
710 BYTE *p = pViewSrc->GetBuffer(u);
711 BYTE *q = pViewDst->GetBuffer(v);
712 memcpy(q + i, p + i, j - i + 1);
713 CWnd *pwndFocus = CWnd::GetFocus();
714 if (pwndFocus != pViewSrc)
715 pViewDst->RepaintRange(i, j);
716 if (pwndFocus != pViewDst)
717 pViewSrc->RepaintRange(i, j);
718 pViewDst->SetModified(TRUE);
723 * @brief Copy all bytes from source view to destination view
724 * @note Grows destination buffer as appropriate
726 void CHexMergeDoc::CopyAll(CHexMergeView *pViewSrc, CHexMergeView *pViewDst)
728 if (int i = pViewSrc->GetLength())
730 int j = pViewDst->GetLength();
731 BYTE *p = pViewSrc->GetBuffer(i);
732 BYTE *q = pViewDst->GetBuffer(max(i, j));
734 AfxThrowMemoryException();
736 CWnd *pwndFocus = CWnd::GetFocus();
737 if (pwndFocus != pViewSrc)
738 pViewDst->RepaintRange(0, i);
739 if (pwndFocus != pViewDst)
740 pViewSrc->RepaintRange(0, i);
741 pViewDst->SetModified(TRUE);
746 * @brief Copy selected bytes from left to right
748 void CHexMergeDoc::OnL2r()
750 int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
751 int srcPane = dstPane - 1;
752 CopySel(m_pView[srcPane], m_pView[dstPane]);
756 * @brief Copy selected bytes from right to left
758 void CHexMergeDoc::OnR2l()
760 int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
761 int srcPane = dstPane + 1;
762 CopySel(m_pView[srcPane], m_pView[dstPane]);
766 * @brief Copy selected bytes from left to active pane
768 void CHexMergeDoc::OnCopyFromLeft()
770 int dstPane = GetActiveMergeView()->m_nThisPane;
771 int srcPane = (dstPane - 1 < 0) ? 0 : dstPane - 1;
772 CopySel(m_pView[srcPane], m_pView[dstPane]);
776 * @brief Copy selected bytes from right to active pane
778 void CHexMergeDoc::OnCopyFromRight()
780 int dstPane = GetActiveMergeView()->m_nThisPane;
781 int srcPane = (dstPane + 1 > m_nBuffers - 1) ? m_nBuffers - 1 : dstPane + 1;
782 CopySel(m_pView[srcPane], m_pView[dstPane]);
786 * @brief Copy all bytes from left to right
788 void CHexMergeDoc::OnAllRight()
790 int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
791 int srcPane = dstPane - 1;
792 CopyAll(m_pView[srcPane], m_pView[dstPane]);
796 * @brief Copy all bytes from right to left
798 void CHexMergeDoc::OnAllLeft()
800 int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
801 int srcPane = dstPane + 1;
802 CopyAll(m_pView[srcPane], m_pView[dstPane]);
806 * @brief Called when user selects View/Zoom In from menu.
808 void CHexMergeDoc::OnViewZoomIn()
810 for (int pane = 0; pane < m_nBuffers; pane++)
811 m_pView[pane]->ZoomText(1);
815 * @brief Called when user selects View/Zoom Out from menu.
817 void CHexMergeDoc::OnViewZoomOut()
819 for (int pane = 0; pane < m_nBuffers; pane++)
820 m_pView[pane]->ZoomText(-1);
824 * @brief Called when user selects View/Zoom Normal from menu.
826 void CHexMergeDoc::OnViewZoomNormal()
828 for (int pane = 0; pane < m_nBuffers; pane++)
829 m_pView[pane]->ZoomText(0);