OSDN Git Service

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