OSDN Git Service

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