OSDN Git Service

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