OSDN Git Service

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