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
29 #include "HexMergeDoc.h"
31 #include "UnicodeString.h"
32 #include "FileTextEncoding.h"
34 #include "HexMergeFrm.h"
35 #include "HexMergeView.h"
37 #include "FolderCmp.h"
38 #include "Environment.h"
39 #include "DiffContext.h" // FILE_SAME
41 #include "DirActions.h"
42 #include "OptionsDef.h"
43 #include "DiffFileInfo.h"
44 #include "SaveClosingDlg.h"
47 #include "OptionsMgr.h"
48 #include "FileOrFolderSelect.h"
49 #include "DiffWrapper.h"
54 static char THIS_FILE[] = __FILE__;
57 int CHexMergeDoc::m_nBuffersTemp = 2;
59 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt);
60 static int Try(HRESULT hr, UINT type = MB_OKCANCEL|MB_ICONSTOP);
63 * @brief Update diff item
65 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt)
67 di.diffcode.diffcode |= DIFFCODE::SIDEFLAGS;
68 for (int nBuffer = 0; nBuffer < nBuffers; nBuffer++)
70 di.diffFileInfo[nBuffer].ClearPartial();
71 di.diffFileInfo[nBuffer].ClearPartial();
72 if (!pCtxt->UpdateInfoFromDiskHalf(di, nBuffer))
75 di.diffcode.diffcode &= ~DIFFCODE::FIRST;
76 else if (nBuffer == 1)
77 di.diffcode.diffcode &= ~DIFFCODE::SECOND;
79 di.diffcode.diffcode &= ~DIFFCODE::THIRD;
83 di.diffcode.diffcode &= ~(DIFFCODE::TEXTFLAGS | DIFFCODE::COMPAREFLAGS);
84 // 2. Process unique files
85 // We must compare unique files to itself to detect encoding
86 if (!di.diffcode.existAll(nBuffers))
88 int compareMethod = pCtxt->GetCompareMethod();
89 if (compareMethod != CMP_DATE && compareMethod != CMP_DATE_SIZE &&
90 compareMethod != CMP_SIZE)
92 di.diffcode.diffcode |= DIFFCODE::SAME;
94 int diffCode = folderCmp.prepAndCompareFiles(pCtxt, di);
95 // Add possible binary flag for unique items
96 if (diffCode & DIFFCODE::BIN)
97 di.diffcode.diffcode |= DIFFCODE::BIN;
100 // 3. Compare two files
105 di.diffcode.diffcode |= folderCmp.prepAndCompareFiles(pCtxt, di);
110 * @brief Issue an error popup if passed in HRESULT is nonzero
112 static int Try(HRESULT hr, UINT type)
114 return hr ? CInternetException(hr).ReportError(type) : 0;
117 /////////////////////////////////////////////////////////////////////////////
120 IMPLEMENT_DYNCREATE(CHexMergeDoc, CDocument)
122 BEGIN_MESSAGE_MAP(CHexMergeDoc, CDocument)
123 //{{AFX_MSG_MAP(CHexMergeDoc)
124 ON_COMMAND(ID_FILE_SAVE, OnFileSave)
125 ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
126 ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
127 ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
128 ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
129 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
130 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
131 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
132 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
133 ON_COMMAND(ID_RESCAN, OnFileReload)
134 ON_COMMAND(ID_L2R, OnL2r)
135 ON_COMMAND(ID_R2L, OnR2l)
136 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
137 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
138 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
139 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
140 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
141 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
142 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
146 /////////////////////////////////////////////////////////////////////////////
147 // CHexMergeDoc construction/destruction
150 * @brief Constructor.
152 CHexMergeDoc::CHexMergeDoc()
155 m_nBuffers = m_nBuffersTemp;
156 m_filePaths.SetSize(m_nBuffers);
157 std::fill_n(m_pView, m_nBuffers, static_cast<CHexMergeView *>(NULL));
158 std::fill_n(m_nBufferType, m_nBuffers, BUFFER_NORMAL);
164 * Informs associated dirdoc that mergedoc is closing.
166 CHexMergeDoc::~CHexMergeDoc()
169 m_pDirDoc->MergeDocClosing(this);
173 * @brief Return active merge edit view (or left one if neither active)
175 CHexMergeView * CHexMergeDoc::GetActiveMergeView() const
177 CView * pActiveView = GetParentFrame()->GetActiveView();
178 CHexMergeView * pHexMergeView = dynamic_cast<CHexMergeView *>(pActiveView);
180 pHexMergeView = m_pView[0]; // default to left view (in case some location or detail view active)
181 return pHexMergeView;
185 * @brief Update associated diff item
187 void CHexMergeDoc::UpdateDiffItem(CDirDoc *pDirDoc)
189 // If directory compare has results
190 if (pDirDoc && pDirDoc->HasDiffs())
192 const String &pathLeft = m_filePaths.GetLeft();
193 const String &pathRight = m_filePaths.GetRight();
194 CDiffContext &ctxt = pDirDoc->GetDiffContext();
195 if (UINT_PTR pos = FindItemFromPaths(ctxt, pathLeft, pathRight))
197 DIFFITEM &di = ctxt.GetDiffRefAt(pos);
198 ::UpdateDiffItem(m_nBuffers, di, &ctxt);
202 int lengthFirst = m_pView[0]->GetLength();
203 void *bufferFirst = m_pView[0]->GetBuffer(lengthFirst);
204 for (int nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
206 int length = m_pView[nBuffer]->GetLength();
207 if (lengthFirst != length)
211 void *buffer = m_pView[nBuffer]->GetBuffer(length);
212 bDiff = (memcmp(bufferFirst, buffer, lengthFirst) != 0);
217 GetParentFrame()->SetLastCompareResult(bDiff);
221 * @brief Asks and then saves modified files
223 BOOL CHexMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
225 bool bLModified = false, bMModified = false, bRModified = false;
229 bLModified = !!m_pView[0]->GetModified();
230 bMModified = !!m_pView[1]->GetModified();
231 bRModified = !!m_pView[2]->GetModified();
235 bLModified = !!m_pView[0]->GetModified();
236 bRModified = !!m_pView[1]->GetModified();
238 if (!bLModified && !bMModified && !bRModified)
241 const String &pathLeft = m_filePaths.GetLeft();
242 const String &pathMiddle = m_filePaths.GetMiddle();
243 const String &pathRight = m_filePaths.GetRight();
246 bool bLSaveSuccess = false, bMSaveSuccess = false, bRSaveSuccess = false;
249 dlg.DoAskFor(bLModified, bMModified, bRModified);
251 dlg.m_bDisableCancel = true;
252 if (!pathLeft.empty())
253 dlg.m_sLeftFile = pathLeft;
255 dlg.m_sLeftFile = m_strDesc[0];
258 if (!pathMiddle.empty())
259 dlg.m_sMiddleFile = pathMiddle;
261 dlg.m_sMiddleFile = m_strDesc[1];
263 if (!pathRight.empty())
264 dlg.m_sRightFile = pathRight;
266 dlg.m_sRightFile = m_strDesc[1];
268 if (dlg.DoModal() == IDOK)
272 if (dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
274 switch (Try(m_pView[0]->SaveFile(pathLeft.c_str())))
277 bLSaveSuccess = TRUE;
286 m_pView[0]->SetSavePoint();
291 if (dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
293 switch (Try(m_pView[1]->SaveFile(pathMiddle.c_str())))
296 bMSaveSuccess = TRUE;
305 m_pView[1]->SetSavePoint();
310 if (dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
312 switch (Try(m_pView[m_nBuffers - 1]->SaveFile(pathRight.c_str())))
315 bRSaveSuccess = TRUE;
324 m_pView[m_nBuffers - 1]->SetSavePoint();
333 // If file were modified and saving was successfull,
334 // update status on dir view
335 if (bLSaveSuccess || bMSaveSuccess || bRSaveSuccess)
337 UpdateDiffItem(m_pDirDoc);
344 * @brief Save modified documents
346 BOOL CHexMergeDoc::SaveModified()
348 return PromptAndSaveIfNeeded(TRUE);
352 * @brief Saves both files
354 void CHexMergeDoc::OnFileSave()
356 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
360 void CHexMergeDoc::DoFileSave(int nBuffer)
362 if (m_pView[nBuffer]->GetModified())
364 if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
365 DoFileSaveAs(nBuffer);
368 const String &path = m_filePaths.GetPath(nBuffer);
369 if (Try(m_pView[nBuffer]->SaveFile(path.c_str())) == IDCANCEL)
372 UpdateDiffItem(m_pDirDoc);
376 void CHexMergeDoc::DoFileSaveAs(int nBuffer)
378 const String &path = m_filePaths.GetPath(nBuffer);
382 title = _("Save Left File As");
383 else if (nBuffer == m_nBuffers - 1)
384 title = _("Save Right File As");
386 title = _("Save Middle File As");
387 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, path.c_str(), title, _T(""), FALSE))
389 if (Try(m_pView[nBuffer]->SaveFile(strPath.c_str())) == IDCANCEL)
393 // We are saving scratchpad (unnamed file)
394 m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
395 m_strDesc[nBuffer].erase();
398 m_filePaths.SetPath(nBuffer, strPath);
399 UpdateDiffItem(m_pDirDoc);
400 UpdateHeaderPath(nBuffer);
405 * @brief Saves left-side file
407 void CHexMergeDoc::OnFileSaveLeft()
413 * @brief Saves middle-side file
415 void CHexMergeDoc::OnFileSaveMiddle()
421 * @brief Saves right-side file
423 void CHexMergeDoc::OnFileSaveRight()
425 DoFileSave(m_nBuffers - 1);
429 * @brief Saves left-side file with name asked
431 void CHexMergeDoc::OnFileSaveAsLeft()
437 * @brief Saves right-side file with name asked
439 void CHexMergeDoc::OnFileSaveAsMiddle()
445 * @brief Saves right-side file with name asked
447 void CHexMergeDoc::OnFileSaveAsRight()
449 DoFileSaveAs(m_nBuffers - 1);
453 * @brief Update diff-number pane text
455 void CHexMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI)
458 pCmdUI->SetText(s.c_str());
462 * @brief DirDoc gives us its identity just after it creates us
464 void CHexMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
466 ASSERT(pDirDoc && !m_pDirDoc);
471 * @brief Return pointer to parent frame
473 CHexMergeFrame * CHexMergeDoc::GetParentFrame() const
475 return static_cast<CHexMergeFrame *>(m_pView[0]->GetParentFrame());
479 * @brief DirDoc is closing
481 void CHexMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
483 ASSERT(m_pDirDoc == pDirDoc);
488 * @brief DirDoc commanding us to close
490 bool CHexMergeDoc::CloseNow()
492 // Allow user to cancel closing
493 if (!PromptAndSaveIfNeeded(TRUE))
496 GetParentFrame()->CloseNow();
501 * @brief Load one file
503 HRESULT CHexMergeDoc::LoadOneFile(int index, LPCTSTR filename, BOOL readOnly)
507 if (Try(m_pView[index]->LoadFile(filename), MB_ICONSTOP) != 0)
509 m_pView[index]->SetReadOnly(readOnly);
510 m_filePaths.SetPath(index, filename);
511 ASSERT(m_nBufferType[index] == BUFFER_NORMAL); // should have been initialized to BUFFER_NORMAL in constructor
512 String strDesc = theApp.m_strDescriptions[index];
513 if (!strDesc.empty())
515 m_strDesc[index] = strDesc;
516 m_nBufferType[index] = BUFFER_NORMAL_NAMED;
521 m_nBufferType[index] = BUFFER_UNNAMED;
522 m_strDesc[index] = theApp.m_strDescriptions[index];
525 UpdateHeaderPath(index);
526 m_pView[index]->ResizeWindow();
531 * @brief Load files and initialize frame's compare result icon
533 HRESULT CHexMergeDoc::OpenDocs(const PathContext &paths, const bool bRO[])
535 CHexMergeFrame *pf = GetParentFrame();
539 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
541 if (FAILED(hr = LoadOneFile(nBuffer, paths.GetPath(nBuffer).c_str(), bRO[nBuffer])))
544 if (nBuffer == m_nBuffers)
547 // An extra ResizeWindow() on the left view aligns scroll ranges, and
548 // also triggers initial diff coloring by invalidating the client area.
549 m_pView[0]->ResizeWindow();
550 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
551 m_pView[0]->SendMessage(WM_COMMAND, ID_FIRSTDIFF);
555 // Use verify macro to trap possible error in debug.
556 VERIFY(pf->DestroyWindow());
561 void CHexMergeDoc::CheckFileChanged(void)
563 for (int pane = 0; pane < m_nBuffers; ++pane)
565 if (m_pView[pane]->IsFileChangedOnDisk(m_filePaths[pane].c_str()))
567 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]);
568 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING) == IDYES)
578 * @brief Write path and filename to headerbar
579 * @note SetText() does not repaint unchanged text
581 void CHexMergeDoc::UpdateHeaderPath(int pane)
583 CHexMergeFrame *pf = GetParentFrame();
587 if (m_nBufferType[pane] == BUFFER_UNNAMED ||
588 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
590 sText = m_strDesc[pane];
594 sText = m_filePaths.GetPath(pane);
596 m_pDirDoc->ApplyDisplayRoot(pane, sText);
598 if (m_pView[pane]->GetModified())
599 sText.insert(0, _T("* "));
600 pf->GetHeaderInterface()->SetText(pane, sText);
607 * @brief Customize a heksedit control's settings
609 static void Customize(IHexEditorWindow::Settings *settings)
611 settings->bSaveIni = FALSE;
612 //settings->iAutomaticBPL = FALSE;
613 //settings->iBytesPerLine = 16;
614 //settings->iFontSize = 8;
618 * @brief Customize a heksedit control's colors
620 static void Customize(IHexEditorWindow::Colors *colors)
622 COptionsMgr *pOptionsMgr = GetOptionsMgr();
623 colors->iSelBkColorValue = RGB(224, 224, 224);
624 colors->iDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF);
625 colors->iSelDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF);
626 colors->iDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF_TEXT);
627 if (colors->iDiffTextColorValue == 0xFFFFFFFF)
628 colors->iDiffTextColorValue = 0;
629 colors->iSelDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF_TEXT);
630 if (colors->iSelDiffTextColorValue == 0xFFFFFFFF)
631 colors->iSelDiffTextColorValue = 0;
635 * @brief Customize a heksedit control's settings and colors
637 static void Customize(IHexEditorWindow *pif)
639 Customize(pif->get_settings());
640 Customize(pif->get_colors());
641 //LANGID wLangID = (LANGID)GetThreadLocale();
642 //pif->load_lang(wLangID);
645 void CHexMergeDoc::RefreshOptions()
647 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
649 IHexEditorWindow *pif = m_pView[nBuffer]->GetInterface();
650 pif->read_ini_data();
652 pif->resize_window();
657 * @brief Update document filenames to title
659 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
668 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
669 sFileName[nBuffer] = !m_strDesc[nBuffer].empty() ? m_strDesc[nBuffer] : paths_FindFileName(m_filePaths[nBuffer]);
670 if (std::count(&sFileName[0], &sFileName[0] + m_nBuffers, sFileName[0]) == m_nBuffers)
671 sTitle = sFileName[0] + string_format(_T(" x %d"), m_nBuffers);
673 sTitle = string_join(&sFileName[0], &sFileName[0] + m_nBuffers, _T(" - "));
675 CDocument::SetTitle(sTitle.c_str());
679 * @brief We have two child views (left & right), so we keep pointers directly
680 * at them (the MFC view list doesn't have them both)
682 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
684 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
686 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
687 m_pView[nBuffer] = pView[nBuffer];
688 m_pView[nBuffer]->m_nThisPane = nBuffer;
693 * @brief Called when "Save left" item is updated
695 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
697 pCmdUI->Enable(m_pView[0]->GetModified());
701 * @brief Called when "Save middle" item is updated
703 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
705 pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
709 * @brief Called when "Save right" item is updated
711 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
713 pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
717 * @brief Called when "Save" item is updated
719 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
721 BOOL bModified = FALSE;
722 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
723 bModified |= m_pView[nBuffer]->GetModified();
724 pCmdUI->Enable(bModified);
728 * @brief Reloads the opened files
730 void CHexMergeDoc::OnFileReload()
732 if (!PromptAndSaveIfNeeded(true))
736 for (int pane = 0; pane < m_nBuffers; pane++)
738 bRO[pane] = !!m_pView[pane]->GetReadOnly();
739 theApp.m_strDescriptions[pane] = m_strDesc[pane];
741 int nActivePane = GetActiveMergeView()->m_nThisPane;
742 OpenDocs(m_filePaths, bRO);
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 CHexMergeView::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 CHexMergeView::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 CHexMergeView::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 CHexMergeView::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 CHexMergeView::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 CHexMergeView::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);