OSDN Git Service

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