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