OSDN Git Service

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