OSDN Git Service

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