OSDN Git Service

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