OSDN Git Service

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