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 CHexMergeDoc
27 // ID line follows -- this is updated by SVN
28 // $Id: HexMergeView.cpp 7165 2010-05-15 14:04:43Z jtuc $
31 #include "HexMergeFrm.h"
34 #include "HexMergeView.h"
35 #include "OptionsDef.h"
36 #include "OptionsMgr.h"
37 #include "Environment.h"
42 static char THIS_FILE[] = __FILE__;
46 * @brief Turn bool api result into success/error code
48 static HRESULT NTAPI SE(BOOL f)
52 HRESULT hr = (HRESULT)::GetLastError();
59 static UINT64 NTAPI GetLastWriteTime(HANDLE h)
62 return ::GetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft)) ? ft : 0;
65 static void NTAPI SetLastWriteTime(HANDLE h, UINT64 ft)
67 ::SetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft));
70 /////////////////////////////////////////////////////////////////////////////
73 IMPLEMENT_DYNCREATE(CHexMergeView, CView)
75 BEGIN_MESSAGE_MAP(CHexMergeView, CView)
76 //{{AFX_MSG_MAP(CHexMergeView)
77 ON_MESSAGE_VOID(WM_PAINT, CWnd::OnPaint)
82 ON_COMMAND(ID_EDIT_FIND, OnEditFind)
83 ON_COMMAND(ID_EDIT_REPLACE, OnEditReplace)
84 ON_COMMAND(ID_EDIT_REPEAT, OnEditRepeat)
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()
113 * @brief Drawing is not supported
115 void CHexMergeView::OnDraw(CDC *)
121 * @brief Load heksedit.dll and setup window class name
123 BOOL CHexMergeView::PreCreateWindow(CREATESTRUCT& cs)
125 static void *pv = NULL;
128 static const CLSID clsid = { 0xBCA3CA6B, 0xCC6B, 0x4F79,
129 { 0xA2, 0xC2, 0xDD, 0xBE, 0x86, 0x4B, 0x1C, 0x90 } };
130 if (FAILED(::CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, &pv)))
132 pv = LoadLibrary(_T("Frhed\\hekseditU.dll"));
134 LangMessageBox(IDS_FRHED_NOTINSTALLED, MB_OK);
137 cs.lpszClass = _T("heksedit");
138 cs.style |= WS_HSCROLL | WS_VSCROLL;
143 * @brief Grab the control's IHexEditorWindow interface pointer upon window creation
145 int CHexMergeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
147 if (CView::OnCreate(lpCreateStruct) == -1)
149 m_pif = reinterpret_cast<IHexEditorWindow *>(::GetWindowLongPtr(m_hWnd, GWLP_USERDATA));
150 if (m_pif == 0 || m_pif->get_interface_version() < HEKSEDIT_INTERFACE_VERSION)
156 * @brief Skip default WM_NCCALCSIZE processing so as to prevent scrollbars from showing up
158 void CHexMergeView::OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS *)
163 * @brief Synchronize all involved scrollbars
165 void CHexMergeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
168 if (pScrollBar && nSBCode == SB_THUMBTRACK)
170 pScrollBar->GetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
171 si.nPos = si.nTrackPos;
172 SetScrollInfo(SB_HORZ, &si);
174 CView::OnHScroll(nSBCode, nPos, pScrollBar);
177 GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
178 if (nSBCode != SB_THUMBTRACK)
180 pScrollBar->SetScrollInfo(&si);
182 CSplitterWndEx *pSplitter = (CSplitterWndEx *)GetParentSplitter(this, TRUE);
183 int nID = GetDlgCtrlID();
184 nID ^= pSplitter->IdFromRowCol(0, 0) ^ pSplitter->IdFromRowCol(0, 1);
185 CWnd *pWnd = pSplitter->GetDlgItem(nID);
186 pWnd->SetScrollInfo(SB_HORZ, &si);
187 pWnd->SendMessage(WM_HSCROLL, MAKEWPARAM(nSBCode, nPos));
192 * @brief Synchronize all involved scrollbars
194 void CHexMergeView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
197 if (pScrollBar && nSBCode == SB_THUMBTRACK)
199 pScrollBar->GetScrollInfo(&si);
200 si.nPos = si.nTrackPos;
201 SetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
203 CView::OnVScroll(nSBCode, nPos, pScrollBar);
204 if (pScrollBar && nSBCode != SB_THUMBTRACK)
206 GetScrollInfo(SB_VERT, &si);
207 pScrollBar->SetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
212 * @brief Synchronize file path bar activation states
214 void CHexMergeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
216 CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
217 CHexMergeFrame *pFrameWnd = static_cast<CHexMergeFrame *>(GetParentFrame());
218 pFrameWnd->GetHeaderInterface()->SetActive(m_nThisPane, !!bActivate);
222 * @brief Get pointer to control's content buffer
224 BYTE *CHexMergeView::GetBuffer(int length)
226 return m_pif->get_buffer(length);
230 * @brief Get length of control's content buffer
232 int CHexMergeView::GetLength()
234 return m_pif->get_length();
238 * @brief Checks if file has changed since last update
239 * @param [in] path File to check
240 * @return TRUE if file is changed.
242 BOOL CHexMergeView::IsFileChangedOnDisk(LPCTSTR path)
244 // NB: FileTimes are measured in 100 nanosecond intervals since 1601-01-01.
245 BOOL bChanged = FALSE;
246 HANDLE h = CreateFile(path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE,
247 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
248 if (h != INVALID_HANDLE_VALUE)
250 UINT64 mtime = GetLastWriteTime(h);
251 UINT64 lower = min(mtime, m_mtime);
252 UINT64 upper = max(mtime, m_mtime);
253 BOOL bIgnoreSmallDiff = GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME);
254 UINT64 tolerance = bIgnoreSmallDiff ? SmallTimeDiff * 10000000 : 0;
255 bChanged = upper - lower > tolerance || m_size != GetFileSize(h, 0);
264 HRESULT CHexMergeView::LoadFile(LPCTSTR path)
266 HANDLE h = CreateFile(path, GENERIC_READ,
267 FILE_SHARE_READ | FILE_SHARE_WRITE,
268 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
269 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
272 m_mtime = GetLastWriteTime(h);
273 DWORD length = m_size = GetFileSize(h, 0);
274 hr = SE(length != INVALID_FILE_SIZE);
277 if (void *buffer = GetBuffer(length))
280 hr = SE(ReadFile(h, buffer, length, &cb, 0) && cb == length);
284 else if (length != 0)
296 HRESULT CHexMergeView::SaveFile(LPCTSTR path)
298 // Warn user in case file has been changed by someone else
299 if (IsFileChangedOnDisk(path))
301 String msg = string_format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), path);
302 if (AfxMessageBox(msg.c_str(), MB_ICONWARNING | MB_YESNO) == IDNO)
305 // Ask user what to do about FILE_ATTRIBUTE_READONLY
306 String strPath = path;
307 BOOL bApplyToAll = FALSE;
308 if (theApp.HandleReadonlySave(strPath, FALSE, bApplyToAll) == IDCANCEL)
310 path = strPath.c_str();
311 // Take a chance to create a backup
312 if (!theApp.CreateBackup(FALSE, path))
314 // Write data to an intermediate file
315 String tempPath = env_GetTempPath();
316 String sIntermediateFilename = env_GetTempFileName(tempPath, _T("MRG_"), 0);
317 if (sIntermediateFilename.empty())
318 return E_FAIL; //Nothing to do if even tempfile name fails
319 HANDLE h = CreateFile(sIntermediateFilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
320 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
321 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
324 DWORD length = GetLength();
325 void *buffer = GetBuffer(length);
329 hr = SE(WriteFile(h, buffer, length, &cb, 0) && cb == length);
330 UINT64 mtime = GetLastWriteTime(h);
334 hr = SE(CopyFile(sIntermediateFilename.c_str(), path, FALSE));
339 hr = SE(DeleteFile(sIntermediateFilename.c_str()));
342 LogErrorString(string_format(_T("DeleteFile(%s) failed: %s"),
343 sIntermediateFilename.c_str(), GetSysError(hr).c_str()));
351 IHexEditorWindow::Status *CHexMergeView::GetStatus()
353 return m_pif->get_status();
357 * @brief Get modified flag
359 BOOL CHexMergeView::GetModified()
361 return m_pif->get_status()->iFileChanged;
365 * @brief Set modified flag
367 void CHexMergeView::SetModified(BOOL bModified)
369 m_pif->get_status()->iFileChanged = bModified;
373 * @brief Get readonly flag
375 BOOL CHexMergeView::GetReadOnly()
377 return m_pif->get_settings()->bReadOnly;
381 * @brief Set readonly flag
383 void CHexMergeView::SetReadOnly(BOOL bReadOnly)
385 m_pif->get_settings()->bReadOnly = bReadOnly;
389 * @brief Allow the control to update all kinds of things that need to be updated when
390 * the window or content buffer have been resized or certain settings have been changed.
392 void CHexMergeView::ResizeWindow()
394 m_pif->resize_window();
398 * @brief Repaint a range of bytes
400 void CHexMergeView::RepaintRange(int i, int j)
402 int iBytesPerLine = m_pif->get_settings()->iBytesPerLine;
403 m_pif->repaint(i / iBytesPerLine, j / iBytesPerLine);
407 * @brief Find a sequence of bytes
409 void CHexMergeView::OnEditFind()
415 * @brief Find & replace a sequence of bytes
417 void CHexMergeView::OnEditReplace()
419 m_pif->CMD_replace();
423 * @brief Repeat last find in one or another direction
425 void CHexMergeView::OnEditRepeat()
427 if (GetKeyState(VK_SHIFT) < 0)
428 m_pif->CMD_findprev();
430 m_pif->CMD_findnext();
434 * @brief Cut selected content
436 void CHexMergeView::OnEditCut()
438 m_pif->CMD_edit_cut();
442 * @brief Copy selected content
444 void CHexMergeView::OnEditCopy()
446 m_pif->CMD_edit_copy();
450 * @brief Paste clipboard content over selected content
452 void CHexMergeView::OnEditPaste()
454 m_pif->CMD_edit_paste();
458 * @brief Select entire content
460 void CHexMergeView::OnEditSelectAll()
462 m_pif->CMD_select_all();
466 * @brief Clear selected content
468 void CHexMergeView::OnEditClear()
470 m_pif->CMD_edit_clear();
474 * @brief Check for keyboard commands
476 BOOL CHexMergeView::PreTranslateMessage(MSG* pMsg)
478 if (GetTopLevelFrame()->PreTranslateMessage(pMsg))
480 if (pMsg->message == WM_KEYDOWN)
482 // Close window in response to VK_ESCAPE if user has allowed it from options
483 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC))
485 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
489 return m_pif->translate_accelerator(pMsg);
493 * @brief Go to first diff
495 void CHexMergeView::OnFirstdiff()
497 m_pif->select_next_diff(TRUE);
501 * @brief Go to last diff
503 void CHexMergeView::OnLastdiff()
505 m_pif->select_prev_diff(TRUE);
509 * @brief Go to next diff
511 void CHexMergeView::OnNextdiff()
513 m_pif->select_next_diff(FALSE);
517 * @brief Go to previous diff
519 void CHexMergeView::OnPrevdiff()
521 m_pif->select_prev_diff(FALSE);
524 void CHexMergeView::ZoomText(int amount)
526 m_pif->CMD_zoom(amount);