OSDN Git Service

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