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 std::fill_n(m_pView, m_nBuffers, static_cast<CHexMergeView *>(NULL));
157 std::fill_n(m_nBufferType, m_nBuffers, BUFFER_NORMAL);
163 * Informs associated dirdoc that mergedoc is closing.
165 CHexMergeDoc::~CHexMergeDoc()
168 m_pDirDoc->MergeDocClosing(this);
172 * @brief Return active merge edit view (or left one if neither active)
174 CHexMergeView * CHexMergeDoc::GetActiveMergeView() const
176 CView * pActiveView = GetParentFrame()->GetActiveView();
177 CHexMergeView * pHexMergeView = dynamic_cast<CHexMergeView *>(pActiveView);
179 pHexMergeView = m_pView[0]; // default to left view (in case some location or detail view active)
180 return pHexMergeView;
184 * @brief Update associated diff item
186 void CHexMergeDoc::UpdateDiffItem(CDirDoc *pDirDoc)
188 // If directory compare has results
189 if (pDirDoc && pDirDoc->HasDiffs())
191 const String &pathLeft = m_filePaths.GetLeft();
192 const String &pathRight = m_filePaths.GetRight();
193 CDiffContext &ctxt = pDirDoc->GetDiffContext();
194 if (UINT_PTR pos = FindItemFromPaths(ctxt, pathLeft, pathRight))
196 DIFFITEM &di = ctxt.GetDiffRefAt(pos);
197 ::UpdateDiffItem(m_nBuffers, di, &ctxt);
201 int lengthFirst = m_pView[0]->GetLength();
202 void *bufferFirst = m_pView[0]->GetBuffer(lengthFirst);
203 for (int nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
205 int length = m_pView[nBuffer]->GetLength();
206 if (lengthFirst != length)
210 void *buffer = m_pView[nBuffer]->GetBuffer(length);
211 bDiff = (memcmp(bufferFirst, buffer, lengthFirst) != 0);
216 GetParentFrame()->SetLastCompareResult(bDiff);
220 * @brief Asks and then saves modified files
222 BOOL CHexMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
224 bool bLModified = false, bMModified = false, bRModified = false;
228 bLModified = !!m_pView[0]->GetModified();
229 bMModified = !!m_pView[1]->GetModified();
230 bRModified = !!m_pView[2]->GetModified();
234 bLModified = !!m_pView[0]->GetModified();
235 bRModified = !!m_pView[1]->GetModified();
237 if (!bLModified && !bMModified && !bRModified)
240 const String &pathLeft = m_filePaths.GetLeft();
241 const String &pathMiddle = m_filePaths.GetMiddle();
242 const String &pathRight = m_filePaths.GetRight();
245 bool bLSaveSuccess = false, bMSaveSuccess = false, bRSaveSuccess = false;
248 dlg.DoAskFor(bLModified, bMModified, bRModified);
250 dlg.m_bDisableCancel = true;
251 if (!pathLeft.empty())
252 dlg.m_sLeftFile = pathLeft;
254 dlg.m_sLeftFile = m_strDesc[0];
257 if (!pathMiddle.empty())
258 dlg.m_sMiddleFile = pathMiddle;
260 dlg.m_sMiddleFile = m_strDesc[1];
262 if (!pathRight.empty())
263 dlg.m_sRightFile = pathRight;
265 dlg.m_sRightFile = m_strDesc[1];
267 if (dlg.DoModal() == IDOK)
271 if (dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
273 switch (Try(m_pView[0]->SaveFile(pathLeft.c_str())))
276 bLSaveSuccess = TRUE;
285 m_pView[0]->SetSavePoint();
290 if (dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
292 switch (Try(m_pView[1]->SaveFile(pathMiddle.c_str())))
295 bMSaveSuccess = TRUE;
304 m_pView[1]->SetSavePoint();
309 if (dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
311 switch (Try(m_pView[m_nBuffers - 1]->SaveFile(pathRight.c_str())))
314 bRSaveSuccess = TRUE;
323 m_pView[m_nBuffers - 1]->SetSavePoint();
332 // If file were modified and saving was successfull,
333 // update status on dir view
334 if (bLSaveSuccess || bMSaveSuccess || bRSaveSuccess)
336 UpdateDiffItem(m_pDirDoc);
343 * @brief Save modified documents
345 BOOL CHexMergeDoc::SaveModified()
347 return PromptAndSaveIfNeeded(TRUE);
351 * @brief Saves both files
353 void CHexMergeDoc::OnFileSave()
355 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
359 void CHexMergeDoc::DoFileSave(int nBuffer)
361 if (m_pView[nBuffer]->GetModified())
363 if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
364 DoFileSaveAs(nBuffer);
367 const String &path = m_filePaths.GetPath(nBuffer);
368 if (Try(m_pView[nBuffer]->SaveFile(path.c_str())) == IDCANCEL)
371 UpdateDiffItem(m_pDirDoc);
375 void CHexMergeDoc::DoFileSaveAs(int nBuffer)
377 const String &path = m_filePaths.GetPath(nBuffer);
381 title = _("Save Left File As");
382 else if (nBuffer == m_nBuffers - 1)
383 title = _("Save Right File As");
385 title = _("Save Middle File As");
386 if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, path.c_str(), title, _T(""), FALSE))
388 if (Try(m_pView[nBuffer]->SaveFile(strPath.c_str())) == IDCANCEL)
392 // We are saving scratchpad (unnamed file)
393 m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
394 m_strDesc[nBuffer].erase();
397 m_filePaths.SetPath(nBuffer, strPath);
398 UpdateDiffItem(m_pDirDoc);
399 UpdateHeaderPath(nBuffer);
404 * @brief Saves left-side file
406 void CHexMergeDoc::OnFileSaveLeft()
412 * @brief Saves middle-side file
414 void CHexMergeDoc::OnFileSaveMiddle()
420 * @brief Saves right-side file
422 void CHexMergeDoc::OnFileSaveRight()
424 DoFileSave(m_nBuffers - 1);
428 * @brief Saves left-side file with name asked
430 void CHexMergeDoc::OnFileSaveAsLeft()
436 * @brief Saves right-side file with name asked
438 void CHexMergeDoc::OnFileSaveAsMiddle()
444 * @brief Saves right-side file with name asked
446 void CHexMergeDoc::OnFileSaveAsRight()
448 DoFileSaveAs(m_nBuffers - 1);
452 * @brief Update diff-number pane text
454 void CHexMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI)
457 pCmdUI->SetText(s.c_str());
461 * @brief DirDoc gives us its identity just after it creates us
463 void CHexMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
465 ASSERT(pDirDoc && !m_pDirDoc);
470 * @brief Return pointer to parent frame
472 CHexMergeFrame * CHexMergeDoc::GetParentFrame() const
474 return static_cast<CHexMergeFrame *>(m_pView[0]->GetParentFrame());
478 * @brief DirDoc is closing
480 void CHexMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
482 ASSERT(m_pDirDoc == pDirDoc);
487 * @brief DirDoc commanding us to close
489 bool CHexMergeDoc::CloseNow()
491 // Allow user to cancel closing
492 if (!PromptAndSaveIfNeeded(TRUE))
495 GetParentFrame()->CloseNow();
500 * @brief Load one file
502 HRESULT CHexMergeDoc::LoadOneFile(int index, LPCTSTR filename, BOOL readOnly)
506 if (Try(m_pView[index]->LoadFile(filename), MB_ICONSTOP) != 0)
508 m_pView[index]->SetReadOnly(readOnly);
509 m_filePaths.SetPath(index, filename);
510 ASSERT(m_nBufferType[index] == BUFFER_NORMAL); // should have been initialized to BUFFER_NORMAL in constructor
511 String strDesc = theApp.m_strDescriptions[index];
512 if (!strDesc.empty())
514 m_strDesc[index] = strDesc;
515 m_nBufferType[index] = BUFFER_NORMAL_NAMED;
520 m_nBufferType[index] = BUFFER_UNNAMED;
521 m_strDesc[index] = theApp.m_strDescriptions[index];
524 UpdateHeaderPath(index);
525 m_pView[index]->ResizeWindow();
530 * @brief Load files and initialize frame's compare result icon
532 HRESULT CHexMergeDoc::OpenDocs(const PathContext &paths, const bool bRO[])
534 CHexMergeFrame *pf = GetParentFrame();
538 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
540 if (FAILED(hr = LoadOneFile(nBuffer, paths.GetPath(nBuffer).c_str(), bRO[nBuffer])))
543 if (nBuffer == m_nBuffers)
546 // An extra ResizeWindow() on the left view aligns scroll ranges, and
547 // also triggers initial diff coloring by invalidating the client area.
548 m_pView[0]->ResizeWindow();
549 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
550 m_pView[0]->SendMessage(WM_COMMAND, ID_FIRSTDIFF);
554 // Use verify macro to trap possible error in debug.
555 VERIFY(pf->DestroyWindow());
560 void CHexMergeDoc::CheckFileChanged(void)
562 for (int pane = 0; pane < m_nBuffers; ++pane)
564 if (m_pView[pane]->IsFileChangedOnDisk(m_filePaths[pane].c_str()))
566 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]);
567 if (AfxMessageBox(msg.c_str(), MB_YESNO | MB_ICONWARNING) == IDYES)
577 * @brief Write path and filename to headerbar
578 * @note SetText() does not repaint unchanged text
580 void CHexMergeDoc::UpdateHeaderPath(int pane)
582 CHexMergeFrame *pf = GetParentFrame();
586 if (m_nBufferType[pane] == BUFFER_UNNAMED ||
587 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
589 sText = m_strDesc[pane];
593 sText = m_filePaths.GetPath(pane);
595 m_pDirDoc->ApplyDisplayRoot(pane, sText);
597 if (m_pView[pane]->GetModified())
598 sText.insert(0, _T("* "));
599 pf->GetHeaderInterface()->SetText(pane, sText);
606 * @brief Customize a heksedit control's settings
608 static void Customize(IHexEditorWindow::Settings *settings)
610 settings->bSaveIni = FALSE;
611 //settings->iAutomaticBPL = FALSE;
612 //settings->iBytesPerLine = 16;
613 //settings->iFontSize = 8;
617 * @brief Customize a heksedit control's colors
619 static void Customize(IHexEditorWindow::Colors *colors)
621 COptionsMgr *pOptionsMgr = GetOptionsMgr();
622 colors->iSelBkColorValue = RGB(224, 224, 224);
623 colors->iDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF);
624 colors->iSelDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF);
625 colors->iDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF_TEXT);
626 if (colors->iDiffTextColorValue == 0xFFFFFFFF)
627 colors->iDiffTextColorValue = 0;
628 colors->iSelDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF_TEXT);
629 if (colors->iSelDiffTextColorValue == 0xFFFFFFFF)
630 colors->iSelDiffTextColorValue = 0;
634 * @brief Customize a heksedit control's settings and colors
636 static void Customize(IHexEditorWindow *pif)
638 Customize(pif->get_settings());
639 Customize(pif->get_colors());
640 //LANGID wLangID = (LANGID)GetThreadLocale();
641 //pif->load_lang(wLangID);
644 void CHexMergeDoc::RefreshOptions()
646 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
648 IHexEditorWindow *pif = m_pView[nBuffer]->GetInterface();
649 pif->read_ini_data();
651 pif->resize_window();
656 * @brief Update document filenames to title
658 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
667 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
668 sFileName[nBuffer] = !m_strDesc[nBuffer].empty() ? m_strDesc[nBuffer] : paths_FindFileName(m_filePaths[nBuffer]);
669 if (std::count(&sFileName[0], &sFileName[0] + m_nBuffers, sFileName[0]) == m_nBuffers)
670 sTitle = sFileName[0] + string_format(_T(" x %d"), m_nBuffers);
672 sTitle = string_join(&sFileName[0], &sFileName[0] + m_nBuffers, _T(" - "));
674 CDocument::SetTitle(sTitle.c_str());
678 * @brief We have two child views (left & right), so we keep pointers directly
679 * at them (the MFC view list doesn't have them both)
681 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
683 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
685 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
686 m_pView[nBuffer] = pView[nBuffer];
687 m_pView[nBuffer]->m_nThisPane = nBuffer;
692 * @brief Called when "Save left" item is updated
694 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
696 pCmdUI->Enable(m_pView[0]->GetModified());
700 * @brief Called when "Save middle" item is updated
702 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
704 pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
708 * @brief Called when "Save right" item is updated
710 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
712 pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
716 * @brief Called when "Save" item is updated
718 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
720 BOOL bModified = FALSE;
721 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
722 bModified |= m_pView[nBuffer]->GetModified();
723 pCmdUI->Enable(bModified);
727 * @brief Reloads the opened files
729 void CHexMergeDoc::OnFileReload()
731 if (!PromptAndSaveIfNeeded(true))
735 for (int pane = 0; pane < m_nBuffers; pane++)
737 bRO[pane] = !!m_pView[pane]->GetReadOnly();
738 theApp.m_strDescriptions[pane] = m_strDesc[pane];
740 int nActivePane = GetActiveMergeView()->m_nThisPane;
741 OpenDocs(m_filePaths, bRO);
745 * @brief Copy selected bytes from left to right
747 void CHexMergeDoc::OnL2r()
749 int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
750 int srcPane = dstPane - 1;
751 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
755 * @brief Copy selected bytes from right to left
757 void CHexMergeDoc::OnR2l()
759 int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
760 int srcPane = dstPane + 1;
761 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
765 * @brief Copy selected bytes from left to active pane
767 void CHexMergeDoc::OnCopyFromLeft()
769 int dstPane = GetActiveMergeView()->m_nThisPane;
770 int srcPane = (dstPane - 1 < 0) ? 0 : dstPane - 1;
771 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
775 * @brief Copy selected bytes from right to active pane
777 void CHexMergeDoc::OnCopyFromRight()
779 int dstPane = GetActiveMergeView()->m_nThisPane;
780 int srcPane = (dstPane + 1 > m_nBuffers - 1) ? m_nBuffers - 1 : dstPane + 1;
781 CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
785 * @brief Copy all bytes from left to right
787 void CHexMergeDoc::OnAllRight()
789 int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
790 int srcPane = dstPane - 1;
791 CHexMergeView::CopyAll(m_pView[srcPane], m_pView[dstPane]);
795 * @brief Copy all bytes from right to left
797 void CHexMergeDoc::OnAllLeft()
799 int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
800 int srcPane = dstPane + 1;
801 CHexMergeView::CopyAll(m_pView[srcPane], m_pView[dstPane]);
805 * @brief Called when user selects View/Zoom In from menu.
807 void CHexMergeDoc::OnViewZoomIn()
809 for (int pane = 0; pane < m_nBuffers; pane++)
810 m_pView[pane]->ZoomText(1);
814 * @brief Called when user selects View/Zoom Out from menu.
816 void CHexMergeDoc::OnViewZoomOut()
818 for (int pane = 0; pane < m_nBuffers; pane++)
819 m_pView[pane]->ZoomText(-1);
823 * @brief Called when user selects View/Zoom Normal from menu.
825 void CHexMergeDoc::OnViewZoomNormal()
827 for (int pane = 0; pane < m_nBuffers; pane++)
828 m_pView[pane]->ZoomText(0);