OSDN Git Service

* Use standard order of header files
[winmerge-jp/winmerge-jp.git] / Src / HexMergeDoc.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  HexMergeDoc.cpp
23  *
24  * @brief Implementation file for CHexMergeDoc
25  *
26  */
27 // ID line follows -- this is updated by SVN
28 // $Id: HexMergeDoc.cpp 7166 2010-05-16 12:05:13Z jtuc $
29
30 #include "stdafx.h"
31 #include "HexMergeDoc.h"
32 #include <afxinet.h>
33 #include "UnicodeString.h"
34 #include "FileTextEncoding.h"
35 #include "Merge.h"
36 #include "HexMergeFrm.h"
37 #include "HexMergeView.h"
38 #include "DiffItem.h"
39 #include "FolderCmp.h"
40 #include "Environment.h"
41 #include "diffcontext.h"        // FILE_SAME
42 #include "dirdoc.h"
43 #include "OptionsDef.h"
44 #include "DiffFileInfo.h"
45 #include "SaveClosingDlg.h"
46 #include "DiffList.h"
47 #include "paths.h"
48 #include "OptionsMgr.h"
49 #include "FileOrFolderSelect.h"
50 #include "DiffWrapper.h"
51
52 #ifdef _DEBUG
53 #define new DEBUG_NEW
54 #undef THIS_FILE
55 static char THIS_FILE[] = __FILE__;
56 #endif
57
58 int CHexMergeDoc::m_nBuffersTemp = 2;
59
60 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt);
61 static int Try(HRESULT hr, UINT type = MB_OKCANCEL|MB_ICONSTOP);
62
63 /**
64  * @brief Update diff item
65  */
66 static void UpdateDiffItem(int nBuffers, DIFFITEM &di, CDiffContext *pCtxt)
67 {
68         di.diffcode.diffcode |= DIFFCODE::SIDEFLAGS;
69         for (int nBuffer = 0; nBuffer < nBuffers; nBuffer++)
70         {
71                 di.diffFileInfo[nBuffer].ClearPartial();
72                 di.diffFileInfo[nBuffer].ClearPartial();
73                 if (!pCtxt->UpdateInfoFromDiskHalf(di, nBuffer))
74                 {
75                         if (nBuffer == 0)
76                                 di.diffcode.diffcode &= ~DIFFCODE::FIRST;
77                         else if (nBuffer == 1)
78                                 di.diffcode.diffcode &= ~DIFFCODE::SECOND;
79                         else
80                                 di.diffcode.diffcode &= ~DIFFCODE::THIRD;
81                 }
82         }
83         // 1. Clear flags
84         di.diffcode.diffcode &= ~(DIFFCODE::TEXTFLAGS | DIFFCODE::COMPAREFLAGS);
85         // 2. Process unique files
86         // We must compare unique files to itself to detect encoding
87         if (!di.diffcode.isExistsFirst() || !di.diffcode.isExistsSecond() || (nBuffers == 3 && di.diffcode.isExistsThird()))
88         {
89                 int compareMethod = pCtxt->GetCompareMethod();
90                 if (compareMethod != CMP_DATE && compareMethod != CMP_DATE_SIZE &&
91                         compareMethod != CMP_SIZE)
92                 {
93                         di.diffcode.diffcode |= DIFFCODE::SAME;
94                         FolderCmp folderCmp;
95                         int diffCode = folderCmp.prepAndCompareFiles(pCtxt, di);
96                         // Add possible binary flag for unique items
97                         if (diffCode & DIFFCODE::BIN)
98                                 di.diffcode.diffcode |= DIFFCODE::BIN;
99                 }
100         }
101         // 3. Compare two files
102         else
103         {
104                 // Really compare
105                 FolderCmp folderCmp;
106                 di.diffcode.diffcode |= folderCmp.prepAndCompareFiles(pCtxt, di);
107         }
108 }
109
110 /**
111  * @brief Issue an error popup if passed in HRESULT is nonzero
112  */
113 static int Try(HRESULT hr, UINT type)
114 {
115         return hr ? CInternetException(hr).ReportError(type) : 0;
116 }
117
118 /////////////////////////////////////////////////////////////////////////////
119 // CHexMergeDoc
120
121 IMPLEMENT_DYNCREATE(CHexMergeDoc, CDocument)
122
123 BEGIN_MESSAGE_MAP(CHexMergeDoc, CDocument)
124         //{{AFX_MSG_MAP(CHexMergeDoc)
125         ON_COMMAND(ID_FILE_SAVE, OnFileSave)
126         ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
127         ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
128         ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
129         ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
130         ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
131         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
132         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
133         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
134         ON_COMMAND(ID_L2R, OnL2r)
135         ON_COMMAND(ID_R2L, OnR2l)
136         ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
137         ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
138         ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
139         ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
140         ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
141         //}}AFX_MSG_MAP
142 END_MESSAGE_MAP()
143
144 /////////////////////////////////////////////////////////////////////////////
145 // CHexMergeDoc construction/destruction
146
147 /**
148  * @brief Constructor.
149  */
150 CHexMergeDoc::CHexMergeDoc()
151 : m_pDirDoc(NULL)
152 {
153         m_nBuffers = m_nBuffersTemp;
154         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
155         {
156                 m_pView[nBuffer] = NULL;
157                 m_nBufferType[nBuffer] = BUFFER_NORMAL;
158         }
159 }
160
161 /**
162  * @brief Destructor.
163  *
164  * Informs associated dirdoc that mergedoc is closing.
165  */
166 CHexMergeDoc::~CHexMergeDoc()
167 {       
168         if (m_pDirDoc)
169                 m_pDirDoc->MergeDocClosing(this);
170 }
171
172 /**
173  * @brief Update associated diff item
174  */
175 void CHexMergeDoc::UpdateDiffItem(CDirDoc *pDirDoc)
176 {
177         // If directory compare has results
178         if (pDirDoc && pDirDoc->HasDiffs())
179         {
180                 const String &pathLeft = m_filePaths.GetLeft();
181                 const String &pathRight = m_filePaths.GetRight();
182                 CDiffContext &ctxt = const_cast<CDiffContext &>(pDirDoc->GetDiffContext());
183                 if (UINT_PTR pos = pDirDoc->FindItemFromPaths(pathLeft, pathRight))
184                 {
185                         DIFFITEM &di = pDirDoc->GetDiffRefByKey(pos);
186                         ::UpdateDiffItem(m_nBuffers, di, &ctxt);
187                 }
188         }
189         BOOL bDiff = FALSE;
190         int lengthFirst = m_pView[0]->GetLength();
191         void *bufferFirst = m_pView[0]->GetBuffer(lengthFirst);
192         for (int nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
193         {
194                 int length = m_pView[nBuffer]->GetLength();
195                 if (lengthFirst != length)
196                         bDiff = TRUE;
197                 else
198                 {
199                         void *buffer = m_pView[nBuffer]->GetBuffer(length);
200                         bDiff = (memcmp(bufferFirst, buffer, lengthFirst) != 0);
201                 }
202                 if (bDiff)
203                         break;
204         }
205         GetParentFrame()->SetLastCompareResult(bDiff);
206 }
207
208 /**
209  * @brief Asks and then saves modified files
210  */
211 BOOL CHexMergeDoc::PromptAndSaveIfNeeded(BOOL bAllowCancel)
212 {
213         BOOL bLModified = FALSE, bMModified = FALSE, bRModified = FALSE;
214
215         if (m_nBuffers == 3)
216         {
217                 bLModified = m_pView[0]->GetModified();
218                 bMModified = m_pView[1]->GetModified();
219                 bRModified = m_pView[2]->GetModified();
220         }
221         else
222         {
223                 bLModified = m_pView[0]->GetModified();
224                 bRModified = m_pView[1]->GetModified();
225         }
226         if (!bLModified && !bMModified && !bRModified)
227                  return TRUE;
228
229         const String &pathLeft = m_filePaths.GetLeft();
230         const String &pathMiddle = m_filePaths.GetMiddle();
231         const String &pathRight = m_filePaths.GetRight();
232
233         BOOL result = TRUE;
234         BOOL bLSaveSuccess = FALSE, bMSaveSuccess = FALSE, bRSaveSuccess = FALSE;
235
236         SaveClosingDlg dlg;
237         dlg.DoAskFor(bLModified, bMModified, bRModified);
238         if (!bAllowCancel)
239                 dlg.m_bDisableCancel = TRUE;
240         if (!pathLeft.empty())
241                 dlg.m_sLeftFile = pathLeft.c_str();
242         else
243                 dlg.m_sLeftFile = m_strDesc[0].c_str();
244         if (m_nBuffers == 3)
245         {
246                 if (!pathMiddle.empty())
247                         dlg.m_sMiddleFile = pathMiddle.c_str();
248                 else
249                         dlg.m_sMiddleFile = m_strDesc[1].c_str();
250         }
251         if (!pathRight.empty())
252                 dlg.m_sRightFile = pathRight.c_str();
253         else
254                 dlg.m_sRightFile = m_strDesc[1].c_str();
255
256         if (dlg.DoModal() == IDOK)
257         {
258                 if (bLModified)
259                 {
260                         if (dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
261                         {
262                                 switch (Try(m_pView[0]->SaveFile(pathLeft.c_str())))
263                                 {
264                                 case 0:
265                                         bLSaveSuccess = TRUE;
266                                         break;
267                                 case IDCANCEL:
268                                         result = FALSE;
269                                         break;
270                                 }
271                         }
272                         else
273                         {
274                                 m_pView[0]->SetModified(FALSE);
275                         }
276                 }
277                 if (bMModified)
278                 {
279                         if (dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
280                         {
281                                 switch (Try(m_pView[1]->SaveFile(pathMiddle.c_str())))
282                                 {
283                                 case 0:
284                                         bMSaveSuccess = TRUE;
285                                         break;
286                                 case IDCANCEL:
287                                         result = FALSE;
288                                         break;
289                                 }
290                         }
291                         else
292                         {
293                                 m_pView[1]->SetModified(FALSE);
294                         }
295                 }
296                 if (bRModified)
297                 {
298                         if (dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
299                         {
300                                 switch (Try(m_pView[m_nBuffers - 1]->SaveFile(pathRight.c_str())))
301                                 {
302                                 case 0:
303                                         bRSaveSuccess = TRUE;
304                                         break;
305                                 case IDCANCEL:
306                                         result = FALSE;
307                                         break;
308                                 }
309                         }
310                         else
311                         {
312                                 m_pView[m_nBuffers - 1]->SetModified(FALSE);
313                         }
314                 }
315         }
316         else
317         {       
318                 result = FALSE;
319         }
320
321         // If file were modified and saving was successfull,
322         // update status on dir view
323         if (bLSaveSuccess || bMSaveSuccess || bRSaveSuccess)
324         {
325                 UpdateDiffItem(m_pDirDoc);
326         }
327
328         return result;
329 }
330
331 /**
332  * @brief Save modified documents
333  */
334 BOOL CHexMergeDoc::SaveModified()
335 {
336         return PromptAndSaveIfNeeded(TRUE);
337 }
338
339 /**
340  * @brief Saves both files
341  */
342 void CHexMergeDoc::OnFileSave() 
343 {
344         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
345                 DoFileSave(nBuffer);
346 }
347
348 void CHexMergeDoc::DoFileSave(int nBuffer)
349 {
350         if (m_pView[nBuffer]->GetModified())
351         {
352                 if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
353                         DoFileSaveAs(nBuffer);
354                 else
355                 {
356                         const String &path = m_filePaths.GetPath(nBuffer);
357                         if (Try(m_pView[nBuffer]->SaveFile(path.c_str())) == IDCANCEL)
358                                 return;
359                 }
360                 UpdateDiffItem(m_pDirDoc);
361         }
362 }
363
364 void CHexMergeDoc::DoFileSaveAs(int nBuffer)
365 {
366         const String &path = m_filePaths.GetPath(nBuffer);
367         String strPath;
368         int id;
369         if (nBuffer == 0)
370                 id = IDS_SAVE_LEFT_AS;
371         else if (nBuffer == m_nBuffers - 1)
372                 id = IDS_SAVE_RIGHT_AS;
373         else
374                 id = IDS_SAVE_MIDDLE_AS;
375         if (SelectFile(AfxGetMainWnd()->GetSafeHwnd(), strPath, path.c_str(), id, NULL, FALSE))
376         {
377                 if (Try(m_pView[nBuffer]->SaveFile(strPath.c_str())) == IDCANCEL)
378                         return;
379                 if (path.empty())
380                 {
381                         // We are saving scratchpad (unnamed file)
382                         m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
383                         m_strDesc[nBuffer].erase();
384                 }
385
386                 m_filePaths.SetPath(nBuffer, strPath);
387                 UpdateDiffItem(m_pDirDoc);
388                 UpdateHeaderPath(nBuffer);
389         }
390 }
391
392 /**
393  * @brief Saves left-side file
394  */
395 void CHexMergeDoc::OnFileSaveLeft()
396 {
397         DoFileSave(0);
398 }
399
400 /**
401  * @brief Saves middle-side file
402  */
403 void CHexMergeDoc::OnFileSaveMiddle()
404 {
405         DoFileSave(1);
406 }
407
408 /**
409  * @brief Saves right-side file
410  */
411 void CHexMergeDoc::OnFileSaveRight()
412 {
413         DoFileSave(m_nBuffers - 1);
414 }
415
416 /**
417  * @brief Saves left-side file with name asked
418  */
419 void CHexMergeDoc::OnFileSaveAsLeft()
420 {
421         DoFileSaveAs(0);
422 }
423
424 /**
425  * @brief Saves right-side file with name asked
426  */
427 void CHexMergeDoc::OnFileSaveAsMiddle()
428 {
429         DoFileSaveAs(1);
430 }
431
432 /**
433  * @brief Saves right-side file with name asked
434  */
435 void CHexMergeDoc::OnFileSaveAsRight()
436 {
437         DoFileSaveAs(m_nBuffers - 1);
438 }
439
440 /**
441  * @brief Update diff-number pane text
442  */
443 void CHexMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI) 
444 {
445         String s;
446         pCmdUI->SetText(s.c_str());
447 }
448
449 /**
450  * @brief DirDoc gives us its identity just after it creates us
451  */
452 void CHexMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
453 {
454         ASSERT(pDirDoc && !m_pDirDoc);
455         m_pDirDoc = pDirDoc;
456 }
457
458 /**
459  * @brief Return pointer to parent frame
460  */
461 CHexMergeFrame * CHexMergeDoc::GetParentFrame() 
462 {
463         return static_cast<CHexMergeFrame *>(m_pView[0]->GetParentFrame()); 
464 }
465
466 /**
467  * @brief DirDoc is closing
468  */
469 void CHexMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
470 {
471         ASSERT(m_pDirDoc == pDirDoc);
472         m_pDirDoc = 0;
473 }
474
475 /**
476  * @brief DirDoc commanding us to close
477  */
478 BOOL CHexMergeDoc::CloseNow()
479 {
480         // Allow user to cancel closing
481         if (!PromptAndSaveIfNeeded(TRUE))
482                 return FALSE;
483
484         GetParentFrame()->CloseNow();
485         return TRUE;
486 }
487
488 /**
489 * @brief Load one file
490 */
491 HRESULT CHexMergeDoc::LoadOneFile(int index, LPCTSTR filename, BOOL readOnly)
492 {
493         if (filename[0])
494         {
495                 if (Try(m_pView[index]->LoadFile(filename), MB_ICONSTOP) != 0)
496                         return E_FAIL;
497                 m_pView[index]->SetReadOnly(readOnly);
498                 m_filePaths.SetPath(index, filename);
499                 ASSERT(m_nBufferType[index] == BUFFER_NORMAL); // should have been initialized to BUFFER_NORMAL in constructor
500                 String strDesc = theApp.m_strDescriptions[index];
501                 if (!strDesc.empty())
502                 {
503                         m_strDesc[index] = strDesc;
504                         m_nBufferType[index] = BUFFER_NORMAL_NAMED;
505                 }
506         }
507         else
508         {
509                 m_nBufferType[index] = BUFFER_UNNAMED;
510                 m_strDesc[index] = theApp.m_strDescriptions[index];
511
512         }
513         UpdateHeaderPath(index);
514         m_pView[index]->ResizeWindow();
515         return S_OK;
516 }
517
518 /**
519  * @brief Load files and initialize frame's compare result icon
520  */
521 HRESULT CHexMergeDoc::OpenDocs(const PathContext &paths, bool bRO[])
522 {
523         CHexMergeFrame *pf = GetParentFrame();
524         ASSERT(pf);
525         HRESULT hr;
526         int nBuffer;
527         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
528         {
529                 if (FAILED(hr = LoadOneFile(nBuffer, paths.GetPath(nBuffer).c_str(), bRO[nBuffer])))
530                         break;
531         }
532         if (nBuffer == m_nBuffers)
533         {
534                 UpdateDiffItem(0);
535                 // An extra ResizeWindow() on the left view aligns scroll ranges, and
536                 // also triggers initial diff coloring by invalidating the client area.
537                 m_pView[0]->ResizeWindow();
538                 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST))
539                         m_pView[0]->SendMessage(WM_COMMAND, ID_FIRSTDIFF);
540         }
541         else
542         {
543                 // Use verify macro to trap possible error in debug.
544                 VERIFY(pf->DestroyWindow());
545         }
546         return hr;
547 }
548
549 /**
550  * @brief Write path and filename to headerbar
551  * @note SetText() does not repaint unchanged text
552  */
553 void CHexMergeDoc::UpdateHeaderPath(int pane)
554 {
555         CHexMergeFrame *pf = GetParentFrame();
556         ASSERT(pf);
557         String sText;
558
559         if (m_nBufferType[pane] == BUFFER_UNNAMED ||
560                 m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
561         {
562                 sText = m_strDesc[pane];
563         }
564         else
565         {
566                 sText = m_filePaths.GetPath(pane);
567                 if (m_pDirDoc)
568                         m_pDirDoc->ApplyDisplayRoot(pane, sText);
569         }
570         if (m_pView[pane]->GetModified())
571                 sText.insert(0, _T("* "));
572         pf->GetHeaderInterface()->SetText(pane, sText.c_str());
573
574         SetTitle(NULL);
575 }
576
577 /**
578  * @brief Update document filenames to title
579  */
580 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
581 {
582         const TCHAR pszSeparator[] = _T(" - ");
583         String sTitle;
584         String sFileName[3];
585
586         if (lpszTitle)
587                 sTitle = lpszTitle;
588         else
589         {
590                 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
591                 {
592                         if (!m_strDesc[nBuffer].empty())
593                                 sFileName[nBuffer] = m_strDesc[nBuffer];
594                         else
595                         {
596                                 String file;
597                                 String ext;
598                                 paths_SplitFilename(m_filePaths[nBuffer], NULL, &file, &ext);
599                                 sFileName[nBuffer] += file.c_str();
600                                 if (!ext.empty())
601                                 {
602                                         sFileName[nBuffer] += _T(".");
603                                         sFileName[nBuffer] += ext.c_str();
604                                 }
605                         }
606                 }
607                 if (m_nBuffers < 3)
608                 {
609                         if (sFileName[0] == sFileName[1])
610                         {
611                                 sTitle = sFileName[0];
612                                 sTitle += _T(" x 2");
613                         }
614                         else
615                         {
616                                 sTitle = sFileName[0];
617                                 sTitle += pszSeparator;
618                                 sTitle += sFileName[1];
619                         }
620                 }
621                 else
622                 {
623                         if (sFileName[0] == sFileName[1] && sFileName[0] == sFileName[2])
624                         {
625                                 sTitle = sFileName[0];
626                                 sTitle += _T(" x 3");
627                         }
628                         else
629                         {
630                                 sTitle = sFileName[0];
631                                 sTitle += pszSeparator;
632                                 sTitle += sFileName[1];
633                                 sTitle += pszSeparator;
634                                 sTitle += sFileName[2];
635                         }
636                 }
637
638         }
639         CDocument::SetTitle(sTitle.c_str());
640 }
641
642 /**
643  * @brief We have two child views (left & right), so we keep pointers directly
644  * at them (the MFC view list doesn't have them both)
645  */
646 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
647 {
648         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
649         {
650                 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
651                 m_pView[nBuffer] = pView[nBuffer];
652         }
653 }
654
655 /**
656  * @brief Called when "Save left" item is updated
657  */
658 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
659 {
660         pCmdUI->Enable(m_pView[0]->GetModified());
661 }
662
663 /**
664  * @brief Called when "Save middle" item is updated
665  */
666 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
667 {
668         pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
669 }
670
671 /**
672  * @brief Called when "Save right" item is updated
673  */
674 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
675 {
676         pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
677 }
678
679 /**
680  * @brief Called when "Save" item is updated
681  */
682 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
683 {
684         BOOL bModified = FALSE;
685         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
686                 bModified |= m_pView[nBuffer]->GetModified();
687         pCmdUI->Enable(bModified);
688 }
689
690 /**
691  * @brief Copy selected bytes from source view to destination view
692  * @note Grows destination buffer as appropriate
693  */
694 void CHexMergeDoc::CopySel(CHexMergeView *pViewSrc, CHexMergeView *pViewDst)
695 {
696         const IHexEditorWindow::Status *pStatSrc = pViewSrc->GetStatus();
697         int i = min(pStatSrc->iStartOfSelection, pStatSrc->iEndOfSelection);
698         int j = max(pStatSrc->iStartOfSelection, pStatSrc->iEndOfSelection);
699         int u = pViewSrc->GetLength();
700         int v = pViewDst->GetLength();
701         if (pStatSrc->bSelected && i <= v)
702         {
703                 if (v <= j)
704                         v = j + 1;
705                 BYTE *p = pViewSrc->GetBuffer(u);
706                 BYTE *q = pViewDst->GetBuffer(v);
707                 memcpy(q + i, p + i, j - i + 1);
708                 CWnd *pwndFocus = CWnd::GetFocus();
709                 if (pwndFocus != pViewSrc)
710                         pViewDst->RepaintRange(i, j);
711                 if (pwndFocus != pViewDst)
712                         pViewSrc->RepaintRange(i, j);
713                 pViewDst->SetModified(TRUE);
714         }
715 }
716
717 /**
718  * @brief Copy all bytes from source view to destination view
719  * @note Grows destination buffer as appropriate
720  */
721 void CHexMergeDoc::CopyAll(CHexMergeView *pViewSrc, CHexMergeView *pViewDst)
722 {
723         if (int i = pViewSrc->GetLength())
724         {
725                 int j = pViewDst->GetLength();
726                 BYTE *p = pViewSrc->GetBuffer(i);
727                 BYTE *q = pViewDst->GetBuffer(max(i, j));
728                 if (q == 0)
729                         AfxThrowMemoryException();
730                 memcpy(q, p, i);
731                 CWnd *pwndFocus = CWnd::GetFocus();
732                 if (pwndFocus != pViewSrc)
733                         pViewDst->RepaintRange(0, i);
734                 if (pwndFocus != pViewDst)
735                         pViewSrc->RepaintRange(0, i);
736                 pViewDst->SetModified(TRUE);
737         }
738 }
739
740 /**
741  * @brief Copy selected bytes from left to right
742  */
743 void CHexMergeDoc::OnL2r()
744 {
745         CopySel(m_pView[0], m_pView[1]);
746 }
747
748 /**
749  * @brief Copy selected bytes from right to left
750  */
751 void CHexMergeDoc::OnR2l()
752 {
753         CopySel(m_pView[1], m_pView[0]);
754 }
755
756 /**
757  * @brief Copy all bytes from left to right
758  */
759 void CHexMergeDoc::OnAllRight()
760 {
761         CopyAll(m_pView[0], m_pView[1]);
762 }
763
764 /**
765  * @brief Copy all bytes from right to left
766  */
767 void CHexMergeDoc::OnAllLeft()
768 {
769         CopyAll(m_pView[1], m_pView[0]);
770 }
771
772 /**
773  * @brief Called when user selects View/Zoom In from menu.
774  */
775 void CHexMergeDoc::OnViewZoomIn()
776 {
777         for (int pane = 0; pane < m_nBuffers; pane++)
778                 m_pView[pane]->ZoomText(1);
779 }
780
781 /**
782  * @brief Called when user selects View/Zoom Out from menu.
783  */
784 void CHexMergeDoc::OnViewZoomOut()
785 {
786         for (int pane = 0; pane < m_nBuffers; pane++)
787                 m_pView[pane]->ZoomText(-1);
788 }
789
790 /**
791  * @brief Called when user selects View/Zoom Normal from menu.
792  */
793 void CHexMergeDoc::OnViewZoomNormal()
794 {
795         for (int pane = 0; pane < m_nBuffers; pane++)
796                 m_pView[pane]->ZoomText(0);
797 }