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
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()
114 * @brief Drawing is not supported
116 void CHexMergeView::OnDraw(CDC *)
122 * @brief Load heksedit.dll and setup window class name
124 BOOL CHexMergeView::PreCreateWindow(CREATESTRUCT& cs)
126 static void *pv = NULL;
129 static const CLSID clsid = { 0xBCA3CA6B, 0xCC6B, 0x4F79,
130 { 0xA2, 0xC2, 0xDD, 0xBE, 0x86, 0x4B, 0x1C, 0x90 } };
131 if (FAILED(::CoGetClassObject(clsid, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, &pv)))
133 pv = LoadLibrary(_T("Frhed\\hekseditU.dll"));
135 LangMessageBox(IDS_FRHED_NOTINSTALLED, MB_OK);
138 cs.lpszClass = _T("heksedit");
139 cs.style |= WS_HSCROLL | WS_VSCROLL;
144 * @brief Grab the control's IHexEditorWindow interface pointer upon window creation
146 int CHexMergeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
148 if (CView::OnCreate(lpCreateStruct) == -1)
150 m_pif = reinterpret_cast<IHexEditorWindow *>(::GetWindowLongPtr(m_hWnd, GWLP_USERDATA));
151 if (m_pif == 0 || m_pif->get_interface_version() < HEKSEDIT_INTERFACE_VERSION)
157 * @brief Skip default WM_NCCALCSIZE processing so as to prevent scrollbars from showing up
159 void CHexMergeView::OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS *)
164 * @brief Synchronize all involved scrollbars
166 void CHexMergeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
169 if (pScrollBar && nSBCode == SB_THUMBTRACK)
171 pScrollBar->GetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
172 si.nPos = si.nTrackPos;
173 SetScrollInfo(SB_HORZ, &si);
175 CView::OnHScroll(nSBCode, nPos, pScrollBar);
178 GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
179 if (nSBCode != SB_THUMBTRACK)
181 pScrollBar->SetScrollInfo(&si);
184 CSplitterWndEx *pSplitter = static_cast<CSplitterWndEx *>(GetParentSplitter(this, TRUE));
185 for (int pane = 0; pane < pSplitter->GetColumnCount(); ++pane)
187 if (pane != m_nThisPane)
189 CWnd *pWnd = pSplitter->GetDlgItem(pSplitter->IdFromRowCol(0, pane));
190 pWnd->SetScrollInfo(SB_HORZ, &si);
191 pWnd->SendMessage(WM_HSCROLL, MAKEWPARAM(nSBCode, nPos));
198 * @brief Synchronize all involved scrollbars
200 void CHexMergeView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
203 if (pScrollBar && nSBCode == SB_THUMBTRACK)
205 pScrollBar->GetScrollInfo(&si);
206 si.nPos = si.nTrackPos;
207 SetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
209 CView::OnVScroll(nSBCode, nPos, pScrollBar);
210 if (pScrollBar && nSBCode != SB_THUMBTRACK)
212 GetScrollInfo(SB_VERT, &si);
213 pScrollBar->SetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
218 * @brief Synchronize file path bar activation states
220 void CHexMergeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
222 CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
223 CHexMergeFrame *pFrameWnd = static_cast<CHexMergeFrame *>(GetParentFrame());
224 pFrameWnd->GetHeaderInterface()->SetActive(m_nThisPane, !!bActivate);
228 * @brief Get pointer to control's content buffer
230 BYTE *CHexMergeView::GetBuffer(int length)
232 return m_pif->get_buffer(length);
236 * @brief Get length of control's content buffer
238 int CHexMergeView::GetLength()
240 return m_pif->get_length();
244 * @brief Checks if file has changed since last update
245 * @param [in] path File to check
246 * @return TRUE if file is changed.
248 BOOL CHexMergeView::IsFileChangedOnDisk(LPCTSTR path)
250 // NB: FileTimes are measured in 100 nanosecond intervals since 1601-01-01.
251 BOOL bChanged = FALSE;
252 HANDLE h = CreateFile(path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE,
253 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
254 if (h != INVALID_HANDLE_VALUE)
256 UINT64 mtime = GetLastWriteTime(h);
257 UINT64 lower = min(mtime, m_mtime);
258 UINT64 upper = max(mtime, m_mtime);
259 BOOL bIgnoreSmallDiff = GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME);
260 UINT64 tolerance = bIgnoreSmallDiff ? SmallTimeDiff * 10000000 : 0;
261 bChanged = upper - lower > tolerance || m_size != GetFileSize(h, 0);
270 HRESULT CHexMergeView::LoadFile(LPCTSTR path)
272 HANDLE h = CreateFile(path, GENERIC_READ,
273 FILE_SHARE_READ | FILE_SHARE_WRITE,
274 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
275 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
278 m_mtime = GetLastWriteTime(h);
279 DWORD length = m_size = GetFileSize(h, 0);
280 hr = SE(length != INVALID_FILE_SIZE);
283 if (void *buffer = GetBuffer(length))
286 hr = SE(ReadFile(h, buffer, length, &cb, 0) && cb == length);
290 else if (length != 0)
302 HRESULT CHexMergeView::SaveFile(LPCTSTR path)
304 // Warn user in case file has been changed by someone else
305 if (IsFileChangedOnDisk(path))
307 String msg = string_format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), path);
308 if (AfxMessageBox(msg.c_str(), MB_ICONWARNING | MB_YESNO) == IDNO)
311 // Ask user what to do about FILE_ATTRIBUTE_READONLY
312 String strPath = path;
313 BOOL bApplyToAll = FALSE;
314 if (theApp.HandleReadonlySave(strPath, FALSE, bApplyToAll) == IDCANCEL)
316 path = strPath.c_str();
317 // Take a chance to create a backup
318 if (!theApp.CreateBackup(FALSE, path))
320 // Write data to an intermediate file
321 String tempPath = env_GetTempPath();
322 String sIntermediateFilename = env_GetTempFileName(tempPath, _T("MRG_"), 0);
323 if (sIntermediateFilename.empty())
324 return E_FAIL; //Nothing to do if even tempfile name fails
325 HANDLE h = CreateFile(sIntermediateFilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
326 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
327 HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
330 DWORD length = GetLength();
331 void *buffer = GetBuffer(length);
335 hr = SE(WriteFile(h, buffer, length, &cb, 0) && cb == length);
336 UINT64 mtime = GetLastWriteTime(h);
340 hr = SE(CopyFile(sIntermediateFilename.c_str(), path, FALSE));
345 hr = SE(DeleteFile(sIntermediateFilename.c_str()));
348 LogErrorString(string_format(_T("DeleteFile(%s) failed: %s"),
349 sIntermediateFilename.c_str(), GetSysError(hr).c_str()));
357 IHexEditorWindow::Status *CHexMergeView::GetStatus()
359 return m_pif->get_status();
363 * @brief Get modified flag
365 BOOL CHexMergeView::GetModified()
367 return m_pif->get_status()->iFileChanged;
371 * @brief Set modified flag
373 void CHexMergeView::SetModified(BOOL bModified)
375 m_pif->get_status()->iFileChanged = bModified;
379 * @brief Get readonly flag
381 BOOL CHexMergeView::GetReadOnly()
383 return m_pif->get_settings()->bReadOnly;
387 * @brief Set readonly flag
389 void CHexMergeView::SetReadOnly(BOOL bReadOnly)
391 m_pif->get_settings()->bReadOnly = bReadOnly;
395 * @brief Allow the control to update all kinds of things that need to be updated when
396 * the window or content buffer have been resized or certain settings have been changed.
398 void CHexMergeView::ResizeWindow()
400 m_pif->resize_window();
404 * @brief Repaint a range of bytes
406 void CHexMergeView::RepaintRange(int i, int j)
408 int iBytesPerLine = m_pif->get_settings()->iBytesPerLine;
409 m_pif->repaint(i / iBytesPerLine, j / iBytesPerLine);
413 * @brief Find a sequence of bytes
415 void CHexMergeView::OnEditFind()
421 * @brief Find & replace a sequence of bytes
423 void CHexMergeView::OnEditReplace()
425 m_pif->CMD_replace();
429 * @brief Repeat last find in one or another direction
431 void CHexMergeView::OnEditRepeat()
433 if (GetKeyState(VK_SHIFT) < 0)
434 m_pif->CMD_findprev();
436 m_pif->CMD_findnext();
440 * @brief Cut selected content
442 void CHexMergeView::OnEditCut()
444 m_pif->CMD_edit_cut();
448 * @brief Copy selected content
450 void CHexMergeView::OnEditCopy()
452 m_pif->CMD_edit_copy();
456 * @brief Paste clipboard content over selected content
458 void CHexMergeView::OnEditPaste()
460 m_pif->CMD_edit_paste();
464 * @brief Select entire content
466 void CHexMergeView::OnEditSelectAll()
468 m_pif->CMD_select_all();
472 * @brief Clear selected content
474 void CHexMergeView::OnEditClear()
476 m_pif->CMD_edit_clear();
480 * @brief Check for keyboard commands
482 BOOL CHexMergeView::PreTranslateMessage(MSG* pMsg)
484 if (GetTopLevelFrame()->PreTranslateMessage(pMsg))
486 if (pMsg->message == WM_KEYDOWN)
488 // Close window in response to VK_ESCAPE if user has allowed it from options
489 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC))
491 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
495 return m_pif->translate_accelerator(pMsg);
499 * @brief Go to first diff
501 void CHexMergeView::OnFirstdiff()
503 m_pif->select_next_diff(TRUE);
507 * @brief Go to last diff
509 void CHexMergeView::OnLastdiff()
511 m_pif->select_prev_diff(TRUE);
515 * @brief Go to next diff
517 void CHexMergeView::OnNextdiff()
519 m_pif->select_next_diff(FALSE);
523 * @brief Go to previous diff
525 void CHexMergeView::OnPrevdiff()
527 m_pif->select_prev_diff(FALSE);
530 void CHexMergeView::ZoomText(int amount)
532 m_pif->CMD_zoom(amount);