OSDN Git Service

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