OSDN Git Service

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