OSDN Git Service

78f36c41340378da53e12294c3adde1e1d9572bb
[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 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /** 
8  * @file  HexMergeView.cpp
9  *
10  * @brief Implementation file for CHexMergeView
11  *
12  */
13
14 #include "stdafx.h"
15 #include "HexMergeFrm.h"
16 #include "Merge.h"
17 #include "MainFrm.h"
18 #include "HexMergeView.h"
19 #include "OptionsDef.h"
20 #include "OptionsMgr.h"
21 #include "Environment.h"
22
23 #ifdef _DEBUG
24 #define new DEBUG_NEW
25 #endif
26
27 /** @brief Location for hex compare specific help to open. */
28 static TCHAR HexMergeViewHelpLocation[] = _T("::/htmlhelp/Compare_bin.html");
29
30 /**
31  * @brief Turn bool api result into success/error code
32  */
33 static HRESULT NTAPI SE(BOOL f)
34 {
35         if (f)
36                 return S_OK;
37         HRESULT hr = (HRESULT)::GetLastError();
38         ASSERT(hr != NULL);
39         if (hr == NULL)
40                 hr = E_UNEXPECTED;
41         return hr;
42 }
43
44 static UINT64 NTAPI GetLastWriteTime(HANDLE h)
45 {
46         UINT64 ft;
47         return ::GetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft)) ? ft : 0;
48 }
49
50 static void NTAPI SetLastWriteTime(HANDLE h, UINT64 ft)
51 {
52         ::SetFileTime(h, 0, 0, reinterpret_cast<FILETIME *>(&ft));
53 }
54
55 /////////////////////////////////////////////////////////////////////////////
56 // CHexMergeView
57
58 IMPLEMENT_DYNCREATE(CHexMergeView, CView)
59
60 BEGIN_MESSAGE_MAP(CHexMergeView, CView)
61         //{{AFX_MSG_MAP(CHexMergeView)
62         ON_MESSAGE_VOID(WM_PAINT, CWnd::OnPaint)
63         ON_WM_CREATE()
64         ON_WM_HSCROLL()
65         ON_WM_VSCROLL()
66         ON_WM_MOUSEWHEEL()
67         ON_WM_NCCALCSIZE()
68         ON_COMMAND(ID_HELP, OnHelp)
69         ON_COMMAND(ID_EDIT_FIND, OnEditFind)
70         ON_COMMAND(ID_EDIT_REPLACE, OnEditReplace)
71         ON_COMMAND(ID_EDIT_REPEAT, OnEditRepeat)
72         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
73         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
74         ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
75         ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
76         ON_COMMAND(ID_EDIT_CUT, OnEditCut)
77         ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
78         ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
79         ON_COMMAND(ID_EDIT_CLEAR, OnEditClear)
80         ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll)
81         ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
82         ON_COMMAND(ID_LASTDIFF, OnLastdiff)
83         ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
84         ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
85         //}}AFX_MSG_MAP
86         // Test case to verify WM_COMMAND won't accidentally go through Default()
87         //ON_COMMAND(ID_APP_ABOUT, Default)
88 END_MESSAGE_MAP()
89
90 /////////////////////////////////////////////////////////////////////////////
91 // CHexMergeView construction/destruction
92
93 /**
94  * @brief Constructor.
95  */
96 CHexMergeView::CHexMergeView()
97 : m_pif(nullptr)
98 , m_nThisPane(0)
99 {
100 }
101
102 /**
103  * @brief Drawing is not supported
104  */
105 void CHexMergeView::OnDraw(CDC *)
106 {
107         ASSERT(false);
108 }
109
110 /**
111  * @brief returns true if heksedit.dll is loadable
112  */
113 bool CHexMergeView::IsLoadable()
114 {
115         static void *pv = nullptr;
116         if (pv == nullptr)
117         {
118                 pv = LoadLibrary(_T("Frhed\\hekseditU.dll"));
119         }
120         return pv != nullptr;
121 }
122
123 /**
124  * @brief Load heksedit.dll and setup window class name
125  */
126 BOOL CHexMergeView::PreCreateWindow(CREATESTRUCT& cs)
127 {
128         if (!IsLoadable())
129                 LangMessageBox(IDS_FRHED_NOTINSTALLED, MB_OK);
130         cs.lpszClass = _T("heksedit");
131         cs.style |= WS_HSCROLL | WS_VSCROLL;
132         return TRUE;
133 }
134
135 /**
136  * @brief Grab the control's IHexEditorWindow interface pointer upon window creation
137  */
138 int CHexMergeView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
139 {
140         if (CView::OnCreate(lpCreateStruct) == -1)
141                 return -1;
142         m_pif = reinterpret_cast<IHexEditorWindow *>(::GetWindowLongPtr(m_hWnd, GWLP_USERDATA));
143         if (m_pif == nullptr || m_pif->get_interface_version() < HEKSEDIT_INTERFACE_VERSION)
144                 return -1;
145         return 0;
146 }
147
148 /**
149  * @brief Skip default WM_NCCALCSIZE processing so as to prevent scrollbars from showing up
150  */
151 void CHexMergeView::OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS *)
152 {
153 }
154
155 /**
156  * @brief Synchronize all involved scrollbars
157  */
158 void CHexMergeView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
159 {
160         SCROLLINFO si;
161         if (pScrollBar && nSBCode == SB_THUMBTRACK)
162         {
163                 pScrollBar->GetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
164                 si.nPos = si.nTrackPos;
165                 SetScrollInfo(SB_HORZ, &si);
166         }
167         CView::OnHScroll(nSBCode, nPos, pScrollBar);
168         if (pScrollBar != nullptr)
169         {
170                 GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
171                 if (nSBCode != SB_THUMBTRACK)
172                 {
173                         pScrollBar->SetScrollInfo(&si);
174                 }
175                 
176                 CSplitterWndEx *pSplitter = static_cast<CSplitterWndEx *>(GetParentSplitter(this, TRUE));
177                 for (int pane = 0; pane < pSplitter->GetColumnCount(); ++pane)
178                 {
179                         if (pane != m_nThisPane)
180                         {
181                                 CWnd *pWnd = pSplitter->GetDlgItem(pSplitter->IdFromRowCol(0, pane));
182                                 pWnd->SetScrollInfo(SB_HORZ, &si);
183                                 pWnd->SendMessage(WM_HSCROLL, MAKEWPARAM(nSBCode, nPos));
184                         }
185                 }
186         }
187 }
188
189 /**
190  * @brief Synchronize all involved scrollbars
191  */
192 void CHexMergeView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar * pScrollBar)
193 {
194         SCROLLINFO si;
195         if (pScrollBar && nSBCode == SB_THUMBTRACK)
196         {
197                 pScrollBar->GetScrollInfo(&si);
198                 si.nPos = si.nTrackPos;
199                 SetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
200         }
201         CView::OnVScroll(nSBCode, nPos, pScrollBar);
202         if (pScrollBar && nSBCode != SB_THUMBTRACK)
203         {
204                 GetScrollInfo(SB_VERT, &si);
205                 pScrollBar->SetScrollInfo(&si, SIF_ALL | SIF_DISABLENOSCROLL);
206         }
207 }
208
209 BOOL CHexMergeView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
210 {
211         if ((GetAsyncKeyState(VK_CONTROL) &0x8000) != 0) // if (nFlags & MK_CONTROL)
212         {
213                 PostMessage(WM_COMMAND, zDelta < 0 ? ID_VIEW_ZOOMOUT : ID_VIEW_ZOOMIN);
214                 return 1;
215         }
216         return 0;
217 }
218
219 /**
220  * @brief Synchronize file path bar activation states
221  */
222 void CHexMergeView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
223 {
224         CView::OnActivateView(bActivate, pActivateView, pDeactiveView);
225         CHexMergeFrame *pFrameWnd = static_cast<CHexMergeFrame *>(GetParentFrame());
226         pFrameWnd->GetHeaderInterface()->SetActive(m_nThisPane, !!bActivate);
227 }
228
229 /**
230  * @brief Get pointer to control's content buffer
231  */
232 BYTE *CHexMergeView::GetBuffer(int length)
233 {
234         return m_pif->get_buffer(length);
235 }
236
237 /**
238  * @brief Get length of control's content buffer
239  */
240 int CHexMergeView::GetLength()
241 {
242         return m_pif->get_length();
243 }
244
245 /**
246  * @brief Checks if file has changed since last update
247  * @param [in] path File to check
248  * @return `true` if file is changed.
249  */
250 IMergeDoc::FileChange CHexMergeView::IsFileChangedOnDisk(LPCTSTR path)
251 {
252         DiffFileInfo dfi;
253         if (!dfi.Update(path))
254                 return IMergeDoc::FileChange::Removed;
255         int tolerance = 0;
256         if (GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME))
257                 tolerance = SmallTimeDiff; // From MainFrm.h
258         int64_t timeDiff = dfi.mtime - m_fileInfo.mtime;
259         if (timeDiff < 0) timeDiff = -timeDiff;
260         if ((timeDiff > tolerance * Poco::Timestamp::resolution()) || (dfi.size != m_fileInfo.size))
261                 return IMergeDoc::FileChange::Changed;
262         return IMergeDoc::FileChange::NoChange;
263 }
264
265 /**
266  * @brief Load file
267  */
268 HRESULT CHexMergeView::LoadFile(LPCTSTR path)
269 {
270         HANDLE h = CreateFile(path, GENERIC_READ,
271                 FILE_SHARE_READ | FILE_SHARE_WRITE,
272                 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
273         HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
274         if (h == INVALID_HANDLE_VALUE)
275                 return hr;
276         DWORD length = GetFileSize(h, 0);
277         hr = SE(length != INVALID_FILE_SIZE);
278         if (hr == S_OK)
279         {
280                 if (void *buffer = GetBuffer(length))
281                 {
282                         DWORD cb = 0;
283                         hr = SE(ReadFile(h, buffer, length, &cb, 0) && cb == length);
284                         if (hr != S_OK)
285                                 GetBuffer(0);
286                 }
287                 else if (length != 0)
288                 {
289                         hr = E_OUTOFMEMORY;
290                 }
291         }
292         CloseHandle(h);
293         m_fileInfo.Update(path);
294         return hr;
295 }
296
297 /**
298  * @brief Save file
299  */
300 HRESULT CHexMergeView::SaveFile(LPCTSTR path)
301 {
302         // Warn user in case file has been changed by someone else
303         if (IsFileChangedOnDisk(path) == IMergeDoc::FileChange::Changed)
304         {
305                 String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), path);
306                 if (AfxMessageBox(msg.c_str(), MB_ICONWARNING | MB_YESNO) == IDNO)
307                         return S_OK;
308         }
309         // Ask user what to do about FILE_ATTRIBUTE_READONLY
310         String strPath = path;
311         bool bApplyToAll = false;
312         if (theApp.HandleReadonlySave(strPath, false, bApplyToAll) == IDCANCEL)
313                 return S_OK;
314         path = strPath.c_str();
315         // Take a chance to create a backup
316         if (!theApp.CreateBackup(false, path))
317                 return S_OK;
318         // Write data to an intermediate file
319         String tempPath = env::GetTemporaryPath();
320         String sIntermediateFilename = env::GetTemporaryFileName(tempPath, _T("MRG_"), 0);
321         if (sIntermediateFilename.empty())
322                 return E_FAIL; //Nothing to do if even tempfile name fails
323         HANDLE h = CreateFile(sIntermediateFilename.c_str(), GENERIC_WRITE, FILE_SHARE_READ,
324                 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
325         HRESULT hr = SE(h != INVALID_HANDLE_VALUE);
326         if (h == INVALID_HANDLE_VALUE)
327                 return hr;
328         DWORD length = GetLength();
329         void *buffer = GetBuffer(length);
330         if (buffer == 0)
331         {
332                 CloseHandle(h);
333                 return E_POINTER;
334         }
335         DWORD cb = 0;
336         hr = SE(WriteFile(h, buffer, length, &cb, 0) && cb == length);
337         CloseHandle(h);
338         if (hr != S_OK)
339                 return hr;
340         hr = SE(CopyFile(sIntermediateFilename.c_str(), path, FALSE));
341         if (hr != S_OK)
342                 return hr;
343         m_fileInfo.Update(path);
344         SetSavePoint();
345         hr = SE(DeleteFile(sIntermediateFilename.c_str()));
346         if (hr != S_OK)
347         {
348                 LogErrorString(strutils::format(_T("DeleteFile(%s) failed: %s"),
349                         sIntermediateFilename, GetSysError(hr)));
350         }
351         return S_OK;
352 }
353
354 /**
355  * @brief Get modified flag
356  */
357 bool CHexMergeView::GetModified()
358 {
359         return m_pif->get_status()->iFileChanged != 0;
360 }
361
362 /**
363  * @brief Set modified flag
364  */
365 void CHexMergeView::SetSavePoint()
366 {
367         m_pif->set_savepoint();
368 }
369
370 /**
371  * @brief Clear undo records
372  */
373 void CHexMergeView::ClearUndoRecords()
374 {
375         m_pif->clear_undorecords();
376 }
377
378 /**
379  * @brief Get readonly flag
380  */
381 bool CHexMergeView::GetReadOnly()
382 {
383         return m_pif->get_settings()->bReadOnly;
384 }
385
386 /**
387  * @brief Set readonly flag
388  */
389 void CHexMergeView::SetReadOnly(bool bReadOnly)
390 {
391         m_pif->get_settings()->bReadOnly = bReadOnly;
392 }
393
394 /**
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.
397  */
398 void CHexMergeView::ResizeWindow()
399 {
400         m_pif->resize_window();
401 }
402
403 /**
404  * @brief Find a sequence of bytes
405  */
406 void CHexMergeView::OnEditFind()
407 {
408         m_pif->CMD_find();
409 }
410
411 /**
412  * @brief Find & replace a sequence of bytes
413  */
414 void CHexMergeView::OnEditReplace()
415 {
416         m_pif->CMD_replace();
417 }
418
419 /**
420  * @brief Repeat last find in one or another direction
421  */
422 void CHexMergeView::OnEditRepeat()
423 {
424         if (GetKeyState(VK_SHIFT) < 0)
425                 m_pif->CMD_findprev();
426         else
427                 m_pif->CMD_findnext();
428 }
429
430 /**
431 * @brief Called when "Undo" item is updated
432 */
433 void CHexMergeView::OnUpdateEditUndo(CCmdUI* pCmdUI)
434 {
435         pCmdUI->Enable(m_pif->can_undo());
436 }
437
438 /**
439  * @brief Undo last action
440  */
441 void CHexMergeView::OnEditUndo()
442 {
443         m_pif->CMD_edit_undo();
444 }
445
446 /**
447 * @brief Called when "Redo" item is updated
448 */
449 void CHexMergeView::OnUpdateEditRedo(CCmdUI* pCmdUI)
450 {
451         pCmdUI->Enable(m_pif->can_redo());
452 }
453
454 /**
455  * @brief Redo last action
456  */
457 void CHexMergeView::OnEditRedo()
458 {
459         m_pif->CMD_edit_redo();
460 }
461
462 /**
463  * @brief Cut selected content
464  */
465 void CHexMergeView::OnEditCut()
466 {
467         m_pif->CMD_edit_cut();
468 }
469
470 /**
471  * @brief Copy selected content
472  */
473 void CHexMergeView::OnEditCopy()
474 {
475         m_pif->CMD_edit_copy();
476 }
477
478 /**
479  * @brief Paste clipboard content over selected content
480  */
481 void CHexMergeView::OnEditPaste()
482 {
483         m_pif->CMD_edit_paste();
484 }
485
486 /**
487  * @brief Select entire content
488  */
489 void CHexMergeView::OnEditSelectAll()
490 {
491         m_pif->CMD_select_all();
492 }
493
494 /**
495  * @brief Clear selected content
496  */
497 void CHexMergeView::OnEditClear()
498 {
499         m_pif->CMD_edit_clear();
500 }
501
502 /**
503  * @brief Check for keyboard commands
504  */
505 BOOL CHexMergeView::PreTranslateMessage(MSG* pMsg)
506 {
507         if (GetTopLevelFrame()->PreTranslateMessage(pMsg))
508                 return TRUE;
509         if (pMsg->message == WM_KEYDOWN)
510         {
511                 // Close window in response to VK_ESCAPE if user has allowed it from options
512                 if (pMsg->wParam == VK_ESCAPE && GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC) != 0)
513                 {
514                         GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
515                         return TRUE;
516                 }
517         }
518         return m_pif->translate_accelerator(pMsg);
519 }
520
521 /**
522  * @brief Go to first diff
523  */
524 void CHexMergeView::OnFirstdiff()
525 {
526         m_pif->select_next_diff(TRUE);
527 }
528
529 /**
530  * @brief Go to last diff
531  */
532 void CHexMergeView::OnLastdiff()
533 {
534         m_pif->select_prev_diff(TRUE);
535 }
536
537 /**
538  * @brief Go to next diff
539  */
540 void CHexMergeView::OnNextdiff()
541 {
542         m_pif->select_next_diff(FALSE);
543 }
544
545 /**
546  * @brief Go to previous diff
547  */
548 void CHexMergeView::OnPrevdiff()
549 {
550         m_pif->select_prev_diff(FALSE);
551 }
552
553 /** @brief Open help from mainframe when user presses F1*/
554 void CHexMergeView::OnHelp()
555 {
556         theApp.ShowHelp(HexMergeViewHelpLocation);
557 }
558
559 void CHexMergeView::ZoomText(int amount)
560 {
561         m_pif->CMD_zoom(amount);
562 }
563
564 /**
565  * @brief Copy selected bytes from source view to destination view
566  * @note Grows destination buffer as appropriate
567  */
568 void CHexMergeView::CopySel(const CHexMergeView *src, CHexMergeView *dst)
569 {
570         dst->m_pif->copy_sel_from(src->m_pif);
571 }
572
573 /**
574  * @brief Copy all bytes from source view to destination view
575  * @note Grows destination buffer as appropriate
576  */
577 void CHexMergeView::CopyAll(const CHexMergeView *src, CHexMergeView *dst)
578 {
579         dst->m_pif->copy_all_from(src->m_pif);
580 }
581