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 HexMergeView.cpp
24 * @brief Implementation file for CHexMergeView
29 #include "HexMergeFrm.h"
32 #include "HexMergeView.h"
33 #include "OptionsDef.h"
34 #include "OptionsMgr.h"
35 #include "Environment.h"
42 * @brief Turn bool api result into success/error code
44 static HRESULT NTAPI SE(BOOL f)
48 HRESULT hr = (HRESULT)::GetLastError();
55 static UINT64 NTAPI GetLastWriteTime(HANDLE h)
58 return ::GetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft)) ? ft : 0;
61 static void NTAPI SetLastWriteTime(HANDLE h, UINT64 ft)
63 ::SetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft));
66 /////////////////////////////////////////////////////////////////////////////
69 IMPLEMENT_DYNCREATE(CHexMergeView, CView)
71 BEGIN_MESSAGE_MAP(CHexMergeView, CView)
72 //{{AFX_MSG_MAP(CHexMergeView)
73 ON_MESSAGE_VOID(WM_PAINT, CWnd::OnPaint)
78 ON_COMMAND(ID_EDIT_FIND, OnEditFind)
79 ON_COMMAND(ID_EDIT_REPLACE, OnEditReplace)
80 ON_COMMAND(ID_EDIT_REPEAT, OnEditRepeat)
81 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
82 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
83 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
84 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
85 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
86 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
87 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
88 ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
89 ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
90 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
91 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
92 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
93 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
95 // Test case to verify WM_COMMAND won't accidentally go through Default()
96 //ON_COMMAND(ID_APP_ABOUT, Default)
99 /////////////////////////////////////////////////////////////////////////////
100 // CHexMergeView construction/destruction
103 * @brief Constructor.
105 CHexMergeView::CHexMergeView()
112 * @brief Drawing is not supported
114 void CHexMergeView::OnDraw(CDC *)
120 * @brief returns true if heksedit.dll is loadable
122 bool CHexMergeView::IsLoadable()
124 static void *pv = NULL;
127 pv = LoadLibrary(_T("Frhed\\hekseditU.dll"));
129 return pv != nullptr;
133 * @brief Load heksedit.dll and setup window class name
135 BOOL CHexMergeView::PreCreateWindow(CREATESTRUCT& cs)
138 LangMessageBox(IDS_FRHED_NOTINSTALLED, MB_OK);
139 cs.lpszClass = _T("heksedit");
140 cs.style |= WS_HSCROLL | WS_VSCROLL;
145 * @brief Grab the control's IHexEditorWindow interface pointer upon window creation
147 int CHexMergeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
149 if (CView::OnCreate(lpCreateStruct) == -1)
151 m_pif = reinterpret_cast<IHexEditorWindow *>(::GetWindowLongPtr(m_hWnd, GWLP_USERDATA));
152 if (m_pif == 0 || m_pif->get_interface_version() < HEKSEDIT_INTERFACE_VERSION)
158 * @brief Skip default WM_NCCALCSIZE processing so as to prevent scrollbars from showing up
160 void CHexMergeView::OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS *)
165 * @brief Synchronize all involved scrollbars
167 void CHexMergeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
170 if (pScrollBar && nSBCode == SB_THUMBTRACK)
172 pScrollBar->GetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
173 si.nPos = si.nTrackPos;
174 SetScrollInfo(SB_HORZ, &si);
176 CView::OnHScroll(nSBCode, nPos, pScrollBar);
179 GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
180 if (nSBCode != SB_THUMBTRACK)
182 pScrollBar->SetScrollInfo(&si);
185 CSplitterWndEx *pSplitter = static_cast<CSplitterWndEx *>(GetParentSplitter(this, TRUE));
186 for (int pane = 0; pane < pSplitter->GetColumnCount(); ++pane)
188 if (pane != m_nThisPane)
190 CWnd *pWnd = pSplitter->GetDlgItem(pSplitter->IdFromRowCol(0, pane));
191 pWnd->SetScrollInfo(SB_HORZ, &si);
192 pWnd->SendMessage(WM_HSCROLL, MAKEWPARAM(nSBCode, nPos));
199 * @brief Synchronize all involved scrollbars
201 void CHexMergeView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
204 if (pScrollBar && nSBCode == SB_THUMBTRACK)
206 pScrollBar->GetScrollInfo(&si);
207 si.nPos = si.nTrackPos;
208 SetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
210 CView::OnVScroll(nSBCode, nPos, pScrollBar);
211 if (pScrollBar && nSBCode != SB_THUMBTRACK)
213 GetScrollInfo(SB_VERT, &si);
214 pScrollBar->SetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
219 * @brief Synchronize file path bar activation states
221 void CHexMergeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
223 CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
224 CHexMergeFrame *pFrameWnd = static_cast<CHexMergeFrame *>(GetParentFrame());
225 pFrameWnd->GetHeaderInterface()->SetActive(m_nThisPane, !!bActivate);
229 * @brief Get pointer to control's content buffer
231 BYTE *CHexMergeView::GetBuffer(int length)
233 return m_pif->get_buffer(length);
237 * @brief Get length of control's content buffer
239 int CHexMergeView::GetLength()
241 return m_pif->get_length();
245 * @brief Checks if file has changed since last update
246 * @param [in] path File to check
247 * @return `true` if file is changed.
249 bool CHexMergeView::IsFileChangedOnDisk(LPCTSTR path)
254 if (GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME))
255 tolerance = SmallTimeDiff; // From MainFrm.h
256 int64_t timeDiff = dfi.mtime - m_fileInfo.mtime;
257 if (timeDiff < 0) timeDiff = -timeDiff;
258 if ((timeDiff > tolerance * Poco::Timestamp::resolution()) || (dfi.size != m_fileInfo.size))
266 HRESULT CHexMergeView::LoadFile(LPCTSTR path)
268 HANDLE h = CreateFile(path, GENERIC_READ,
269 FILE_SHARE_READ | FILE_SHARE_WRITE,
270 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
271 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
272 if (h == INVALID_HANDLE_VALUE)
274 DWORD length = GetFileSize(h, 0);
275 hr = SE(length != INVALID_FILE_SIZE);
278 if (void *buffer = GetBuffer(length))
281 hr = SE(ReadFile(h, buffer, length, &cb, 0) && cb == length);
285 else if (length != 0)
291 m_fileInfo.Update(path);
298 HRESULT CHexMergeView::SaveFile(LPCTSTR path)
300 // Warn user in case file has been changed by someone else
301 if (IsFileChangedOnDisk(path))
303 String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), path);
304 if (AfxMessageBox(msg.c_str(), MB_ICONWARNING | MB_YESNO) == IDNO)
307 // Ask user what to do about FILE_ATTRIBUTE_READONLY
308 String strPath = path;
309 bool bApplyToAll = false;
310 if (theApp.HandleReadonlySave(strPath, false, bApplyToAll) == IDCANCEL)
312 path = strPath.c_str();
313 // Take a chance to create a backup
314 if (!theApp.CreateBackup(false, path))
316 // Write data to an intermediate file
317 String tempPath = env::GetTemporaryPath();
318 String sIntermediateFilename = env::GetTemporaryFileName(tempPath, _T("MRG_"), 0);
319 if (sIntermediateFilename.empty())
320 return E_FAIL; //Nothing to do if even tempfile name fails
321 HANDLE h = CreateFile(sIntermediateFilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
322 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
323 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
324 if (h == INVALID_HANDLE_VALUE)
326 DWORD length = GetLength();
327 void *buffer = GetBuffer(length);
334 hr = SE(WriteFile(h, buffer, length, &cb, 0) && cb == length);
338 hr = SE(CopyFile(sIntermediateFilename.c_str(), path, FALSE));
341 m_fileInfo.Update(path);
343 hr = SE(DeleteFile(sIntermediateFilename.c_str()));
346 LogErrorString(strutils::format(_T("DeleteFile(%s) failed: %s"),
347 sIntermediateFilename.c_str(), GetSysError(hr).c_str()));
353 * @brief Get modified flag
355 bool CHexMergeView::GetModified()
357 return m_pif->get_status()->bFileChanged;
361 * @brief Set modified flag
363 void CHexMergeView::SetSavePoint()
365 m_pif->set_savepoint();
369 * @brief Clear undo records
371 void CHexMergeView::ClearUndoRecords()
373 m_pif->clear_undorecords();
377 * @brief Get readonly flag
379 bool CHexMergeView::GetReadOnly()
381 return m_pif->get_settings()->bReadOnly;
385 * @brief Set readonly flag
387 void CHexMergeView::SetReadOnly(bool bReadOnly)
389 m_pif->get_settings()->bReadOnly = bReadOnly;
393 * @brief Allow the control to update all kinds of things that need to be updated when
394 * the window or content buffer have been resized or certain settings have been changed.
396 void CHexMergeView::ResizeWindow()
398 m_pif->resize_window();
402 * @brief Find a sequence of bytes
404 void CHexMergeView::OnEditFind()
410 * @brief Find & replace a sequence of bytes
412 void CHexMergeView::OnEditReplace()
414 m_pif->CMD_replace();
418 * @brief Repeat last find in one or another direction
420 void CHexMergeView::OnEditRepeat()
422 if (GetKeyState(VK_SHIFT) < 0)
423 m_pif->CMD_findprev();
425 m_pif->CMD_findnext();
429 * @brief Called when "Undo" item is updated
431 void CHexMergeView::OnUpdateEditUndo(CCmdUI* pCmdUI)
433 pCmdUI->Enable(m_pif->can_undo());
437 * @brief Undo last action
439 void CHexMergeView::OnEditUndo()
441 m_pif->CMD_edit_undo();
445 * @brief Called when "Redo" item is updated
447 void CHexMergeView::OnUpdateEditRedo(CCmdUI* pCmdUI)
449 pCmdUI->Enable(m_pif->can_redo());
453 * @brief Redo last action
455 void CHexMergeView::OnEditRedo()
457 m_pif->CMD_edit_redo();
461 * @brief Cut selected content
463 void CHexMergeView::OnEditCut()
465 m_pif->CMD_edit_cut();
469 * @brief Copy selected content
471 void CHexMergeView::OnEditCopy()
473 m_pif->CMD_edit_copy();
477 * @brief Paste clipboard content over selected content
479 void CHexMergeView::OnEditPaste()
481 m_pif->CMD_edit_paste();
485 * @brief Select entire content
487 void CHexMergeView::OnEditSelectAll()
489 m_pif->CMD_select_all();
493 * @brief Clear selected content
495 void CHexMergeView::OnEditClear()
497 m_pif->CMD_edit_clear();
501 * @brief Check for keyboard commands
503 BOOL CHexMergeView::PreTranslateMessage(MSG* pMsg)
505 if (GetTopLevelFrame()->PreTranslateMessage(pMsg))
507 if (pMsg->message == WM_KEYDOWN)
509 // Close window in response to VK_ESCAPE if user has allowed it from options
510 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC))
512 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
516 return m_pif->translate_accelerator(pMsg);
520 * @brief Go to first diff
522 void CHexMergeView::OnFirstdiff()
524 m_pif->select_next_diff(TRUE);
528 * @brief Go to last diff
530 void CHexMergeView::OnLastdiff()
532 m_pif->select_prev_diff(TRUE);
536 * @brief Go to next diff
538 void CHexMergeView::OnNextdiff()
540 m_pif->select_next_diff(FALSE);
544 * @brief Go to previous diff
546 void CHexMergeView::OnPrevdiff()
548 m_pif->select_prev_diff(FALSE);
551 void CHexMergeView::ZoomText(int amount)
553 m_pif->CMD_zoom(amount);
557 * @brief Copy selected bytes from source view to destination view
558 * @note Grows destination buffer as appropriate
560 void CHexMergeView::CopySel(const CHexMergeView *src, CHexMergeView *dst)
562 dst->m_pif->copy_sel_from(src->m_pif);
566 * @brief Copy all bytes from source view to destination view
567 * @note Grows destination buffer as appropriate
569 void CHexMergeView::CopyAll(const CHexMergeView *src, CHexMergeView *dst)
571 dst->m_pif->copy_all_from(src->m_pif);