1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file HexMergeView.cpp
10 * @brief Implementation file for CHexMergeView
15 #include "HexMergeFrm.h"
18 #include "HexMergeView.h"
19 #include "HexMergeDoc.h"
20 #include "OptionsDef.h"
21 #include "OptionsMgr.h"
22 #include "Environment.h"
28 /** @brief Location for hex compare specific help to open. */
29 static TCHAR HexMergeViewHelpLocation[] = _T("::/htmlhelp/Compare_bin.html");
32 * @brief Turn bool api result into success/error code
34 static HRESULT NTAPI SE(BOOL f)
38 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
45 static UINT64 NTAPI GetLastWriteTime(HANDLE h)
48 return ::GetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft)) ? ft : 0;
51 static void NTAPI SetLastWriteTime(HANDLE h, UINT64 ft)
53 ::SetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft));
56 /////////////////////////////////////////////////////////////////////////////
59 IMPLEMENT_DYNCREATE(CHexMergeView, CView)
61 BEGIN_MESSAGE_MAP(CHexMergeView, CView)
62 //{{AFX_MSG_MAP(CHexMergeView)
63 ON_MESSAGE_VOID(WM_PAINT, CWnd::OnPaint)
69 ON_COMMAND(ID_HELP, OnHelp)
70 ON_COMMAND(ID_EDIT_FIND, OnEditFind)
71 ON_COMMAND(ID_EDIT_REPLACE, OnEditReplace)
72 ON_COMMAND(ID_EDIT_REPEAT, OnEditRepeat)
73 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
74 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
75 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
76 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
77 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
78 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
79 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
80 ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
81 ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
82 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
83 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
84 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
85 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
87 // Test case to verify WM_COMMAND won't accidentally go through Default()
88 //ON_COMMAND(ID_APP_ABOUT, Default)
91 /////////////////////////////////////////////////////////////////////////////
92 // CHexMergeView construction/destruction
97 CHexMergeView::CHexMergeView()
104 * @brief Drawing is not supported
106 void CHexMergeView::OnDraw(CDC *)
112 * @brief returns true if heksedit.dll is loadable
114 bool CHexMergeView::IsLoadable()
116 static void *pv = nullptr;
119 pv = LoadLibrary(_T("Frhed\\hekseditU.dll"));
121 return pv != nullptr;
125 * @brief Load heksedit.dll and setup window class name
127 BOOL CHexMergeView::PreCreateWindow(CREATESTRUCT& cs)
130 LangMessageBox(IDS_FRHED_NOTINSTALLED, MB_OK);
131 cs.lpszClass = _T("heksedit");
132 cs.style |= WS_HSCROLL | WS_VSCROLL;
137 * @brief Grab the control's IHexEditorWindow interface pointer upon window creation
139 int CHexMergeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
141 if (CView::OnCreate(lpCreateStruct) == -1)
143 m_pif = reinterpret_cast<IHexEditorWindow *>(::GetWindowLongPtr(m_hWnd, GWLP_USERDATA));
144 if (m_pif == nullptr || m_pif->get_interface_version() < HEKSEDIT_INTERFACE_VERSION)
150 * @brief Skip default WM_NCCALCSIZE processing so as to prevent scrollbars from showing up
152 void CHexMergeView::OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS *)
157 * @brief Synchronize all involved scrollbars
159 void CHexMergeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
162 if (pScrollBar && nSBCode == SB_THUMBTRACK)
164 pScrollBar->GetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
165 si.nPos = si.nTrackPos;
166 SetScrollInfo(SB_HORZ, &si);
168 CView::OnHScroll(nSBCode, nPos, pScrollBar);
169 if (pScrollBar != nullptr)
171 GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
172 if (nSBCode != SB_THUMBTRACK)
174 pScrollBar->SetScrollInfo(&si);
177 CSplitterWndEx *pSplitter = static_cast<CSplitterWndEx *>(GetParentSplitter(this, TRUE));
178 for (int pane = 0; pane < pSplitter->GetColumnCount(); ++pane)
180 if (pane != m_nThisPane)
182 CWnd *pWnd = pSplitter->GetDlgItem(pSplitter->IdFromRowCol(0, pane));
183 pWnd->SetScrollInfo(SB_HORZ, &si);
184 pWnd->SendMessage(WM_HSCROLL, MAKEWPARAM(nSBCode, nPos));
191 * @brief Synchronize all involved scrollbars
193 void CHexMergeView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
196 if (pScrollBar && nSBCode == SB_THUMBTRACK)
198 pScrollBar->GetScrollInfo(&si);
199 si.nPos = si.nTrackPos;
200 SetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
202 CView::OnVScroll(nSBCode, nPos, pScrollBar);
203 if (pScrollBar && nSBCode != SB_THUMBTRACK)
205 GetScrollInfo(SB_VERT, &si);
206 pScrollBar->SetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
210 BOOL CHexMergeView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
212 if ((GetAsyncKeyState(VK_CONTROL) &0x8000) != 0) // if (nFlags & MK_CONTROL)
214 PostMessage(WM_COMMAND, zDelta < 0 ? ID_VIEW_ZOOMOUT : ID_VIEW_ZOOMIN);
221 * @brief Synchronize file path bar activation states
223 void CHexMergeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
225 CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
226 CHexMergeFrame *pFrameWnd = static_cast<CHexMergeFrame *>(GetParentFrame());
227 pFrameWnd->GetHeaderInterface()->SetActive(m_nThisPane, !!bActivate);
231 * @brief Get pointer to control's content buffer
233 BYTE *CHexMergeView::GetBuffer(int length)
235 return m_pif->get_buffer(length);
239 * @brief Get length of control's content buffer
241 int CHexMergeView::GetLength()
243 return m_pif->get_length();
247 * @brief Checks if file has changed since last update
248 * @param [in] path File to check
249 * @return `true` if file is changed.
251 IMergeDoc::FileChange CHexMergeView::IsFileChangedOnDisk(LPCTSTR path)
254 if (!dfi.Update(path))
255 return IMergeDoc::FileChange::Removed;
257 if (GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME))
258 tolerance = SmallTimeDiff; // From MainFrm.h
259 int64_t timeDiff = dfi.mtime - m_fileInfo.mtime;
260 if (timeDiff < 0) timeDiff = -timeDiff;
261 if ((timeDiff > tolerance * Poco::Timestamp::resolution()) || (dfi.size != m_fileInfo.size))
262 return IMergeDoc::FileChange::Changed;
263 return IMergeDoc::FileChange::NoChange;
269 HRESULT CHexMergeView::LoadFile(LPCTSTR path)
271 CHexMergeDoc *pDoc = static_cast<CHexMergeDoc *>(GetDocument());
272 String strTempFileName = path;
273 if (!pDoc->GetUnpacker()->Unpacking(&m_unpackerSubcodes, strTempFileName, path, { strTempFileName }))
275 HANDLE h = CreateFile(strTempFileName.c_str(), GENERIC_READ,
276 FILE_SHARE_READ | FILE_SHARE_WRITE,
277 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
278 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
279 if (h == INVALID_HANDLE_VALUE)
281 DWORD length = GetFileSize(h, 0);
282 hr = SE(length != INVALID_FILE_SIZE);
285 if (void *buffer = GetBuffer(length))
288 hr = SE(ReadFile(h, buffer, length, &cb, 0) && cb == length);
292 else if (length != 0)
298 m_fileInfo.Update(path);
305 HRESULT CHexMergeView::SaveFile(LPCTSTR path, bool packing)
307 // Warn user in case file has been changed by someone else
308 if (IsFileChangedOnDisk(path) == IMergeDoc::FileChange::Changed)
310 String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), path);
311 if (AfxMessageBox(msg.c_str(), MB_ICONWARNING | MB_YESNO) == IDNO)
314 // Ask user what to do about FILE_ATTRIBUTE_READONLY
315 String strPath = path;
316 bool bApplyToAll = false;
317 if (CMergeApp::HandleReadonlySave(strPath, false, bApplyToAll) == IDCANCEL)
319 path = strPath.c_str();
320 // Take a chance to create a backup
321 if (!CMergeApp::CreateBackup(false, path))
323 // Write data to an intermediate file
324 String tempPath = env::GetTemporaryPath();
325 String sIntermediateFilename = env::GetTemporaryFileName(tempPath, _T("MRG_"), 0);
326 if (sIntermediateFilename.empty())
327 return E_FAIL; //Nothing to do if even tempfile name fails
328 HANDLE h = CreateFile(sIntermediateFilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
329 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
330 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
331 if (h == INVALID_HANDLE_VALUE)
333 DWORD length = GetLength();
334 void *buffer = GetBuffer(length);
341 hr = SE(WriteFile(h, buffer, length, &cb, 0) && cb == length);
346 CHexMergeDoc* pDoc = static_cast<CHexMergeDoc*>(GetDocument());
347 if (packing && !pDoc->GetUnpacker()->GetPluginPipeline().empty())
349 if (!pDoc->GetUnpacker()->Packing(sIntermediateFilename, path, m_unpackerSubcodes, { path }))
351 String str = CMergeApp::GetPackingErrorMessage(m_nThisPane, pDoc->m_nBuffers, path, *pDoc->GetUnpacker());
352 int answer = AfxMessageBox(str.c_str(), MB_OKCANCEL | MB_ICONWARNING);
355 pDoc->SaveAs(m_nThisPane, false);
363 hr = SE(CopyFile(sIntermediateFilename.c_str(), path, FALSE));
368 m_fileInfo.Update(path);
370 hr = SE(DeleteFile(sIntermediateFilename.c_str()));
373 LogErrorString(strutils::format(_T("DeleteFile(%s) failed: %s"),
374 sIntermediateFilename, GetSysError(hr)));
380 * @brief Get modified flag
382 bool CHexMergeView::GetModified()
384 return m_pif->get_status()->iFileChanged != 0;
388 * @brief Set modified flag
390 void CHexMergeView::SetSavePoint()
392 m_pif->set_savepoint();
396 * @brief Clear undo records
398 void CHexMergeView::ClearUndoRecords()
400 m_pif->clear_undorecords();
404 * @brief Get readonly flag
406 bool CHexMergeView::GetReadOnly()
408 return m_pif->get_settings()->bReadOnly;
412 * @brief Set readonly flag
414 void CHexMergeView::SetReadOnly(bool bReadOnly)
416 m_pif->get_settings()->bReadOnly = bReadOnly;
420 * @brief Allow the control to update all kinds of things that need to be updated when
421 * the window or content buffer have been resized or certain settings have been changed.
423 void CHexMergeView::ResizeWindow()
425 m_pif->resize_window();
429 * @brief Find a sequence of bytes
431 void CHexMergeView::OnEditFind()
437 * @brief Find & replace a sequence of bytes
439 void CHexMergeView::OnEditReplace()
441 m_pif->CMD_replace();
445 * @brief Repeat last find in one or another direction
447 void CHexMergeView::OnEditRepeat()
449 if (GetKeyState(VK_SHIFT) < 0)
450 m_pif->CMD_findprev();
452 m_pif->CMD_findnext();
456 * @brief Called when "Undo" item is updated
458 void CHexMergeView::OnUpdateEditUndo(CCmdUI* pCmdUI)
460 pCmdUI->Enable(m_pif->can_undo());
464 * @brief Undo last action
466 void CHexMergeView::OnEditUndo()
468 m_pif->CMD_edit_undo();
472 * @brief Called when "Redo" item is updated
474 void CHexMergeView::OnUpdateEditRedo(CCmdUI* pCmdUI)
476 pCmdUI->Enable(m_pif->can_redo());
480 * @brief Redo last action
482 void CHexMergeView::OnEditRedo()
484 m_pif->CMD_edit_redo();
488 * @brief Cut selected content
490 void CHexMergeView::OnEditCut()
492 m_pif->CMD_edit_cut();
496 * @brief Copy selected content
498 void CHexMergeView::OnEditCopy()
500 m_pif->CMD_edit_copy();
504 * @brief Paste clipboard content over selected content
506 void CHexMergeView::OnEditPaste()
508 m_pif->CMD_edit_paste();
512 * @brief Select entire content
514 void CHexMergeView::OnEditSelectAll()
516 m_pif->CMD_select_all();
520 * @brief Clear selected content
522 void CHexMergeView::OnEditClear()
524 m_pif->CMD_edit_clear();
528 * @brief Check for keyboard commands
530 BOOL CHexMergeView::PreTranslateMessage(MSG* pMsg)
532 if (GetTopLevelFrame()->PreTranslateMessage(pMsg))
534 if (pMsg->message == WM_KEYDOWN)
536 // Close window in response to VK_ESCAPE if user has allowed it from options
537 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC) != 0)
539 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
543 return m_pif->translate_accelerator(pMsg);
547 * @brief Go to first diff
549 void CHexMergeView::OnFirstdiff()
551 m_pif->select_next_diff(TRUE);
555 * @brief Go to last diff
557 void CHexMergeView::OnLastdiff()
559 m_pif->select_prev_diff(TRUE);
563 * @brief Go to next diff
565 void CHexMergeView::OnNextdiff()
567 m_pif->select_next_diff(FALSE);
571 * @brief Go to previous diff
573 void CHexMergeView::OnPrevdiff()
575 m_pif->select_prev_diff(FALSE);
578 /** @brief Open help from mainframe when user presses F1*/
579 void CHexMergeView::OnHelp()
581 theApp.ShowHelp(HexMergeViewHelpLocation);
584 void CHexMergeView::ZoomText(int amount)
586 m_pif->CMD_zoom(amount);
590 * @brief Copy selected bytes from source view to destination view
591 * @note Grows destination buffer as appropriate
593 void CHexMergeView::CopySel(const CHexMergeView *src, CHexMergeView *dst)
595 dst->m_pif->copy_sel_from(src->m_pif);
599 * @brief Copy all bytes from source view to destination view
600 * @note Grows destination buffer as appropriate
602 void CHexMergeView::CopyAll(const CHexMergeView *src, CHexMergeView *dst)
604 dst->m_pif->copy_all_from(src->m_pif);