OSDN Git Service

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