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"
55 int CHexMergeDoc::m_nBuffersTemp = 2;
57 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt);
58 static int Try(HRESULT hr, UINT type = MB_OKCANCEL|MB_ICONSTOP);
61 * @brief Update diff item
63 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt)
65 di.diffcode.diffcode |= DIFFCODE::SIDEFLAGS;
66 for (int nBuffer = 0; nBuffer < nBuffers; nBuffer++)
68 di.diffFileInfo[nBuffer].ClearPartial();
69 di.diffFileInfo[nBuffer].ClearPartial();
70 if (!pCtxt->UpdateInfoFromDiskHalf(di, nBuffer))
73 di.diffcode.diffcode &= ~DIFFCODE::FIRST;
74 else if (nBuffer == 1)
75 di.diffcode.diffcode &= ~DIFFCODE::SECOND;
77 di.diffcode.diffcode &= ~DIFFCODE::THIRD;
81 di.diffcode.diffcode &= ~(DIFFCODE::TEXTFLAGS | DIFFCODE::COMPAREFLAGS);
82 // 2. Process unique files
83 // We must compare unique files to itself to detect encoding
84 if (!di.diffcode.existAll(nBuffers))
86 int compareMethod = pCtxt->GetCompareMethod();
87 if (compareMethod != CMP_DATE && compareMethod != CMP_DATE_SIZE &&
88 compareMethod != CMP_SIZE)
90 di.diffcode.diffcode |= DIFFCODE::SAME;
92 int diffCode = folderCmp.prepAndCompareFiles(pCtxt, di);
93 // Add possible binary flag for unique items
94 if (diffCode & DIFFCODE::BIN)
95 di.diffcode.diffcode |= DIFFCODE::BIN;
98 // 3. Compare two files
103 di.diffcode.diffcode |= folderCmp.prepAndCompareFiles(pCtxt, di);
108 * @brief Issue an error popup if passed in HRESULT is nonzero
110 static int Try(HRESULT hr, UINT type)
112 return hr ? CInternetException(hr).ReportError(type) : 0;
115 /////////////////////////////////////////////////////////////////////////////
118 IMPLEMENT_DYNCREATE(CHexMergeDoc, CDocument)
120 BEGIN_MESSAGE_MAP(CHexMergeDoc, CDocument)
121 //{{AFX_MSG_MAP(CHexMergeDoc)
122 ON_COMMAND(ID_FILE_SAVE, OnFileSave)
123 ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
124 ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
125 ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
126 ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
127 ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
128 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
129 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
130 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
131 ON_COMMAND(ID_RESCAN, OnFileReload)
132 ON_COMMAND(ID_L2R, OnL2r)
133 ON_COMMAND(ID_R2L, OnR2l)
134 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
135 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
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 m_filePaths.SetSize(m_nBuffers);
155 std::fill_n(m_pView, m_nBuffers, static_cast<CHexMergeView *>(NULL));
156 std::fill_n(m_nBufferType, m_nBuffers, BUFFER_NORMAL);
162 * Informs associated dirdoc that mergedoc is closing.
164 CHexMergeDoc::~CHexMergeDoc()
167 m_pDirDoc->MergeDocClosing(this);
171 * @brief Return active merge edit view (or left one if neither active)
173 CHexMergeView * CHexMergeDoc::GetActiveMergeView() const
175 CView * pActiveView = GetParentFrame()->GetActiveView();
176 CHexMergeView * pHexMergeView = dynamic_cast<CHexMergeView *>(pActiveView);
178 pHexMergeView = m_pView[0]; // default to left view (in case some location or detail view active)
179 return pHexMergeView;
183 * @brief Update associated diff item
185 void CHexMergeDoc::UpdateDiffItem(CDirDoc *pDirDoc)
187 // If directory compare has results
188 if (pDirDoc && pDirDoc->HasDiffs())
190 const String &pathLeft = m_filePaths.GetLeft();
191 const String &pathRight = m_filePaths.GetRight();
192 CDiffContext &ctxt = pDirDoc->GetDiffContext();
193 if (UINT_PTR pos = FindItemFromPaths(ctxt, pathLeft, pathRight))
195 DIFFITEM &di = ctxt.GetDiffRefAt(pos);
196 ::UpdateDiffItem(m_nBuffers, di, &ctxt);
200 int lengthFirst = m_pView[0]->GetLength();
201 void *bufferFirst = m_pView[0]->GetBuffer(lengthFirst);
202 for (int nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
204 int length = m_pView[nBuffer]->GetLength();
205 if (lengthFirst != length)
209 void *buffer = m_pView[nBuffer]->GetBuffer(length);
210 bDiff = (memcmp(bufferFirst, buffer, lengthFirst) != 0);
215 GetParentFrame()->SetLastCompareResult(bDiff);
219 * @brief Asks and then saves modified files
221 BOOL CHexMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
223 bool bLModified = false, bMModified = false, bRModified = false;
227 bLModified = !!m_pView[0]->GetModified();
228 bMModified = !!m_pView[1]->GetModified();
229 bRModified = !!m_pView[2]->GetModified();
233 bLModified = !!m_pView[0]->GetModified();
234 bRModified = !!m_pView[1]->GetModified();
236 if (!bLModified && !bMModified && !bRModified)
239 const String &pathLeft = m_filePaths.GetLeft();
240 const String &pathMiddle = m_filePaths.GetMiddle();
241 const String &pathRight = m_filePaths.GetRight();
244 bool bLSaveSuccess = false, bMSaveSuccess = false, bRSaveSuccess = false;
247 dlg.DoAskFor(bLModified, bMModified, bRModified);
249 dlg.m_bDisableCancel = true;
250 if (!pathLeft.empty())
251 dlg.m_sLeftFile = pathLeft;
253 dlg.m_sLeftFile = m_strDesc[0];
256 if (!pathMiddle.empty())
257 dlg.m_sMiddleFile = pathMiddle;
259 dlg.m_sMiddleFile = m_strDesc[1];
261 if (!pathRight.empty())
262 dlg.m_sRightFile = pathRight;
264 dlg.m_sRightFile = m_strDesc[1];
266 if (dlg.DoModal() == IDOK)
270 if (dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
272 switch (Try(m_pView[0]->SaveFile(pathLeft.c_str())))
275 bLSaveSuccess = TRUE;
284 m_pView[0]->SetSavePoint();
289 if (dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
291 switch (Try(m_pView[1]->SaveFile(pathMiddle.c_str())))
294 bMSaveSuccess = TRUE;
303 m_pView[1]->SetSavePoint();
308 if (dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
310 switch (Try(m_pView[m_nBuffers - 1]->SaveFile(pathRight.c_str())))
313 bRSaveSuccess = TRUE;
322 m_pView[m_nBuffers - 1]->SetSavePoint();
331 // If file were modified and saving was successfull,
332 // update status on dir view
333 if (bLSaveSuccess || bMSaveSuccess || bRSaveSuccess)
335 UpdateDiffItem(m_pDirDoc);
342 * @brief Save modified documents
344 BOOL CHexMergeDoc::SaveModified()
346 return PromptAndSaveIfNeeded(TRUE);
350 * @brief Saves both files
352 void CHexMergeDoc::OnFileSave()
354 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
358 void CHexMergeDoc::DoFileSave(int nBuffer)
360 if (m_pView[nBuffer]->GetModified())
362 if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
363 DoFileSaveAs(nBuffer);
366 const String &path = m_filePaths.GetPath(nBuffer);
367 if (Try(m_pView[nBuffer]->SaveFile(path.c_str())) == IDCANCEL)
370 UpdateDiffItem(m_pDirDoc);
374 void CHexMergeDoc::DoFileSaveAs(int nBuffer)
376 const String &path = m_filePaths.GetPath(nBuffer);
380 title = _("Save Left File As");
381 else if (nBuffer == m_nBuffers - 1)
382 title = _("Save Right File As");
384 title = _("Save Middle File As");
385 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, path.c_str(), title, _T(""), FALSE))
387 if (Try(m_pView[nBuffer]->SaveFile(strPath.c_str())) == IDCANCEL)
391 // We are saving scratchpad (unnamed file)
392 m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
393 m_strDesc[nBuffer].erase();
396 m_filePaths.SetPath(nBuffer, strPath);
397 UpdateDiffItem(m_pDirDoc);
398 UpdateHeaderPath(nBuffer);
403 * @brief Saves left-side file
405 void CHexMergeDoc::OnFileSaveLeft()
411 * @brief Saves middle-side file
413 void CHexMergeDoc::OnFileSaveMiddle()
419 * @brief Saves right-side file
421 void CHexMergeDoc::OnFileSaveRight()
423 DoFileSave(m_nBuffers - 1);
427 * @brief Saves left-side file with name asked
429 void CHexMergeDoc::OnFileSaveAsLeft()
435 * @brief Saves right-side file with name asked
437 void CHexMergeDoc::OnFileSaveAsMiddle()
443 * @brief Saves right-side file with name asked
445 void CHexMergeDoc::OnFileSaveAsRight()
447 DoFileSaveAs(m_nBuffers - 1);
451 * @brief Update diff-number pane text
453 void CHexMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI)
456 pCmdUI->SetText(s.c_str());
460 * @brief DirDoc gives us its identity just after it creates us
462 void CHexMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
464 ASSERT(pDirDoc && !m_pDirDoc);
469 * @brief Return pointer to parent frame
471 CHexMergeFrame * CHexMergeDoc::GetParentFrame() const
473 return static_cast<CHexMergeFrame *>(m_pView[0]->GetParentFrame());
477 * @brief DirDoc is closing
479 void CHexMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
481 ASSERT(m_pDirDoc == pDirDoc);
486 * @brief DirDoc commanding us to close
488 bool CHexMergeDoc::CloseNow()
490 // Allow user to cancel closing
491 if (!PromptAndSaveIfNeeded(TRUE))
494 GetParentFrame()->CloseNow();
499 * @brief Load one file
501 HRESULT CHexMergeDoc::LoadOneFile(int index, LPCTSTR filename, BOOL readOnly)
505 if (Try(m_pView[index]->LoadFile(filename), MB_ICONSTOP) != 0)
507 m_pView[index]->SetReadOnly(readOnly);
508 m_filePaths.SetPath(index, filename);
509 ASSERT(m_nBufferType[index] == BUFFER_NORMAL); // should have been initialized to BUFFER_NORMAL in constructor
510 String strDesc = theApp.m_strDescriptions[index];
511 if (!strDesc.empty())
513 m_strDesc[index] = strDesc;
514 m_nBufferType[index] = BUFFER_NORMAL_NAMED;
519 m_nBufferType[index] = BUFFER_UNNAMED;
520 m_strDesc[index] = theApp.m_strDescriptions[index];
523 UpdateHeaderPath(index);
524 m_pView[index]->ResizeWindow();
529 * @brief Load files and initialize frame's compare result icon
531 HRESULT CHexMergeDoc::OpenDocs(const PathContext &paths, const bool bRO[])
533 CHexMergeFrame *pf = GetParentFrame();
537 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
539 if (FAILED(hr = LoadOneFile(nBuffer, paths.GetPath(nBuffer).c_str(), bRO[nBuffer])))
542 if (nBuffer == m_nBuffers)
545 // An extra ResizeWindow() on the left view aligns scroll ranges, and
546 // also triggers initial diff coloring by invalidating the client area.
547 m_pView[0]->ResizeWindow();
548 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
549 m_pView[0]->SendMessage(WM_COMMAND, ID_FIRSTDIFF);
553 // Use verify macro to trap possible error in debug.
554 VERIFY(pf->DestroyWindow());
559 void CHexMergeDoc::CheckFileChanged(void)
561 for (int pane = 0; pane < m_nBuffers; ++pane)
563 if (m_pView[pane]->IsFileChangedOnDisk(m_filePaths[pane].c_str()))
565 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]);
566 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING) == IDYES)
576 * @brief Write path and filename to headerbar
577 * @note SetText() does not repaint unchanged text
579 void CHexMergeDoc::UpdateHeaderPath(int pane)
581 CHexMergeFrame *pf = GetParentFrame();
585 if (m_nBufferType[pane] == BUFFER_UNNAMED ||
586 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
588 sText = m_strDesc[pane];
592 sText = m_filePaths.GetPath(pane);
594 m_pDirDoc->ApplyDisplayRoot(pane, sText);
596 if (m_pView[pane]->GetModified())
597 sText.insert(0, _T("* "));
598 pf->GetHeaderInterface()->SetText(pane, sText);
605 * @brief Customize a heksedit control's settings
607 static void Customize(IHexEditorWindow::Settings *settings)
609 settings->bSaveIni = FALSE;
610 //settings->iAutomaticBPL = FALSE;
611 //settings->iBytesPerLine = 16;
612 //settings->iFontSize = 8;
616 * @brief Customize a heksedit control's colors
618 static void Customize(IHexEditorWindow::Colors *colors)
620 COptionsMgr *pOptionsMgr = GetOptionsMgr();
621 colors->iSelBkColorValue = RGB(224, 224, 224);
622 colors->iDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF);
623 colors->iSelDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF);
624 colors->iDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF_TEXT);
625 if (colors->iDiffTextColorValue == 0xFFFFFFFF)
626 colors->iDiffTextColorValue = 0;
627 colors->iSelDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF_TEXT);
628 if (colors->iSelDiffTextColorValue == 0xFFFFFFFF)
629 colors->iSelDiffTextColorValue = 0;
633 * @brief Customize a heksedit control's settings and colors
635 static void Customize(IHexEditorWindow *pif)
637 Customize(pif->get_settings());
638 Customize(pif->get_colors());
639 //LANGID wLangID = (LANGID)GetThreadLocale();
640 //pif->load_lang(wLangID);
643 void CHexMergeDoc::RefreshOptions()
645 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
647 IHexEditorWindow *pif = m_pView[nBuffer]->GetInterface();
648 pif->read_ini_data();
650 pif->resize_window();
655 * @brief Update document filenames to title
657 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
666 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
667 sFileName[nBuffer] = !m_strDesc[nBuffer].empty() ? m_strDesc[nBuffer] : paths_FindFileName(m_filePaths[nBuffer]);
668 if (std::count(&sFileName[0], &sFileName[0] + m_nBuffers, sFileName[0]) == m_nBuffers)
669 sTitle = sFileName[0] + string_format(_T(" x %d"), m_nBuffers);
671 sTitle = string_join(&sFileName[0], &sFileName[0] + m_nBuffers, _T(" - "));
673 CDocument::SetTitle(sTitle.c_str());
677 * @brief We have two child views (left & right), so we keep pointers directly
678 * at them (the MFC view list doesn't have them both)
680 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
682 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
684 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
685 m_pView[nBuffer] = pView[nBuffer];
686 m_pView[nBuffer]->m_nThisPane = nBuffer;
691 * @brief Called when "Save left" item is updated
693 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
695 pCmdUI->Enable(m_pView[0]->GetModified());
699 * @brief Called when "Save middle" item is updated
701 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
703 pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
707 * @brief Called when "Save right" item is updated
709 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
711 pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
715 * @brief Called when "Save" item is updated
717 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
719 BOOL bModified = FALSE;
720 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
721 bModified |= m_pView[nBuffer]->GetModified();
722 pCmdUI->Enable(bModified);
726 * @brief Reloads the opened files
728 void CHexMergeDoc::OnFileReload()
730 if (!PromptAndSaveIfNeeded(true))
734 for (int pane = 0; pane < m_nBuffers; pane++)
736 bRO[pane] = !!m_pView[pane]->GetReadOnly();
737 theApp.m_strDescriptions[pane] = m_strDesc[pane];
739 int nActivePane = GetActiveMergeView()->m_nThisPane;
740 OpenDocs(m_filePaths, bRO);
744 * @brief Copy selected bytes from left to right
746 void CHexMergeDoc::OnL2r()
748 int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
749 int srcPane = dstPane - 1;
750 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
754 * @brief Copy selected bytes from right to left
756 void CHexMergeDoc::OnR2l()
758 int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
759 int srcPane = dstPane + 1;
760 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
764 * @brief Copy selected bytes from left to active pane
766 void CHexMergeDoc::OnCopyFromLeft()
768 int dstPane = GetActiveMergeView()->m_nThisPane;
769 int srcPane = (dstPane - 1 < 0) ? 0 : dstPane - 1;
770 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
774 * @brief Copy selected bytes from right to active pane
776 void CHexMergeDoc::OnCopyFromRight()
778 int dstPane = GetActiveMergeView()->m_nThisPane;
779 int srcPane = (dstPane + 1 > m_nBuffers - 1) ? m_nBuffers - 1 : dstPane + 1;
780 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
784 * @brief Copy all bytes from left to right
786 void CHexMergeDoc::OnAllRight()
788 int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
789 int srcPane = dstPane - 1;
790 CHexMergeView::CopyAll(m_pView[srcPane], m_pView[dstPane]);
794 * @brief Copy all bytes from right to left
796 void CHexMergeDoc::OnAllLeft()
798 int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
799 int srcPane = dstPane + 1;
800 CHexMergeView::CopyAll(m_pView[srcPane], m_pView[dstPane]);
804 * @brief Called when user selects View/Zoom In from menu.
806 void CHexMergeDoc::OnViewZoomIn()
808 for (int pane = 0; pane < m_nBuffers; pane++)
809 m_pView[pane]->ZoomText(1);
813 * @brief Called when user selects View/Zoom Out from menu.
815 void CHexMergeDoc::OnViewZoomOut()
817 for (int pane = 0; pane < m_nBuffers; pane++)
818 m_pView[pane]->ZoomText(-1);
822 * @brief Called when user selects View/Zoom Normal from menu.
824 void CHexMergeDoc::OnViewZoomNormal()
826 for (int pane = 0; pane < m_nBuffers; pane++)
827 m_pView[pane]->ZoomText(0);