OSDN Git Service

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