OSDN Git Service

Merge with stable
[winmerge-jp/winmerge-jp.git] / Src / HexMergeView.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
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.
10 //
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.
15 //
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.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /** 
22  * @file  HexMergeView.cpp
23  *
24  * @brief Implementation file for CHexMergeDoc
25  *
26  */
27 // ID line follows -- this is updated by SVN
28 // $Id: HexMergeView.cpp 7165 2010-05-15 14:04:43Z jtuc $
29
30 #include "stdafx.h"
31 #include "HexMergeFrm.h"
32 #include "Merge.h"
33 #include "MainFrm.h"
34 #include "HexMergeView.h"
35 #include "OptionsDef.h"
36 #include "OptionsMgr.h"
37 #include "Environment.h"
38
39 #ifdef _DEBUG
40 #define new DEBUG_NEW
41 #undef THIS_FILE
42 static char THIS_FILE[] = __FILE__;
43 #endif
44
45 /**
46  * @brief Turn bool api result into success/error code
47  */
48 static HRESULT NTAPI SE(BOOL f)
49 {
50         if (f)
51                 return S_OK;
52         HRESULT hr = (HRESULT)::GetLastError();
53         ASSERT(hr);
54         if (hr == 0)
55                 hr = E_UNEXPECTED;
56         return hr;
57 }
58
59 static UINT64 NTAPI GetLastWriteTime(HANDLE h)
60 {
61         UINT64 ft;
62         return ::GetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft)) ? ft : 0;
63 }
64
65 static void NTAPI SetLastWriteTime(HANDLE h, UINT64 ft)
66 {
67         ::SetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft));
68 }
69
70 /////////////////////////////////////////////////////////////////////////////
71 // CHexMergeView
72
73 IMPLEMENT_DYNCREATE(CHexMergeView, CView)
74
75 BEGIN_MESSAGE_MAP(CHexMergeView, CView)
76         //{{AFX_MSG_MAP(CHexMergeView)
77         ON_MESSAGE_VOID(WM_PAINT, CWnd::OnPaint)
78         ON_WM_CREATE()
79         ON_WM_HSCROLL()
80         ON_WM_VSCROLL()
81         ON_WM_NCCALCSIZE()
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)
94         //}}AFX_MSG_MAP
95         // Test case to verify WM_COMMAND won't accidentally go through Default()
96         //ON_COMMAND(ID_APP_ABOUT, Default)
97 END_MESSAGE_MAP()
98
99 /////////////////////////////////////////////////////////////////////////////
100 // CHexMergeView construction/destruction
101
102 /**
103  * @brief Constructor.
104  */
105 CHexMergeView::CHexMergeView()
106 : m_pif(0)
107 , m_nThisPane(0)
108 , m_mtime(0)
109 {
110 }
111
112 /**
113  * @brief Drawing is not supported
114  */
115 void CHexMergeView::OnDraw(CDC *)
116 {
117         ASSERT(FALSE);
118 }
119
120 /**
121  * @brief Load heksedit.dll and setup window class name
122  */
123 BOOL CHexMergeView::PreCreateWindow(CREATESTRUCT& cs)
124 {
125         static void *pv = NULL;
126         if (pv == NULL)
127         {
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)))
131                 {
132                         pv = LoadLibrary(_T("Frhed\\hekseditU.dll"));
133                         if (!pv)
134                                 LangMessageBox(IDS_FRHED_NOTINSTALLED, MB_OK);
135                 }
136         }
137         cs.lpszClass = _T("heksedit");
138         cs.style |= WS_HSCROLL | WS_VSCROLL;
139         return TRUE;
140 }
141
142 /**
143  * @brief Grab the control's IHexEditorWindow interface pointer upon window creation
144  */
145 int CHexMergeView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
146 {
147         if (CView::OnCreate(lpCreateStruct) == -1)
148                 return -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)
151                 return -1;
152         return 0;
153 }
154
155 /**
156  * @brief Skip default WM_NCCALCSIZE processing so as to prevent scrollbars from showing up
157  */
158 void CHexMergeView::OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS *)
159 {
160 }
161
162 /**
163  * @brief Synchronize all involved scrollbars
164  */
165 void CHexMergeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
166 {
167         SCROLLINFO si;
168         if (pScrollBar && nSBCode == SB_THUMBTRACK)
169         {
170                 pScrollBar->GetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
171                 si.nPos = si.nTrackPos;
172                 SetScrollInfo(SB_HORZ, &si);
173         }
174         CView::OnHScroll(nSBCode, nPos, pScrollBar);
175         if (pScrollBar)
176         {
177                 GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
178                 if (nSBCode != SB_THUMBTRACK)
179                 {
180                         pScrollBar->SetScrollInfo(&si);
181                 }
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));
188         }
189 }
190
191 /**
192  * @brief Synchronize all involved scrollbars
193  */
194 void CHexMergeView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
195 {
196         SCROLLINFO si;
197         if (pScrollBar && nSBCode == SB_THUMBTRACK)
198         {
199                 pScrollBar->GetScrollInfo(&si);
200                 si.nPos = si.nTrackPos;
201                 SetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
202         }
203         CView::OnVScroll(nSBCode, nPos, pScrollBar);
204         if (pScrollBar && nSBCode != SB_THUMBTRACK)
205         {
206                 GetScrollInfo(SB_VERT, &si);
207                 pScrollBar->SetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
208         }
209 }
210
211 /**
212  * @brief Synchronize file path bar activation states
213  */
214 void CHexMergeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
215 {
216         CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
217         CHexMergeFrame *pFrameWnd = static_cast<CHexMergeFrame *>(GetParentFrame());
218         pFrameWnd->GetHeaderInterface()->SetActive(m_nThisPane, !!bActivate);
219 }
220
221 /**
222  * @brief Get pointer to control's content buffer
223  */
224 BYTE *CHexMergeView::GetBuffer(int length)
225 {
226         return m_pif->get_buffer(length);
227 }
228
229 /**
230  * @brief Get length of control's content buffer
231  */
232 int CHexMergeView::GetLength()
233 {
234         return m_pif->get_length();
235 }
236
237 /**
238  * @brief Checks if file has changed since last update
239  * @param [in] path File to check
240  * @return TRUE if file is changed.
241  */
242 BOOL CHexMergeView::IsFileChangedOnDisk(LPCTSTR path)
243 {
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)
249         {
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);
256                 CloseHandle(h);
257         }
258         return bChanged;
259 }
260
261 /**
262  * @brief Load file
263  */
264 HRESULT CHexMergeView::LoadFile(LPCTSTR path)
265 {
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);
270         if (hr != S_OK)
271                 return hr;
272         m_mtime = GetLastWriteTime(h);
273         DWORD length = m_size = GetFileSize(h, 0);
274         hr = SE(length != INVALID_FILE_SIZE);
275         if (hr == S_OK)
276         {
277                 if (void *buffer = GetBuffer(length))
278                 {
279                         DWORD cb = 0;
280                         hr = SE(ReadFile(h, buffer, length, &cb, 0) && cb == length);
281                         if (hr != S_OK)
282                                 GetBuffer(0);
283                 }
284                 else if (length != 0)
285                 {
286                         hr = E_OUTOFMEMORY;
287                 }
288         }
289         CloseHandle(h);
290         return hr;
291 }
292
293 /**
294  * @brief Save file
295  */
296 HRESULT CHexMergeView::SaveFile(LPCTSTR path)
297 {
298         // Warn user in case file has been changed by someone else
299         if (IsFileChangedOnDisk(path))
300         {
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)
303                         return S_OK;
304         }
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)
309                 return S_OK;
310         path = strPath.c_str();
311         // Take a chance to create a backup
312         if (!theApp.CreateBackup(FALSE, path))
313                 return S_OK;
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);
322         if (hr != S_OK)
323                 return hr;
324         DWORD length = GetLength();
325         void *buffer = GetBuffer(length);
326         if (buffer == 0)
327                 return E_POINTER;
328         DWORD cb = 0;
329         hr = SE(WriteFile(h, buffer, length, &cb, 0) && cb == length);
330         UINT64 mtime = GetLastWriteTime(h);
331         CloseHandle(h);
332         if (hr != S_OK)
333                 return hr;
334         hr = SE(CopyFile(sIntermediateFilename.c_str(), path, FALSE));
335         if (hr != S_OK)
336                 return hr;
337         m_mtime = mtime;
338         SetModified(FALSE);
339         hr = SE(DeleteFile(sIntermediateFilename.c_str()));
340         if (hr != S_OK)
341         {
342                 LogErrorString(string_format(_T("DeleteFile(%s) failed: %s"),
343                         sIntermediateFilename.c_str(), GetSysError(hr).c_str()));
344         }
345         return S_OK;
346 }
347
348 /**
349  * @brief Get status
350  */
351 IHexEditorWindow::Status *CHexMergeView::GetStatus()
352 {
353         return m_pif->get_status();
354 }
355
356 /**
357  * @brief Get modified flag
358  */
359 BOOL CHexMergeView::GetModified()
360 {
361         return m_pif->get_status()->iFileChanged;
362 }
363
364 /**
365  * @brief Set modified flag
366  */
367 void CHexMergeView::SetModified(BOOL bModified)
368 {
369         m_pif->get_status()->iFileChanged = bModified;
370 }
371
372 /**
373  * @brief Get readonly flag
374  */
375 BOOL CHexMergeView::GetReadOnly()
376 {
377         return m_pif->get_settings()->bReadOnly;
378 }
379
380 /**
381  * @brief Set readonly flag
382  */
383 void CHexMergeView::SetReadOnly(BOOL bReadOnly)
384 {
385         m_pif->get_settings()->bReadOnly = bReadOnly;
386 }
387
388 /**
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.
391  */
392 void CHexMergeView::ResizeWindow()
393 {
394         m_pif->resize_window();
395 }
396
397 /**
398  * @brief Repaint a range of bytes
399  */
400 void CHexMergeView::RepaintRange(int i, int j)
401 {
402         int iBytesPerLine = m_pif->get_settings()->iBytesPerLine;
403         m_pif->repaint(i / iBytesPerLine, j / iBytesPerLine);
404 }
405
406 /**
407  * @brief Find a sequence of bytes
408  */
409 void CHexMergeView::OnEditFind()
410 {
411         m_pif->CMD_find();
412 }
413
414 /**
415  * @brief Find & replace a sequence of bytes
416  */
417 void CHexMergeView::OnEditReplace()
418 {
419         m_pif->CMD_replace();
420 }
421
422 /**
423  * @brief Repeat last find in one or another direction
424  */
425 void CHexMergeView::OnEditRepeat()
426 {
427         if (GetKeyState(VK_SHIFT) < 0)
428                 m_pif->CMD_findprev();
429         else
430                 m_pif->CMD_findnext();
431 }
432
433 /**
434  * @brief Cut selected content
435  */
436 void CHexMergeView::OnEditCut()
437 {
438         m_pif->CMD_edit_cut();
439 }
440
441 /**
442  * @brief Copy selected content
443  */
444 void CHexMergeView::OnEditCopy()
445 {
446         m_pif->CMD_edit_copy();
447 }
448
449 /**
450  * @brief Paste clipboard content over selected content
451  */
452 void CHexMergeView::OnEditPaste()
453 {
454         m_pif->CMD_edit_paste();
455 }
456
457 /**
458  * @brief Select entire content
459  */
460 void CHexMergeView::OnEditSelectAll()
461 {
462         m_pif->CMD_select_all();
463 }
464
465 /**
466  * @brief Clear selected content
467  */
468 void CHexMergeView::OnEditClear()
469 {
470         m_pif->CMD_edit_clear();
471 }
472
473 /**
474  * @brief Check for keyboard commands
475  */
476 BOOL CHexMergeView::PreTranslateMessage(MSG* pMsg)
477 {
478         if (GetTopLevelFrame()->PreTranslateMessage(pMsg))
479                 return TRUE;
480         if (pMsg->message == WM_KEYDOWN)
481         {
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))
484                 {
485                         GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
486                         return TRUE;
487                 }
488         }
489         return m_pif->translate_accelerator(pMsg);
490 }
491
492 /**
493  * @brief Go to first diff
494  */
495 void CHexMergeView::OnFirstdiff()
496 {
497         m_pif->select_next_diff(TRUE);
498 }
499
500 /**
501  * @brief Go to last diff
502  */
503 void CHexMergeView::OnLastdiff()
504 {
505         m_pif->select_prev_diff(TRUE);
506 }
507
508 /**
509  * @brief Go to next diff
510  */
511 void CHexMergeView::OnNextdiff()
512 {
513         m_pif->select_next_diff(FALSE);
514 }
515
516 /**
517  * @brief Go to previous diff
518  */
519 void CHexMergeView::OnPrevdiff()
520 {
521         m_pif->select_prev_diff(FALSE);
522 }
523
524 void CHexMergeView::ZoomText(int amount)
525 {
526         m_pif->CMD_zoom(amount);
527 }