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 "OptionsDef.h"
44 #include "DiffFileInfo.h"
45 #include "SaveClosingDlg.h"
48 #include "OptionsMgr.h"
49 #include "FileOrFolderSelect.h"
50 #include "DiffWrapper.h"
55 static char THIS_FILE[] = __FILE__;
58 int CHexMergeDoc::m_nBuffersTemp = 2;
60 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt);
61 static int Try(HRESULT hr, UINT type = MB_OKCANCEL|MB_ICONSTOP);
64 * @brief Update diff item
66 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt)
68 di.diffcode.diffcode |= DIFFCODE::SIDEFLAGS;
69 for (int nBuffer = 0; nBuffer < nBuffers; nBuffer++)
71 di.diffFileInfo[nBuffer].ClearPartial();
72 di.diffFileInfo[nBuffer].ClearPartial();
73 if (!pCtxt->UpdateInfoFromDiskHalf(di, nBuffer))
76 di.diffcode.diffcode &= ~DIFFCODE::FIRST;
77 else if (nBuffer == 1)
78 di.diffcode.diffcode &= ~DIFFCODE::SECOND;
80 di.diffcode.diffcode &= ~DIFFCODE::THIRD;
84 di.diffcode.diffcode &= ~(DIFFCODE::TEXTFLAGS | DIFFCODE::COMPAREFLAGS);
85 // 2. Process unique files
86 // We must compare unique files to itself to detect encoding
87 if (!di.diffcode.isExistsFirst() || !di.diffcode.isExistsSecond() || (nBuffers == 3 && di.diffcode.isExistsThird()))
89 int compareMethod = pCtxt->GetCompareMethod();
90 if (compareMethod != CMP_DATE && compareMethod != CMP_DATE_SIZE &&
91 compareMethod != CMP_SIZE)
93 di.diffcode.diffcode |= DIFFCODE::SAME;
95 int diffCode = folderCmp.prepAndCompareFiles(pCtxt, di);
96 // Add possible binary flag for unique items
97 if (diffCode & DIFFCODE::BIN)
98 di.diffcode.diffcode |= DIFFCODE::BIN;
101 // 3. Compare two files
106 di.diffcode.diffcode |= folderCmp.prepAndCompareFiles(pCtxt, di);
111 * @brief Issue an error popup if passed in HRESULT is nonzero
113 static int Try(HRESULT hr, UINT type)
115 return hr ? CInternetException(hr).ReportError(type) : 0;
118 /////////////////////////////////////////////////////////////////////////////
121 IMPLEMENT_DYNCREATE(CHexMergeDoc, CDocument)
123 BEGIN_MESSAGE_MAP(CHexMergeDoc, CDocument)
124 //{{AFX_MSG_MAP(CHexMergeDoc)
125 ON_COMMAND(ID_FILE_SAVE, OnFileSave)
126 ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
127 ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
128 ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
129 ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
130 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
131 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
132 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
133 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
134 ON_COMMAND(ID_L2R, OnL2r)
135 ON_COMMAND(ID_R2L, OnR2l)
136 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
137 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
138 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
139 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
140 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
144 /////////////////////////////////////////////////////////////////////////////
145 // CHexMergeDoc construction/destruction
148 * @brief Constructor.
150 CHexMergeDoc::CHexMergeDoc()
153 m_nBuffers = m_nBuffersTemp;
154 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
156 m_pView[nBuffer] = NULL;
157 m_nBufferType[nBuffer] = BUFFER_NORMAL;
164 * Informs associated dirdoc that mergedoc is closing.
166 CHexMergeDoc::~CHexMergeDoc()
169 m_pDirDoc->MergeDocClosing(this);
173 * @brief Update associated diff item
175 void CHexMergeDoc::UpdateDiffItem(CDirDoc *pDirDoc)
177 // If directory compare has results
178 if (pDirDoc && pDirDoc->HasDiffs())
180 const String &pathLeft = m_filePaths.GetLeft();
181 const String &pathRight = m_filePaths.GetRight();
182 CDiffContext &ctxt = const_cast<CDiffContext &>(pDirDoc->GetDiffContext());
183 if (UINT_PTR pos = pDirDoc->FindItemFromPaths(pathLeft, pathRight))
185 DIFFITEM &di = pDirDoc->GetDiffRefByKey(pos);
186 ::UpdateDiffItem(m_nBuffers, di, &ctxt);
190 int lengthFirst = m_pView[0]->GetLength();
191 void *bufferFirst = m_pView[0]->GetBuffer(lengthFirst);
192 for (int nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
194 int length = m_pView[nBuffer]->GetLength();
195 if (lengthFirst != length)
199 void *buffer = m_pView[nBuffer]->GetBuffer(length);
200 bDiff = (memcmp(bufferFirst, buffer, lengthFirst) != 0);
205 GetParentFrame()->SetLastCompareResult(bDiff);
209 * @brief Asks and then saves modified files
211 BOOL CHexMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
213 BOOL bLModified = FALSE, bMModified = FALSE, bRModified = FALSE;
217 bLModified = m_pView[0]->GetModified();
218 bMModified = m_pView[1]->GetModified();
219 bRModified = m_pView[2]->GetModified();
223 bLModified = m_pView[0]->GetModified();
224 bRModified = m_pView[1]->GetModified();
226 if (!bLModified && !bMModified && !bRModified)
229 const String &pathLeft = m_filePaths.GetLeft();
230 const String &pathMiddle = m_filePaths.GetMiddle();
231 const String &pathRight = m_filePaths.GetRight();
234 BOOL bLSaveSuccess = FALSE, bMSaveSuccess = FALSE, bRSaveSuccess = FALSE;
237 dlg.DoAskFor(bLModified, bMModified, bRModified);
239 dlg.m_bDisableCancel = TRUE;
240 if (!pathLeft.empty())
241 dlg.m_sLeftFile = pathLeft.c_str();
243 dlg.m_sLeftFile = m_strDesc[0].c_str();
246 if (!pathMiddle.empty())
247 dlg.m_sMiddleFile = pathMiddle.c_str();
249 dlg.m_sMiddleFile = m_strDesc[1].c_str();
251 if (!pathRight.empty())
252 dlg.m_sRightFile = pathRight.c_str();
254 dlg.m_sRightFile = m_strDesc[1].c_str();
256 if (dlg.DoModal() == IDOK)
260 if (dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
262 switch (Try(m_pView[0]->SaveFile(pathLeft.c_str())))
265 bLSaveSuccess = TRUE;
274 m_pView[0]->SetModified(FALSE);
279 if (dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
281 switch (Try(m_pView[1]->SaveFile(pathMiddle.c_str())))
284 bMSaveSuccess = TRUE;
293 m_pView[1]->SetModified(FALSE);
298 if (dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
300 switch (Try(m_pView[m_nBuffers - 1]->SaveFile(pathRight.c_str())))
303 bRSaveSuccess = TRUE;
312 m_pView[m_nBuffers - 1]->SetModified(FALSE);
321 // If file were modified and saving was successfull,
322 // update status on dir view
323 if (bLSaveSuccess || bMSaveSuccess || bRSaveSuccess)
325 UpdateDiffItem(m_pDirDoc);
332 * @brief Save modified documents
334 BOOL CHexMergeDoc::SaveModified()
336 return PromptAndSaveIfNeeded(TRUE);
340 * @brief Saves both files
342 void CHexMergeDoc::OnFileSave()
344 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
348 void CHexMergeDoc::DoFileSave(int nBuffer)
350 if (m_pView[nBuffer]->GetModified())
352 if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
353 DoFileSaveAs(nBuffer);
356 const String &path = m_filePaths.GetPath(nBuffer);
357 if (Try(m_pView[nBuffer]->SaveFile(path.c_str())) == IDCANCEL)
360 UpdateDiffItem(m_pDirDoc);
364 void CHexMergeDoc::DoFileSaveAs(int nBuffer)
366 const String &path = m_filePaths.GetPath(nBuffer);
370 id = IDS_SAVE_LEFT_AS;
371 else if (nBuffer == m_nBuffers - 1)
372 id = IDS_SAVE_RIGHT_AS;
374 id = IDS_SAVE_MIDDLE_AS;
375 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, path.c_str(), id, NULL, FALSE))
377 if (Try(m_pView[nBuffer]->SaveFile(strPath.c_str())) == IDCANCEL)
381 // We are saving scratchpad (unnamed file)
382 m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
383 m_strDesc[nBuffer].erase();
386 m_filePaths.SetPath(nBuffer, strPath);
387 UpdateDiffItem(m_pDirDoc);
388 UpdateHeaderPath(nBuffer);
393 * @brief Saves left-side file
395 void CHexMergeDoc::OnFileSaveLeft()
401 * @brief Saves middle-side file
403 void CHexMergeDoc::OnFileSaveMiddle()
409 * @brief Saves right-side file
411 void CHexMergeDoc::OnFileSaveRight()
413 DoFileSave(m_nBuffers - 1);
417 * @brief Saves left-side file with name asked
419 void CHexMergeDoc::OnFileSaveAsLeft()
425 * @brief Saves right-side file with name asked
427 void CHexMergeDoc::OnFileSaveAsMiddle()
433 * @brief Saves right-side file with name asked
435 void CHexMergeDoc::OnFileSaveAsRight()
437 DoFileSaveAs(m_nBuffers - 1);
441 * @brief Update diff-number pane text
443 void CHexMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI)
446 pCmdUI->SetText(s.c_str());
450 * @brief DirDoc gives us its identity just after it creates us
452 void CHexMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
454 ASSERT(pDirDoc && !m_pDirDoc);
459 * @brief Return pointer to parent frame
461 CHexMergeFrame * CHexMergeDoc::GetParentFrame()
463 return static_cast<CHexMergeFrame *>(m_pView[0]->GetParentFrame());
467 * @brief DirDoc is closing
469 void CHexMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
471 ASSERT(m_pDirDoc == pDirDoc);
476 * @brief DirDoc commanding us to close
478 BOOL CHexMergeDoc::CloseNow()
480 // Allow user to cancel closing
481 if (!PromptAndSaveIfNeeded(TRUE))
484 GetParentFrame()->CloseNow();
489 * @brief Load one file
491 HRESULT CHexMergeDoc::LoadOneFile(int index, LPCTSTR filename, BOOL readOnly)
495 if (Try(m_pView[index]->LoadFile(filename), MB_ICONSTOP) != 0)
497 m_pView[index]->SetReadOnly(readOnly);
498 m_filePaths.SetPath(index, filename);
499 ASSERT(m_nBufferType[index] == BUFFER_NORMAL); // should have been initialized to BUFFER_NORMAL in constructor
500 String strDesc = theApp.m_strDescriptions[index];
501 if (!strDesc.empty())
503 m_strDesc[index] = strDesc;
504 m_nBufferType[index] = BUFFER_NORMAL_NAMED;
509 m_nBufferType[index] = BUFFER_UNNAMED;
510 m_strDesc[index] = theApp.m_strDescriptions[index];
513 UpdateHeaderPath(index);
514 m_pView[index]->ResizeWindow();
519 * @brief Load files and initialize frame's compare result icon
521 HRESULT CHexMergeDoc::OpenDocs(const PathContext &paths, bool bRO[])
523 CHexMergeFrame *pf = GetParentFrame();
527 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
529 if (FAILED(hr = LoadOneFile(nBuffer, paths.GetPath(nBuffer).c_str(), bRO[nBuffer])))
532 if (nBuffer == m_nBuffers)
535 // An extra ResizeWindow() on the left view aligns scroll ranges, and
536 // also triggers initial diff coloring by invalidating the client area.
537 m_pView[0]->ResizeWindow();
538 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
539 m_pView[0]->SendMessage(WM_COMMAND, ID_FIRSTDIFF);
543 // Use verify macro to trap possible error in debug.
544 VERIFY(pf->DestroyWindow());
550 * @brief Write path and filename to headerbar
551 * @note SetText() does not repaint unchanged text
553 void CHexMergeDoc::UpdateHeaderPath(int pane)
555 CHexMergeFrame *pf = GetParentFrame();
559 if (m_nBufferType[pane] == BUFFER_UNNAMED ||
560 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
562 sText = m_strDesc[pane];
566 sText = m_filePaths.GetPath(pane);
568 m_pDirDoc->ApplyDisplayRoot(pane, sText);
570 if (m_pView[pane]->GetModified())
571 sText.insert(0, _T("* "));
572 pf->GetHeaderInterface()->SetText(pane, sText.c_str());
578 * @brief Update document filenames to title
580 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
582 const TCHAR pszSeparator[] = _T(" - ");
590 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
592 if (!m_strDesc[nBuffer].empty())
593 sFileName[nBuffer] = m_strDesc[nBuffer];
598 paths_SplitFilename(m_filePaths[nBuffer], NULL, &file, &ext);
599 sFileName[nBuffer] += file.c_str();
602 sFileName[nBuffer] += _T(".");
603 sFileName[nBuffer] += ext.c_str();
609 if (sFileName[0] == sFileName[1])
611 sTitle = sFileName[0];
612 sTitle += _T(" x 2");
616 sTitle = sFileName[0];
617 sTitle += pszSeparator;
618 sTitle += sFileName[1];
623 if (sFileName[0] == sFileName[1] && sFileName[0] == sFileName[2])
625 sTitle = sFileName[0];
626 sTitle += _T(" x 3");
630 sTitle = sFileName[0];
631 sTitle += pszSeparator;
632 sTitle += sFileName[1];
633 sTitle += pszSeparator;
634 sTitle += sFileName[2];
639 CDocument::SetTitle(sTitle.c_str());
643 * @brief We have two child views (left & right), so we keep pointers directly
644 * at them (the MFC view list doesn't have them both)
646 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
648 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
650 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
651 m_pView[nBuffer] = pView[nBuffer];
656 * @brief Called when "Save left" item is updated
658 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
660 pCmdUI->Enable(m_pView[0]->GetModified());
664 * @brief Called when "Save middle" item is updated
666 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
668 pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
672 * @brief Called when "Save right" item is updated
674 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
676 pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
680 * @brief Called when "Save" item is updated
682 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
684 BOOL bModified = FALSE;
685 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
686 bModified |= m_pView[nBuffer]->GetModified();
687 pCmdUI->Enable(bModified);
691 * @brief Copy selected bytes from source view to destination view
692 * @note Grows destination buffer as appropriate
694 void CHexMergeDoc::CopySel(CHexMergeView *pViewSrc, CHexMergeView *pViewDst)
696 const IHexEditorWindow::Status *pStatSrc = pViewSrc->GetStatus();
697 int i = min(pStatSrc->iStartOfSelection, pStatSrc->iEndOfSelection);
698 int j = max(pStatSrc->iStartOfSelection, pStatSrc->iEndOfSelection);
699 int u = pViewSrc->GetLength();
700 int v = pViewDst->GetLength();
701 if (pStatSrc->bSelected && i <= v)
705 BYTE *p = pViewSrc->GetBuffer(u);
706 BYTE *q = pViewDst->GetBuffer(v);
707 memcpy(q + i, p + i, j - i + 1);
708 CWnd *pwndFocus = CWnd::GetFocus();
709 if (pwndFocus != pViewSrc)
710 pViewDst->RepaintRange(i, j);
711 if (pwndFocus != pViewDst)
712 pViewSrc->RepaintRange(i, j);
713 pViewDst->SetModified(TRUE);
718 * @brief Copy all bytes from source view to destination view
719 * @note Grows destination buffer as appropriate
721 void CHexMergeDoc::CopyAll(CHexMergeView *pViewSrc, CHexMergeView *pViewDst)
723 if (int i = pViewSrc->GetLength())
725 int j = pViewDst->GetLength();
726 BYTE *p = pViewSrc->GetBuffer(i);
727 BYTE *q = pViewDst->GetBuffer(max(i, j));
729 AfxThrowMemoryException();
731 CWnd *pwndFocus = CWnd::GetFocus();
732 if (pwndFocus != pViewSrc)
733 pViewDst->RepaintRange(0, i);
734 if (pwndFocus != pViewDst)
735 pViewSrc->RepaintRange(0, i);
736 pViewDst->SetModified(TRUE);
741 * @brief Copy selected bytes from left to right
743 void CHexMergeDoc::OnL2r()
745 CopySel(m_pView[0], m_pView[1]);
749 * @brief Copy selected bytes from right to left
751 void CHexMergeDoc::OnR2l()
753 CopySel(m_pView[1], m_pView[0]);
757 * @brief Copy all bytes from left to right
759 void CHexMergeDoc::OnAllRight()
761 CopyAll(m_pView[0], m_pView[1]);
765 * @brief Copy all bytes from right to left
767 void CHexMergeDoc::OnAllLeft()
769 CopyAll(m_pView[1], m_pView[0]);
773 * @brief Called when user selects View/Zoom In from menu.
775 void CHexMergeDoc::OnViewZoomIn()
777 for (int pane = 0; pane < m_nBuffers; pane++)
778 m_pView[pane]->ZoomText(1);
782 * @brief Called when user selects View/Zoom Out from menu.
784 void CHexMergeDoc::OnViewZoomOut()
786 for (int pane = 0; pane < m_nBuffers; pane++)
787 m_pView[pane]->ZoomText(-1);
791 * @brief Called when user selects View/Zoom Normal from menu.
793 void CHexMergeDoc::OnViewZoomNormal()
795 for (int pane = 0; pane < m_nBuffers; pane++)
796 m_pView[pane]->ZoomText(0);