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]->SetSavePoint();
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]->SetSavePoint();
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]->SetSavePoint();
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 /**
608  * @brief Customize a heksedit control's settings
609  */
610 static void Customize(IHexEditorWindow::Settings *settings)
611 {
612         settings->bSaveIni = FALSE;
613         //settings->iAutomaticBPL = FALSE;
614         //settings->iBytesPerLine = 16;
615         //settings->iFontSize = 8;
616 }
617
618 /**
619  * @brief Customize a heksedit control's colors
620  */
621 static void Customize(IHexEditorWindow::Colors *colors)
622 {
623         COptionsMgr *pOptionsMgr = GetOptionsMgr();
624         colors->iSelBkColorValue = RGB(224, 224, 224);
625         colors->iDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF);
626         colors->iSelDiffBkColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF);
627         colors->iDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_DIFF_TEXT);
628         if (colors->iDiffTextColorValue == 0xFFFFFFFF)
629                 colors->iDiffTextColorValue = 0;
630         colors->iSelDiffTextColorValue = pOptionsMgr->GetInt(OPT_CLR_SELECTED_DIFF_TEXT);
631         if (colors->iSelDiffTextColorValue == 0xFFFFFFFF)
632                 colors->iSelDiffTextColorValue = 0;
633 }
634
635 /**
636  * @brief Customize a heksedit control's settings and colors
637  */
638 static void Customize(IHexEditorWindow *pif)
639 {
640         Customize(pif->get_settings());
641         Customize(pif->get_colors());
642         //LANGID wLangID = (LANGID)GetThreadLocale();
643         //pif->load_lang(wLangID);
644 }
645
646 void CHexMergeDoc::RefreshOptions()
647 {
648         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
649         {
650                 IHexEditorWindow *pif = m_pView[nBuffer]->GetInterface();
651                 pif->read_ini_data();
652                 Customize(pif);
653                 pif->resize_window();
654         }
655 }
656
657 /**
658  * @brief Update document filenames to title
659  */
660 void CHexMergeDoc::SetTitle(LPCTSTR lpszTitle)
661 {
662         String sTitle;
663         String sFileName[3];
664
665         if (lpszTitle)
666                 sTitle = lpszTitle;
667         else
668         {
669                 for (int nBuffer = 0; nBuffer < m_filePaths.GetSize(); nBuffer++)
670                         sFileName[nBuffer] = !m_strDesc[nBuffer].empty() ? m_strDesc[nBuffer] : paths_FindFileName(m_filePaths[nBuffer]);
671                 if (std::count(&sFileName[0], &sFileName[0] + m_nBuffers, sFileName[0]) == m_nBuffers)
672                         sTitle = sFileName[0] + string_format(_T(" x %d"), m_nBuffers);
673                 else
674                         sTitle = string_join(&sFileName[0], &sFileName[0] + m_nBuffers, _T(" - "));
675         }
676         CDocument::SetTitle(sTitle.c_str());
677 }
678
679 /**
680  * @brief We have two child views (left & right), so we keep pointers directly
681  * at them (the MFC view list doesn't have them both)
682  */
683 void CHexMergeDoc::SetMergeViews(CHexMergeView *pView[])
684 {
685         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
686         {
687                 ASSERT(pView[nBuffer] && !m_pView[nBuffer]);
688                 m_pView[nBuffer] = pView[nBuffer];
689                 m_pView[nBuffer]->m_nThisPane = nBuffer;
690         }
691 }
692
693 /**
694  * @brief Called when "Save left" item is updated
695  */
696 void CHexMergeDoc::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
697 {
698         pCmdUI->Enable(m_pView[0]->GetModified());
699 }
700
701 /**
702  * @brief Called when "Save middle" item is updated
703  */
704 void CHexMergeDoc::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
705 {
706         pCmdUI->Enable(m_nBuffers == 3 && m_pView[1]->GetModified());
707 }
708
709 /**
710  * @brief Called when "Save right" item is updated
711  */
712 void CHexMergeDoc::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
713 {
714         pCmdUI->Enable(m_pView[m_nBuffers - 1]->GetModified());
715 }
716
717 /**
718  * @brief Called when "Save" item is updated
719  */
720 void CHexMergeDoc::OnUpdateFileSave(CCmdUI* pCmdUI)
721 {
722         BOOL bModified = FALSE;
723         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
724                 bModified |= m_pView[nBuffer]->GetModified();
725         pCmdUI->Enable(bModified);
726 }
727
728 /**
729  * @brief Reloads the opened files
730  */
731 void CHexMergeDoc::OnFileReload()
732 {
733         if (!PromptAndSaveIfNeeded(true))
734                 return;
735         
736         bool bRO[3];
737         for (int pane = 0; pane < m_nBuffers; pane++)
738         {
739                 bRO[pane] = !!m_pView[pane]->GetReadOnly();
740                 theApp.m_strDescriptions[pane] = m_strDesc[pane];
741         }
742         int nActivePane = GetActiveMergeView()->m_nThisPane;
743         OpenDocs(m_filePaths, bRO);
744 }
745
746 /**
747  * @brief Copy selected bytes from left to right
748  */
749 void CHexMergeDoc::OnL2r()
750 {
751         int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
752         int srcPane = dstPane - 1;
753         CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
754 }
755
756 /**
757  * @brief Copy selected bytes from right to left
758  */
759 void CHexMergeDoc::OnR2l()
760 {
761         int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
762         int srcPane = dstPane + 1;
763         CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
764 }
765
766 /**
767  * @brief Copy selected bytes from left to active pane
768  */
769 void CHexMergeDoc::OnCopyFromLeft()
770 {
771         int dstPane = GetActiveMergeView()->m_nThisPane;
772         int srcPane = (dstPane - 1 < 0) ? 0 : dstPane - 1;
773         CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
774 }
775
776 /**
777  * @brief Copy selected bytes from right to active pane
778  */
779 void CHexMergeDoc::OnCopyFromRight()
780 {
781         int dstPane = GetActiveMergeView()->m_nThisPane;
782         int srcPane = (dstPane + 1 > m_nBuffers - 1) ? m_nBuffers - 1 : dstPane + 1;
783         CHexMergeView::CopySel(m_pView[srcPane], m_pView[dstPane]);
784 }
785
786 /**
787  * @brief Copy all bytes from left to right
788  */
789 void CHexMergeDoc::OnAllRight()
790 {
791         int dstPane = (GetActiveMergeView()->m_nThisPane < m_nBuffers - 1) ? GetActiveMergeView()->m_nThisPane + 1 : m_nBuffers - 1;
792         int srcPane = dstPane - 1;
793         CHexMergeView::CopyAll(m_pView[srcPane], m_pView[dstPane]);
794 }
795
796 /**
797  * @brief Copy all bytes from right to left
798  */
799 void CHexMergeDoc::OnAllLeft()
800 {
801         int dstPane = (GetActiveMergeView()->m_nThisPane > 0) ? GetActiveMergeView()->m_nThisPane - 1 : 0;
802         int srcPane = dstPane + 1;
803         CHexMergeView::CopyAll(m_pView[srcPane], m_pView[dstPane]);
804 }
805
806 /**
807  * @brief Called when user selects View/Zoom In from menu.
808  */
809 void CHexMergeDoc::OnViewZoomIn()
810 {
811         for (int pane = 0; pane < m_nBuffers; pane++)
812                 m_pView[pane]->ZoomText(1);
813 }
814
815 /**
816  * @brief Called when user selects View/Zoom Out from menu.
817  */
818 void CHexMergeDoc::OnViewZoomOut()
819 {
820         for (int pane = 0; pane < m_nBuffers; pane++)
821                 m_pView[pane]->ZoomText(-1);
822 }
823
824 /**
825  * @brief Called when user selects View/Zoom Normal from menu.
826  */
827 void CHexMergeDoc::OnViewZoomNormal()
828 {
829         for (int pane = 0; pane < m_nBuffers; pane++)
830                 m_pView[pane]->ZoomText(0);
831 }