OSDN Git Service

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