OSDN Git Service

MergeEditView.cpp: Fix "Copy to Left and Advance" not work in 3-way file compare...
[winmerge-jp/winmerge-jp.git] / Src / MergeEditView.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  MergeEditView.cpp
23  *
24  * @brief Implementation of the CMergeEditView class
25  */
26
27 #include "StdAfx.h"
28 #include "MergeEditView.h"
29 #include <vector>
30 #include "BCMenu.h"
31 #include "Merge.h"
32 #include "LocationView.h"
33 #include "MergeDoc.h"
34 #include "MainFrm.h"
35 #include "OptionsMgr.h"
36 #include "OptionsDiffColors.h"
37 #include "FileTransform.h"
38 #include "Plugins.h"
39 #include "WMGotoDlg.h"
40 #include "OptionsDef.h"
41 #include "SyntaxColors.h"
42 #include "ChildFrm.h"
43 #include "MergeLineFlags.h"
44 #include "paths.h"
45 #include "DropHandler.h"
46 #include "DirDoc.h"
47
48 #ifdef _DEBUG
49 #define new DEBUG_NEW
50 #endif
51
52 using std::vector;
53
54 /** @brief Timer ID for delayed rescan. */
55 const UINT IDT_RESCAN = 2;
56 /** @brief Timer timeout for delayed rescan. */
57 const UINT RESCAN_TIMEOUT = 1000;
58
59 /** @brief Location for file compare specific help to open. */
60 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
61
62 /////////////////////////////////////////////////////////////////////////////
63 // CMergeEditView
64
65 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
66
67 CMergeEditView::CMergeEditView()
68 : m_bCurrentLineIsDiff(false)
69 , m_nThisPane(0)
70 , m_nThisGroup(0)
71 , m_bDetailView(false)
72 , m_piMergeEditStatus(nullptr)
73 , m_bAutomaticRescan(false)
74 , fTimerWaitingForIdle(0)
75 , m_lineBegin(0)
76 , m_lineEnd(-1)
77 , m_CurrentPredifferID(0)
78 {
79         SetParser(&m_xParser);
80         
81         Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
82 }
83
84 CMergeEditView::~CMergeEditView()
85 {
86 }
87
88
89 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
90         //{{AFX_MSG_MAP(CMergeEditView)
91         ON_COMMAND(ID_CURDIFF, OnCurdiff)
92         ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
93         ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
94         ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
95         ON_COMMAND(ID_EDIT_CUT, OnEditCut)
96         ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
97         ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
98         ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
99         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
100         ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
101         ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
102         ON_COMMAND(ID_LASTDIFF, OnLastdiff)
103         ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
104         ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
105         ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
106         ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
107         ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
108         ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
109         ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
110         ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
111         ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
112         ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
113         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
114         ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
115         ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
116         ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
117         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
118         ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
119         ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
120         ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
121         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
122         ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
123         ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
124         ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
125         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
126         ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
127         ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
128         ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
129         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
130         ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
131         ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
132         ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
133         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
134         ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
135         ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
136         ON_WM_LBUTTONDBLCLK()
137         ON_WM_LBUTTONUP()
138         ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
139         ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
140         ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
141         ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
142         ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
143         ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
144         ON_COMMAND(ID_L2R, OnL2r)
145         ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
146         ON_COMMAND(ID_R2L, OnR2l)
147         ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
148         ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
149         ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
150         ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
151         ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
152         ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
153         ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
154         ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
155         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
156         ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
157         ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
158         ON_WM_TIMER()
159         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
160         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
161         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
162         ON_COMMAND(ID_REFRESH, OnRefresh)
163         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
164         ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
165         ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
166         ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
167         ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
168         ON_WM_CONTEXTMENU()
169         ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
170         ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
171         ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
172         ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
173         ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
174         ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
175         ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
176         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
177         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
178         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
179         ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
180         ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
181         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
182         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
183         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
184         ON_COMMAND(ID_L2RNEXT, OnL2RNext)
185         ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
186         ON_COMMAND(ID_R2LNEXT, OnR2LNext)
187         ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
188         ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
189         ON_COMMAND(ID_NEXT_PANE, OnChangePane)
190         ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
191         ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
192         ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
193         ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
194         ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
195         ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
196         ON_WM_VSCROLL ()
197         ON_WM_HSCROLL ()
198         ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
199         ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
200         ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
201         ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
202         ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
203         ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
204         ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
205         ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
206         ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
207         ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
208         ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
209         ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
210         ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
211         ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
212         ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
213         ON_WM_SIZE()
214         ON_WM_MOVE()
215         ON_COMMAND(ID_HELP, OnHelp)
216         ON_COMMAND(ID_VIEW_FILEMARGIN, OnViewMargin)
217         ON_UPDATE_COMMAND_UI(ID_VIEW_FILEMARGIN, OnUpdateViewMargin)
218         ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
219         ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
220         ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
221         ON_WM_MOUSEWHEEL()
222         ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
223         ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
224         ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
225         ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
226         ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
227         //}}AFX_MSG_MAP
228 END_MESSAGE_MAP()
229
230
231 /////////////////////////////////////////////////////////////////////////////
232 // CMergeEditView diagnostics
233
234 #ifdef _DEBUG
235 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
236 {
237         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
238         return (CMergeDoc*)m_pDocument;
239 }
240
241 #endif //_DEBUG
242
243 /////////////////////////////////////////////////////////////////////////////
244 // CMergeEditView message handlers
245
246 /**
247  * @brief Return text buffer for file in view
248  */
249 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
250 {
251         return GetDocument()->m_ptBuf[m_nThisPane].get();
252 }
253
254 /**
255  * @brief Update any resources necessary after a GUI language change
256  */
257 void CMergeEditView::UpdateResources()
258 {
259 }
260
261 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
262 {
263         return GetDocument()->GetView(m_nThisGroup, nBuffer);
264 }
265
266 void CMergeEditView::PrimeListWithFile()
267 {
268         // Set the tab size now, just in case the options change...
269         // We don't update it at the end of OnOptions,
270         // we can update it safely now
271         SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
272 }
273 /**
274  * @brief Return text from line given
275  */
276 CString CMergeEditView::GetLineText(int idx)
277 {
278         return GetLineChars(idx);
279 }
280
281 /**
282  * @brief Return text from selection
283  */
284 CString CMergeEditView::GetSelectedText()
285 {
286         CPoint ptStart, ptEnd;
287         CString strText;
288         GetSelection(ptStart, ptEnd);
289         if (ptStart != ptEnd)
290                 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
291         return strText;
292 }
293
294 /**
295  * @brief Get diffs inside selection.
296  * @param [out] firstDiff First diff inside selection
297  * @param [out] lastDiff Last diff inside selection
298  * @note -1 is returned in parameters if diffs cannot be determined
299  * @todo This shouldn't be called when there is no diffs, so replace
300  * first 'if' with ASSERT()?
301  */
302 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
303 {
304         firstDiff = -1;
305         lastDiff = -1;
306
307         CMergeDoc *pd = GetDocument();
308         const int nDiffs = pd->m_diffList.GetSignificantDiffs();
309         if (nDiffs == 0)
310                 return;
311
312         int firstLine, lastLine;
313         GetFullySelectedLines(firstLine, lastLine);
314         if (lastLine < firstLine)
315                 return;
316
317         firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
318         lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
319         if (firstDiff != -1 && lastDiff != -1)
320         {
321                 DIFFRANGE di;
322                 
323                 // Check that first selected line is first diff's first line or above it
324                 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
325                 if ((int)di.dbegin < firstLine)
326                 {
327                         if (firstDiff < lastDiff)
328                                 ++firstDiff;
329                 }
330
331                 // Check that last selected line is last diff's last line or below it
332                 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
333                 if ((int)di.dend > lastLine)
334                 {
335                         if (firstDiff < lastDiff)
336                                 --lastDiff;
337                 }
338
339                 // Special case: one-line diff is not selected if cursor is in it
340                 if (firstLine == lastLine)
341                 {
342                         firstDiff = -1;
343                         lastDiff = -1;
344                 }
345         }
346 }
347
348 void CMergeEditView::OnInitialUpdate()
349 {
350         CCrystalEditViewEx::OnInitialUpdate();
351         SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
352         SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
353
354         m_lineBegin = 0;
355         m_lineEnd = -1;
356 }
357
358 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
359 {
360         CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
361
362         CMergeDoc* pDoc = GetDocument();
363         pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
364 }
365
366 std::vector<CCrystalTextView::TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
367 {
368         static const std::vector<TEXTBLOCK> emptyBlocks;
369         if (m_bDetailView)
370         {
371                 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
372                         return emptyBlocks;
373         }
374
375         DWORD dwLineFlags = GetLineFlags(nLineIndex);
376         if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
377                 return emptyBlocks;
378
379         if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
380                 return emptyBlocks;
381
382         CMergeDoc *pDoc = GetDocument();
383         if (pDoc->IsEditedAfterRescan(m_nThisPane))
384                 return emptyBlocks;
385         
386         vector<WordDiff> worddiffs;
387         int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
388         if (nDiff == -1)
389                 return emptyBlocks;
390
391         DIFFRANGE cd;
392         pDoc->m_diffList.GetDiff(nDiff, cd);
393         int unemptyLineCount = 0;
394         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
395         {
396                 if (cd.begin[nPane] != cd.end[nPane] + 1)
397                         unemptyLineCount++;
398         }
399         if (unemptyLineCount < 2)
400                 return emptyBlocks;
401
402         pDoc->GetWordDiffArray(nLineIndex, &worddiffs);
403         size_t nWordDiffs = worddiffs.size();
404
405         bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
406
407         std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
408         blocks[0].m_nCharPos = 0;
409         blocks[0].m_nColorIndex = COLORINDEX_NONE;
410         blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
411         size_t i, j;
412         for (i = 0, j = 1; i < nWordDiffs; i++)
413         {
414                 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
415                         continue;
416                 if (pDoc->m_nBuffers > 2)
417                 {
418                         if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
419                                 continue;
420                         else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
421                                 continue;
422                 }
423                 int begin[3], end[3];
424                 bool deleted = false;
425                 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
426                 {
427                         begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
428                         end[pane]   = (worddiffs[i].endline[pane]   > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
429                         if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
430                                 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
431                                 deleted = true;
432                 }
433                 blocks[j].m_nCharPos = begin[m_nThisPane];
434                 if (lineInCurrentDiff)
435                 {
436                         if (m_cachedColors.clrSelDiffText != CLR_NONE)
437                                 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
438                         else
439                                 blocks[j].m_nColorIndex = COLORINDEX_NONE;
440                         blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE | 
441                                 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
442                 }
443                 else
444                 {
445                         if (m_cachedColors.clrDiffText != CLR_NONE)
446                                 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
447                         else
448                                 blocks[j].m_nColorIndex = COLORINDEX_NONE;
449                         blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
450                                 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
451                 }
452                 j++;
453                 blocks[j].m_nCharPos = end[m_nThisPane];
454                 blocks[j].m_nColorIndex = COLORINDEX_NONE;
455                 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
456                 j++;
457         }
458
459         blocks.resize(j);
460
461         return blocks;
462 }
463
464 COLORREF CMergeEditView::GetColor(int nColorIndex)
465 {
466         switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
467         {
468         case COLORINDEX_HIGHLIGHTBKGND1:
469                 return m_cachedColors.clrSelWordDiff;
470         case COLORINDEX_HIGHLIGHTTEXT1:
471                 return m_cachedColors.clrSelWordDiffText;
472         case COLORINDEX_HIGHLIGHTBKGND2:
473                 return m_cachedColors.clrWordDiff;
474         case COLORINDEX_HIGHLIGHTTEXT2:
475                 return m_cachedColors.clrWordDiffText;
476         case COLORINDEX_HIGHLIGHTBKGND3:
477                 return m_cachedColors.clrWordDiffDeleted;
478         case COLORINDEX_HIGHLIGHTBKGND4:
479                 return m_cachedColors.clrSelWordDiffDeleted;
480
481         default:
482                 return CCrystalTextView::GetColor(nColorIndex);
483         }
484 }
485
486 /**
487  * @brief Determine text and background color for line
488  * @param [in] nLineIndex Index of line in view (NOT line in file)
489  * @param [out] crBkgnd Backround color for line
490  * @param [out] crText Text color for line
491  */
492 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
493                                 COLORREF & crText, bool & bDrawWhitespace)
494 {
495         DWORD ignoreFlags = 0;
496         GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
497 }
498
499 /**
500  * @brief Determine text and background color for line
501  * @param [in] nLineIndex Index of line in view (NOT line in file)
502  * @param [in] ignoreFlags Flags that caller wishes ignored
503  * @param [out] crBkgnd Backround color for line
504  * @param [out] crText Text color for line
505  *
506  * This version allows caller to suppress particular flags
507  */
508 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
509                                 COLORREF & crText, bool & bDrawWhitespace)
510 {
511         if (GetLineCount() <= nLineIndex)
512                 return;
513
514         DWORD dwLineFlags = GetLineFlags(nLineIndex);
515
516         if (dwLineFlags & ignoreFlags)
517                 dwLineFlags &= (~ignoreFlags);
518
519         if (m_bDetailView)
520         {
521                 // Line with WinMerge flag, 
522                 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
523                 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
524                 {
525                         crText = m_cachedColors.clrDiffText;
526                         bDrawWhitespace = true;
527
528                         if (dwLineFlags & LF_GHOST)
529                         {
530                                 crBkgnd = m_cachedColors.clrDiffDeleted;
531                         }
532                 }
533                 else
534                 {
535                         // If no syntax hilighting
536                         if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
537                         {
538                                 crBkgnd = GetColor (COLORINDEX_BKGND);
539                                 crText = GetColor (COLORINDEX_NORMALTEXT);
540                                 bDrawWhitespace = false;
541                         }
542                         else
543                                 // Line not inside diff, get colors from CrystalEditor
544                                 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
545                                         crText, bDrawWhitespace);
546                 }
547                 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
548                 {
549                         crBkgnd = GetColor (COLORINDEX_WHITESPACE);
550                         crText = GetColor (COLORINDEX_WHITESPACE);
551                         bDrawWhitespace = false;
552                 }
553                 return;
554         }
555
556         // Line inside diff
557         if (dwLineFlags & LF_WINMERGE_FLAGS)
558         {
559                 crText = m_cachedColors.clrDiffText;
560                 bDrawWhitespace = true;
561                 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
562
563                 if (dwLineFlags & LF_SNP)
564                 {
565                         if (lineInCurrentDiff)
566                         {
567                                 if (dwLineFlags & LF_GHOST)
568                                         crBkgnd = m_cachedColors.clrSelSNPDeleted;
569                                 else
570                                         crBkgnd = m_cachedColors.clrSelSNP;
571                                 crText = m_cachedColors.clrSelSNPText;
572                         }
573                         else
574                         {
575                                 if (dwLineFlags & LF_GHOST)
576                                         crBkgnd = m_cachedColors.clrSNPDeleted;
577                                 else
578                                         crBkgnd = m_cachedColors.clrSNP;
579                                 crText = m_cachedColors.clrSNPText;
580                         }
581                         return;
582                 }
583                 else if (dwLineFlags & LF_DIFF)
584                 {
585                         if (lineInCurrentDiff)
586                         {
587                                 if (dwLineFlags & LF_MOVED)
588                                 {
589                                         if (dwLineFlags & LF_GHOST)
590                                                 crBkgnd = m_cachedColors.clrSelMovedDeleted;
591                                         else
592                                                 crBkgnd = m_cachedColors.clrSelMoved;
593                                         crText = m_cachedColors.clrSelMovedText;
594                                 }
595                                 else
596                                 {
597                                         crBkgnd = m_cachedColors.clrSelDiff;
598                                         crText = m_cachedColors.clrSelDiffText;
599                                 }
600                         
601                         }
602                         else
603                         {
604                                 if (dwLineFlags & LF_MOVED)
605                                 {
606                                         if (dwLineFlags & LF_GHOST)
607                                                 crBkgnd = m_cachedColors.clrMovedDeleted;
608                                         else
609                                                 crBkgnd = m_cachedColors.clrMoved;
610                                         crText = m_cachedColors.clrMovedText;
611                                 }
612                                 else
613                                 {
614                                         crBkgnd = m_cachedColors.clrDiff;
615                                         crText = m_cachedColors.clrDiffText;
616                                 }
617                         }
618                         return;
619                 }
620                 else if (dwLineFlags & LF_TRIVIAL)
621                 {
622                         // trivial diff can not be selected
623                         if (dwLineFlags & LF_GHOST)
624                                 // ghost lines in trivial diff has their own color
625                                 crBkgnd = m_cachedColors.clrTrivialDeleted;
626                         else
627                                 crBkgnd = m_cachedColors.clrTrivial;
628                         crText = m_cachedColors.clrTrivialText;
629                         return;
630                 }
631                 else if (dwLineFlags & LF_GHOST)
632                 {
633                         if (lineInCurrentDiff)
634                                 crBkgnd = m_cachedColors.clrSelDiffDeleted;
635                         else
636                                 crBkgnd = m_cachedColors.clrDiffDeleted;
637                         return;
638                 }
639         }
640         else
641         {
642                 // Line not inside diff,
643                 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
644                 {
645                         // If no syntax hilighting, get windows default colors
646                         crBkgnd = GetColor (COLORINDEX_BKGND);
647                         crText = GetColor (COLORINDEX_NORMALTEXT);
648                         bDrawWhitespace = false;
649                 }
650                 else
651                         // Syntax highlighting, get colors from CrystalEditor
652                         CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
653                                 crText, bDrawWhitespace);
654         }
655 }
656
657 /**
658  * @brief Sync other pane position
659  */
660 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
661 {
662         CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
663         if (pSplitterWnd != nullptr)
664         {
665                 //  See CSplitterWnd::IdFromRowCol() implementation for details
666                 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
667                 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
668                 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
669                 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
670
671                 // limit the TopLine : must be smaller than GetLineCount for all the panels
672                 int newTopSubLine = m_nTopSubLine;
673                 int nRows = pSplitterWnd->GetRowCount ();
674                 int nCols = pSplitterWnd->GetColumnCount ();
675                 int nRow=0;
676 //              for (nRow = 0; nRow < nRows; nRow++)
677 //              {
678 //                      for (int nCol = 0; nCol < nCols; nCol++)
679 //                      {
680 //                              CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
681 //                              if (pSiblingView != nullptr)
682 //                                      if (pSiblingView->GetSubLineCount() <= newTopSubLine)
683 //                                              newTopSubLine = pSiblingView->GetSubLineCount()-1;
684 //                      }
685 //              }
686                 if (m_nTopSubLine != newTopSubLine)
687                         ScrollToSubLine(newTopSubLine);
688
689                 for (nRow = 0; nRow < nRows; nRow++)
690                 {
691                         for (int nCol = 0; nCol < nCols; nCol++)
692                         {
693                                 if (!(nRow == nCurrentRow && nCol == nCurrentCol))  //  We don't need to update ourselves
694                                 {
695                                         CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
696                                         if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
697                                                 pSiblingView->OnUpdateSibling (this, bHorz);
698                                 }
699                         }
700                 }
701         }
702 }
703
704 /**
705  * @brief Update other panes
706  */
707 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
708 {
709         if (pUpdateSource != this)
710         {
711                 ASSERT (pUpdateSource != nullptr);
712                 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
713                 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
714                 if (!bHorz)  // changed this so bHorz works right
715                 {
716                         ASSERT (pSrcView->m_nTopSubLine >= 0);
717
718                         // This ASSERT is wrong: panes have different files and
719                         // different linecounts
720                         // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
721                         if (pSrcView->m_nTopSubLine != m_nTopSubLine)
722                         {
723                                 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
724                                 UpdateCaret ();
725                                 RecalcVertScrollBar(true);
726                         }
727                 }
728                 else
729                 {
730                         ASSERT (pSrcView->m_nOffsetChar >= 0);
731
732                         // This ASSERT is wrong: panes have different files and
733                         // different linelengths
734                         // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
735                         if (pSrcView->m_nOffsetChar != m_nOffsetChar)
736                         {
737                                 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
738                                 UpdateCaret ();
739                                 RecalcHorzScrollBar(true);
740                         }
741                 }
742         }
743 }
744
745 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
746 {
747         int newlineBegin, newlineEnd;
748         CMergeDoc *pd = GetDocument();
749         if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
750         {
751                 newlineBegin = 0;
752                 newlineEnd = -1;
753         }
754         else
755         {
756                 DIFFRANGE curDiff;
757                 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
758
759                 newlineBegin = curDiff.dbegin;
760                 ASSERT (newlineBegin >= 0);
761                 newlineEnd = curDiff.dend;
762         }
763
764         if (newlineBegin == m_lineBegin && newlineEnd == m_lineEnd)
765                 return;
766         m_lineBegin = newlineBegin;
767         m_lineEnd = newlineEnd;
768
769         // scroll to the first line of the diff
770         ScrollToLine(m_lineBegin);
771
772         // tell the others views about this diff (no need to call UpdateSiblingScrollPos)
773         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
774
775         // pSplitterWnd is `nullptr` if WinMerge started minimized.
776         if (pSplitterWnd != nullptr)
777         {
778                 int nRows = pSplitterWnd->GetRowCount ();
779                 int nCols = pSplitterWnd->GetColumnCount ();
780                 for (int nRow = 0; nRow < nRows; nRow++)
781                 {
782                         for (int nCol = 0; nCol < nCols; nCol++)
783                         {
784                                 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
785                                 if (pSiblingView != nullptr)
786                                         pSiblingView->OnDisplayDiff(nDiff);
787                         }
788                 }
789         }
790
791         // update the width of the horizontal scrollbar
792         RecalcHorzScrollBar();
793 }
794
795 /**
796  * @brief Selects diff by number and syncs other file
797  * @param [in] nDiff Diff to select, must be >= 0
798  * @param [in] bScroll Scroll diff to view
799  * @param [in] bSelectText Select diff text
800  * @sa CMergeEditView::ShowDiff()
801  * @sa CMergeDoc::SetCurrentDiff()
802  * @todo Parameter bSelectText is never used?
803  */
804 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
805 {
806         CMergeDoc *pd = GetDocument();
807
808         // Check that nDiff is valid
809         if (nDiff < 0)
810                 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
811         if (nDiff >= pd->m_diffList.GetSize())
812                 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
813                         nDiff, pd->m_diffList.GetSize());
814
815         SelectNone();
816         pd->SetCurrentDiff(nDiff);
817         ShowDiff(bScroll, bSelectText);
818         pd->UpdateAllViews(this);
819         UpdateSiblingScrollPos(false);
820
821         // notify either side, as it will notify the other one
822         pd->ForEachView (0, [&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
823 }
824
825 /**
826  * @brief Called when user selects "Current Difference".
827  * Goes to active diff. If no active diff, selects diff under cursor
828  * @sa CMergeEditView::SelectDiff()
829  * @sa CMergeDoc::GetCurrentDiff()
830  * @sa CMergeDoc::LineToDiff()
831  */
832 void CMergeEditView::OnCurdiff()
833 {
834         CMergeDoc *pd = GetDocument();
835
836         // If no diffs, nothing to select
837         if (!pd->m_diffList.HasSignificantDiffs())
838                 return;
839
840         // GetCurrentDiff() returns -1 if no diff selected
841         int nDiff = pd->GetCurrentDiff();
842         if (nDiff != -1)
843         {
844                 // Scroll to the first line of the currently selected diff
845                 SelectDiff(nDiff, true, false);
846         }
847         else
848         {
849                 // If cursor is inside diff, select that diff
850                 CPoint pos = GetCursorPos();
851                 nDiff = pd->m_diffList.LineToDiff(pos.y);
852                 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
853                         SelectDiff(nDiff, true, false);
854         }
855 }
856
857 /**
858  * @brief Called when "Current diff" item is updated
859  */
860 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
861 {
862         CMergeDoc *pd = GetDocument();
863         CPoint pos = GetCursorPos();
864         int nCurrentDiff = pd->GetCurrentDiff();
865         if (nCurrentDiff == -1)
866         {
867                 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
868                 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
869         }
870         else
871                 pCmdUI->Enable(true);
872 }
873
874 /**
875  * @brief Copy selected text to clipboard
876  */
877 void CMergeEditView::OnEditCopy()
878 {
879         CMergeDoc * pDoc = GetDocument();
880         CPoint ptSelStart, ptSelEnd;
881         GetSelection(ptSelStart, ptSelEnd);
882
883         // Nothing selected
884         if (ptSelStart == ptSelEnd)
885                 return;
886
887         CString text;
888
889         if (!m_bColumnSelection)
890         {
891                 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
892
893                 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
894                         ptSelEnd.y, ptSelEnd.x, text);
895         }
896         else
897                 GetTextWithoutEmptysInColumnSelection(text);
898
899         PutToClipboard(text, text.GetLength(), m_bColumnSelection);
900 }
901
902 /**
903  * @brief Called when "Copy" item is updated
904  */
905 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
906 {
907         CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
908 }
909
910 /**
911  * @brief Cut current selection to clipboard
912  */
913 void CMergeEditView::OnEditCut()
914 {
915         if (IsReadOnly(m_nThisPane))
916                 return;
917
918         CPoint ptSelStart, ptSelEnd;
919         CMergeDoc * pDoc = GetDocument();
920         GetSelection(ptSelStart, ptSelEnd);
921
922         // Nothing selected
923         if (ptSelStart == ptSelEnd)
924                 return;
925
926         CString text;
927         if (!m_bColumnSelection)
928                 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
929                         ptSelEnd.y, ptSelEnd.x, text);
930         else
931                 GetTextWithoutEmptysInColumnSelection(text);
932
933         PutToClipboard(text, text.GetLength(), m_bColumnSelection);
934
935         if (!m_bColumnSelection)
936         {
937                 CPoint ptCursorPos = ptSelStart;
938                 ASSERT_VALIDTEXTPOS(ptCursorPos);
939                 SetAnchor(ptCursorPos);
940                 SetSelection(ptCursorPos, ptCursorPos);
941                 SetCursorPos(ptCursorPos);
942                 EnsureVisible(ptCursorPos);
943
944                 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
945                         ptSelEnd.x, CE_ACTION_CUT);
946         }
947         else
948                 DeleteCurrentColumnSelection (CE_ACTION_CUT);
949
950         m_pTextBuffer->SetModified(true);
951 }
952
953 /**
954  * @brief Called when "Cut" item is updated
955  */
956 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
957 {
958         if (!IsReadOnly(m_nThisPane))
959                 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
960         else
961                 pCmdUI->Enable(false);
962 }
963
964 /**
965  * @brief Paste text from clipboard
966  */
967 void CMergeEditView::OnEditPaste()
968 {
969         if (IsReadOnly(m_nThisPane))
970                 return;
971
972         CCrystalEditViewEx::Paste();
973         m_pTextBuffer->SetModified(true);
974 }
975
976 /**
977  * @brief Called when "Paste" item is updated
978  */
979 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
980 {
981         if (!IsReadOnly(m_nThisPane))
982                 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
983         else
984                 pCmdUI->Enable(false);
985 }
986
987 /**
988  * @brief Undo last action
989  */
990 void CMergeEditView::OnEditUndo()
991 {
992         CWaitCursor waitstatus;
993         CMergeDoc* pDoc = GetDocument();
994         CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
995         if(tgt==this)
996         {
997                 if (IsReadOnly(m_nThisPane))
998                         return;
999
1000                 GetParentFrame()->SetActiveView(this, true);
1001                 if(CCrystalEditViewEx::DoEditUndo())
1002                 {
1003                         --pDoc->curUndo;
1004                         pDoc->UpdateHeaderPath(m_nThisPane);
1005                         pDoc->FlushAndRescan();
1006
1007                         int nAction;
1008                         m_pTextBuffer->GetRedoActionCode(nAction);
1009                         if (nAction == CE_ACTION_MERGE)
1010                                 // select the diff so we may just merge it again
1011                                 OnCurdiff();
1012                 }
1013         }
1014         else
1015         {
1016                 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1017         }
1018         if (!pDoc->CanUndo())
1019                 pDoc->SetAutoMerged(false);
1020 }
1021
1022 /**
1023  * @brief Called when "Undo" item is updated
1024  */
1025 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1026 {
1027         CMergeDoc* pDoc = GetDocument();
1028         if (pDoc->curUndo!=pDoc->undoTgt.begin())
1029         {
1030                 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1031                 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1032         }
1033         else
1034                 pCmdUI->Enable(false);
1035 }
1036
1037 /**
1038  * @brief Go to first diff
1039  *
1040  * Called when user selects "First Difference"
1041  * @sa CMergeEditView::SelectDiff()
1042  */
1043 void CMergeEditView::OnFirstdiff()
1044 {
1045         CMergeDoc *pd = GetDocument();
1046         if (pd->m_diffList.HasSignificantDiffs())
1047         {
1048                 int nDiff = pd->m_diffList.FirstSignificantDiff();
1049                 SelectDiff(nDiff, true, false);
1050         }
1051 }
1052
1053 /**
1054  * @brief Update "First diff" UI items
1055  */
1056 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1057 {
1058         OnUpdatePrevdiff(pCmdUI);
1059 }
1060
1061 /**
1062  * @brief Go to last diff
1063  */
1064 void CMergeEditView::OnLastdiff()
1065 {
1066         CMergeDoc *pd = GetDocument();
1067         if (pd->m_diffList.HasSignificantDiffs())
1068         {
1069                 int nDiff = pd->m_diffList.LastSignificantDiff();
1070                 SelectDiff(nDiff, true, false);
1071         }
1072 }
1073
1074 /**
1075  * @brief Update "Last diff" UI items
1076  */
1077 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1078 {
1079         OnUpdateNextdiff(pCmdUI);
1080 }
1081
1082 /**
1083  * @brief Go to next diff and select it.
1084  *
1085  * Finds and selects next difference. There are several cases:
1086  * - if there is selected difference, and that difference is visible
1087  * on screen, next found difference is selected.
1088  * - if there is selected difference but it is not visible, next
1089  * difference from cursor position is selected. This is what user
1090  * expects to happen and is natural thing to do. Also reduces
1091  * needless scrolling.
1092  * - if there is no selected difference, next difference from cursor
1093  * position is selected.
1094  */
1095 void CMergeEditView::OnNextdiff()
1096 {
1097         CMergeDoc *pd = GetDocument();
1098         int cnt = pd->m_ptBuf[0]->GetLineCount();
1099         if (cnt <= 0)
1100                 return;
1101
1102         // Returns -1 if no diff selected
1103         int nextDiff = -1;
1104         int curDiff = pd->GetCurrentDiff();
1105         if (curDiff != -1)
1106         {
1107                 // We're on a diff
1108                 if (!IsDiffVisible(curDiff))
1109                 {
1110                         // Selected difference not visible, select next from cursor
1111                         int line = GetCursorPos().y;
1112                         // Make sure we aren't in the first line of the diff
1113                         ++line;
1114                         if (!IsValidTextPosY(CPoint(0, line)))
1115                                 line = m_nTopLine;
1116                         nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1117                 }
1118                 else
1119                 {
1120                         // Find out if there is a following significant diff
1121                         if (curDiff < pd->m_diffList.GetSize() - 1)
1122                         {
1123                                 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1124                         }
1125                 }
1126         }
1127         else
1128         {
1129                 // We don't have a selected difference,
1130                 // but cursor can be inside inactive diff
1131                 int line = GetCursorPos().y;
1132                 if (!IsValidTextPosY(CPoint(0, line)))
1133                         line = m_nTopLine;
1134                 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1135         }
1136
1137         int lastDiff = pd->m_diffList.LastSignificantDiff();
1138         if (nextDiff >= 0 && nextDiff <= lastDiff)
1139                 SelectDiff(nextDiff, true, false);
1140         else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1141         {
1142                 if (pDirDoc->MoveableToNextDiff())
1143                         pDirDoc->MoveToNextDiff(pd);
1144         }
1145 }
1146
1147 /**
1148  * @brief Update "Next diff" UI items
1149  */
1150 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1151 {
1152         CMergeDoc *pd = GetDocument();
1153         const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1154         bool enabled;
1155
1156         if (dfi == nullptr)
1157         {
1158                 // There aren't any significant differences
1159                 enabled = false;
1160         }
1161         else
1162         {
1163                 // Enable if the beginning of the last significant difference is after caret
1164                 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1165         }
1166
1167         if (!enabled && pd->GetDirDoc())
1168                 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1169
1170         pCmdUI->Enable(enabled);
1171 }
1172
1173 /**
1174  * @brief Go to previous diff and select it.
1175  *
1176  * Finds and selects previous difference. There are several cases:
1177  * - if there is selected difference, and that difference is visible
1178  * on screen, previous found difference is selected.
1179  * - if there is selected difference but it is not visible, previous
1180  * difference from cursor position is selected. This is what user
1181  * expects to happen and is natural thing to do. Also reduces
1182  * needless scrolling.
1183  * - if there is no selected difference, previous difference from cursor
1184  * position is selected.
1185  */
1186 void CMergeEditView::OnPrevdiff()
1187 {
1188         CMergeDoc *pd = GetDocument();
1189         int cnt = pd->m_ptBuf[0]->GetLineCount();
1190         if (cnt <= 0)
1191                 return;
1192
1193         // GetCurrentDiff() returns -1 if no diff selected
1194         int prevDiff = -1;
1195         int curDiff = pd->GetCurrentDiff();
1196         if (curDiff != -1)
1197         {
1198                 // We're on a diff
1199                 if (!IsDiffVisible(curDiff))
1200                 {
1201                         // Selected difference not visible, select previous from cursor
1202                         int line = GetCursorPos().y;
1203                         // Make sure we aren't in the last line of the diff
1204                         --line;
1205                         if (!IsValidTextPosY(CPoint(0, line)))
1206                                 line = m_nTopLine;
1207                         prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1208                 }
1209                 else
1210                 {
1211                         // Find out if there is a preceding significant diff
1212                         if (curDiff > 0)
1213                         {
1214                                 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1215                         }
1216                 }
1217         }
1218         else
1219         {
1220                 // We don't have a selected difference,
1221                 // but cursor can be inside inactive diff
1222                 int line = GetCursorPos().y;
1223                 if (!IsValidTextPosY(CPoint(0, line)))
1224                         line = m_nTopLine;
1225                 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1226         }
1227
1228         int firstDiff = pd->m_diffList.FirstSignificantDiff();
1229         if (prevDiff >= 0 && prevDiff >= firstDiff)
1230                 SelectDiff(prevDiff, true, false);
1231         else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1232         {
1233                 if (pDirDoc->MoveableToPrevDiff())
1234                         pDirDoc->MoveToPrevDiff(pd);
1235         }
1236 }
1237
1238 /**
1239  * @brief Update "Previous diff" UI items
1240  */
1241 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1242 {
1243         CMergeDoc *pd = GetDocument();
1244         const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1245         bool enabled;
1246
1247         if (dfi == nullptr)
1248         {
1249                 // There aren't any significant differences
1250                 enabled = false;
1251         }
1252         else
1253         {
1254                 // Enable if the end of the first significant difference is before caret
1255                 enabled = (GetCursorPos().y > (long)dfi->dend);
1256         }
1257
1258         if (!enabled && pd->GetDirDoc())
1259                 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1260
1261         pCmdUI->Enable(enabled);
1262 }
1263
1264 void CMergeEditView::OnNextConflict()
1265 {
1266         OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1267 }
1268
1269 /**
1270  * @brief Update "Next Conflict" UI items
1271  */
1272 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1273 {
1274         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1275 }
1276
1277 void CMergeEditView::OnPrevConflict()
1278 {
1279         OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1280 }
1281
1282 /**
1283  * @brief Update "Prev Conflict" UI items
1284  */
1285 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1286 {
1287         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1288 }
1289
1290 /**
1291  * @brief Go to next 3-way diff and select it.
1292  */
1293 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1294 {
1295         CMergeDoc *pd = GetDocument();
1296         int cnt = pd->m_ptBuf[0]->GetLineCount();
1297         if (cnt <= 0)
1298                 return;
1299
1300         // Returns -1 if no diff selected
1301         int curDiff = pd->GetCurrentDiff();
1302         if (curDiff != -1)
1303         {
1304                 // We're on a diff
1305                 int nextDiff = curDiff;
1306                 if (!IsDiffVisible(curDiff))
1307                 {
1308                         // Selected difference not visible, select next from cursor
1309                         int line = GetCursorPos().y;
1310                         // Make sure we aren't in the first line of the diff
1311                         ++line;
1312                         if (!IsValidTextPosY(CPoint(0, line)))
1313                                 line = m_nTopLine;
1314                         nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1315                 }
1316                 else
1317                 {
1318                         // Find out if there is a following significant diff
1319                         if (curDiff < pd->m_diffList.GetSize() - 1)
1320                         {
1321                                 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1322                         }
1323                 }
1324                 if (nextDiff == -1)
1325                         nextDiff = curDiff;
1326
1327                 // nextDiff is the next one if there is one, else it is the one we're on
1328                 SelectDiff(nextDiff, true, false);
1329         }
1330         else
1331         {
1332                 // We don't have a selected difference,
1333                 // but cursor can be inside inactive diff
1334                 int line = GetCursorPos().y;
1335                 if (!IsValidTextPosY(CPoint(0, line)))
1336                         line = m_nTopLine;
1337                 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1338                 if (curDiff >= 0)
1339                         SelectDiff(curDiff, true, false);
1340         }
1341 }
1342
1343 /**
1344  * @brief Update "Next 3-way diff" UI items
1345  */
1346 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1347 {
1348         CMergeDoc *pd = GetDocument();
1349
1350         if (pd->m_nBuffers < 3)
1351         {
1352                 pCmdUI->Enable(false);
1353                 return;
1354         }
1355
1356         const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1357
1358         if (dfi == nullptr)
1359         {
1360                 // There aren't any significant differences
1361                 pCmdUI->Enable(false);
1362         }
1363         else
1364         {
1365                 // Enable if the beginning of the last significant difference is after caret
1366                 CPoint pos = GetCursorPos();
1367                 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1368         }
1369 }
1370
1371 /**
1372  * @brief Go to previous 3-way diff and select it.
1373  */
1374 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1375 {
1376         CMergeDoc *pd = GetDocument();
1377
1378         int cnt = pd->m_ptBuf[0]->GetLineCount();
1379         if (cnt <= 0)
1380                 return;
1381
1382         // GetCurrentDiff() returns -1 if no diff selected
1383         int curDiff = pd->GetCurrentDiff();
1384         if (curDiff != -1)
1385         {
1386                 // We're on a diff
1387                 int prevDiff = curDiff;
1388                 if (!IsDiffVisible(curDiff))
1389                 {
1390                         // Selected difference not visible, select previous from cursor
1391                         int line = GetCursorPos().y;
1392                         // Make sure we aren't in the last line of the diff
1393                         --line;
1394                         if (!IsValidTextPosY(CPoint(0, line)))
1395                                 line = m_nTopLine;
1396                         prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1397                 }
1398                 else
1399                 {
1400                         // Find out if there is a preceding significant diff
1401                         if (curDiff > 0)
1402                         {
1403                                 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1404                         }
1405                 }
1406                 if (prevDiff == -1)
1407                         prevDiff = curDiff;
1408
1409                 // prevDiff is the preceding one if there is one, else it is the one we're on
1410                 SelectDiff(prevDiff, true, false);
1411         }
1412         else
1413         {
1414                 // We don't have a selected difference,
1415                 // but cursor can be inside inactive diff
1416                 int line = GetCursorPos().y;
1417                 if (!IsValidTextPosY(CPoint(0, line)))
1418                         line = m_nTopLine;
1419                 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1420                 if (curDiff >= 0)
1421                         SelectDiff(curDiff, true, false);
1422         }
1423 }
1424
1425 /**
1426  * @brief Update "Previous diff X and Y" UI items
1427  */
1428 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1429 {
1430         CMergeDoc *pd = GetDocument();
1431
1432         if (pd->m_nBuffers < 3)
1433         {
1434                 pCmdUI->Enable(false);
1435                 return;
1436         }
1437
1438         const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1439
1440         if (dfi == nullptr)
1441         {
1442                 // There aren't any significant differences
1443                 pCmdUI->Enable(false);
1444         }
1445         else
1446         {
1447                 // Enable if the end of the first significant difference is before caret
1448                 CPoint pos = GetCursorPos();
1449                 pCmdUI->Enable(pos.y > (long)dfi->dend);
1450         }
1451 }
1452
1453 void CMergeEditView::OnNextdiffLM()
1454 {
1455         OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1456 }
1457
1458 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1459 {
1460         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1461 }
1462
1463 void CMergeEditView::OnNextdiffLR()
1464 {
1465         OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1466 }
1467
1468 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1469 {
1470         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1471 }
1472
1473 void CMergeEditView::OnNextdiffMR()
1474 {
1475         OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1476 }
1477
1478 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1479 {
1480         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1481 }
1482
1483 void CMergeEditView::OnNextdiffLO()
1484 {
1485         OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1486 }
1487
1488 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1489 {
1490         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1491 }
1492
1493 void CMergeEditView::OnNextdiffMO()
1494 {
1495         OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1496 }
1497
1498 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1499 {
1500         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1501 }
1502
1503 void CMergeEditView::OnNextdiffRO()
1504 {
1505         OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1506 }
1507
1508 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1509 {
1510         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1511 }
1512
1513 void CMergeEditView::OnPrevdiffLM()
1514 {
1515         OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1516 }
1517
1518 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1519 {
1520         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1521 }
1522
1523 void CMergeEditView::OnPrevdiffLR()
1524 {
1525         OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1526 }
1527
1528 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1529 {
1530         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1531 }
1532
1533 void CMergeEditView::OnPrevdiffMR()
1534 {
1535         OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1536 }
1537
1538 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1539 {
1540         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1541 }
1542
1543 void CMergeEditView::OnPrevdiffLO()
1544 {
1545         OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1546 }
1547
1548 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1549 {
1550         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1551 }
1552
1553 void CMergeEditView::OnPrevdiffMO()
1554 {
1555         OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1556 }
1557
1558 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1559 {
1560         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1561 }
1562
1563 void CMergeEditView::OnPrevdiffRO()
1564 {
1565         OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1566 }
1567
1568 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1569 {
1570         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1571 }
1572
1573 /**
1574  * @brief Clear selection
1575  */
1576 void CMergeEditView::SelectNone()
1577 {
1578         SetSelection (GetCursorPos(), GetCursorPos());
1579         UpdateCaret();
1580 }
1581
1582 /**
1583  * @brief Check if line is inside currently selected diff
1584  * @param [in] nLine 0-based linenumber in view
1585  * @sa CMergeDoc::GetCurrentDiff()
1586  * @sa CMergeDoc::LineInDiff()
1587  */
1588 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1589 {
1590         // Check validity of nLine
1591 #ifdef _DEBUG
1592         if (nLine < 0)
1593                 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1594         int nLineCount = LocateTextBuffer()->GetLineCount();
1595         if (nLine >= nLineCount)
1596                 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1597 #endif
1598
1599         const CMergeDoc *pd = GetDocument();
1600         int curDiff = pd->GetCurrentDiff();
1601         if (curDiff == -1)
1602                 return false;
1603         return pd->m_diffList.LineInDiff(nLine, curDiff);
1604 }
1605
1606 /**
1607  * @brief Called when mouse left-button double-clicked
1608  *
1609  * Double-clicking mouse inside diff selects that diff
1610  */
1611 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1612 {
1613         CMergeDoc *pd = GetDocument();
1614         CPoint pos = GetCursorPos();
1615
1616         int diff = pd->m_diffList.LineToDiff(pos.y);
1617         if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1618                 SelectDiff(diff, false, false);
1619
1620         CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1621 }
1622
1623 /**
1624  * @brief Called when mouse left button is released.
1625  *
1626  * If button is released outside diffs, current diff
1627  * is deselected.
1628  */
1629 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1630 {
1631         CMergeDoc *pd = GetDocument();
1632         CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1633
1634         // If we have a selected diff, deselect it
1635         int nCurrentDiff = pd->GetCurrentDiff();
1636         if (nCurrentDiff != -1)
1637         {
1638                 CPoint pos = GetCursorPos();
1639                 if (!IsLineInCurrentDiff(pos.y))
1640                 {
1641                         pd->SetCurrentDiff(-1);
1642                         Invalidate();
1643                         pd->UpdateAllViews(this);
1644                 }
1645         }
1646 }
1647
1648 void CMergeEditView::OnX2Y(int srcPane, int dstPane)
1649 {
1650         // Check that right side is not readonly
1651         if (IsReadOnly(dstPane))
1652                 return;
1653
1654         CMergeDoc *pDoc = GetDocument();
1655         int currentDiff = pDoc->GetCurrentDiff();
1656
1657         if (currentDiff == -1)
1658         {
1659                 // No selected diff
1660                 // If cursor is inside diff get number of that diff
1661                 if (m_bCurrentLineIsDiff)
1662                 {
1663                         CPoint pt = GetCursorPos();
1664                         currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1665                 }
1666         }
1667
1668         int firstDiff, lastDiff;
1669         GetFullySelectedDiffs(firstDiff, lastDiff);
1670
1671         if (firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff))
1672         {
1673                 CWaitCursor waitstatus;
1674                 if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff) && !IsSelection())
1675                         pDoc->ListCopy(srcPane, dstPane, currentDiff);
1676                 else
1677                         pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1678         }
1679         else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1680         {
1681                 CWaitCursor waitstatus;
1682                 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1683         }
1684 }
1685
1686 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1687 {
1688         // Check that right side is not readonly
1689         if (!IsReadOnly(dstPane))
1690         {
1691                 int firstDiff, lastDiff;
1692                 GetFullySelectedDiffs(firstDiff, lastDiff);
1693
1694                 // If one or more diffs inside selection OR
1695                 // there is an active diff OR
1696                 // cursor is inside diff
1697                 if (firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff))
1698                         pCmdUI->Enable(true);
1699                 else
1700                 {
1701                         const int currDiff = GetDocument()->GetCurrentDiff();
1702                         if (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff))
1703                                 pCmdUI->Enable(true);
1704                         else
1705                                 pCmdUI->Enable(m_bCurrentLineIsDiff);
1706                 }
1707         }
1708         else
1709                 pCmdUI->Enable(false);
1710 }
1711
1712 /**
1713  * @brief Copy diff from left pane to right pane
1714  *
1715  * Difference is copied from left to right when
1716  * - difference is selected
1717  * - difference is inside selection (allows merging multiple differences).
1718  * - cursor is inside diff
1719  *
1720  * If there is selected diff outside selection, we copy selected
1721  * difference only.
1722  */
1723 void CMergeEditView::OnL2r()
1724 {
1725         int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1726         int srcPane = dstPane - 1;
1727         OnX2Y(srcPane, dstPane);
1728 }
1729
1730 /**
1731  * @brief Called when "Copy to left" item is updated
1732  */
1733 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
1734 {
1735         OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
1736 }
1737
1738 /**
1739  * @brief Copy diff from right pane to left pane
1740  *
1741  * Difference is copied from left to right when
1742  * - difference is selected
1743  * - difference is inside selection (allows merging multiple differences).
1744  * - cursor is inside diff
1745  *
1746  * If there is selected diff outside selection, we copy selected
1747  * difference only.
1748  */
1749 void CMergeEditView::OnR2l()
1750 {
1751         int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
1752         int srcPane = dstPane + 1;
1753         OnX2Y(srcPane, dstPane);
1754 }
1755
1756 /**
1757  * @brief Called when "Copy to right" item is updated
1758  */
1759 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
1760 {
1761         OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
1762 }
1763
1764 void CMergeEditView::OnCopyFromLeft()
1765 {
1766         int dstPane = m_nThisPane;
1767         int srcPane = dstPane - 1;
1768         if (srcPane < 0)
1769                 return;
1770         OnX2Y(srcPane, dstPane);
1771 }
1772
1773 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
1774 {
1775         int dstPane = m_nThisPane;
1776         int srcPane = dstPane - 1;
1777         if (srcPane < 0)
1778                 pCmdUI->Enable(false);
1779         else
1780                 OnUpdateX2Y(dstPane, pCmdUI);
1781 }
1782
1783 void CMergeEditView::OnCopyFromRight()
1784 {
1785         int dstPane = m_nThisPane;
1786         int srcPane = dstPane + 1;
1787         if (srcPane >= GetDocument()->m_nBuffers)
1788                 return;
1789         OnX2Y(srcPane, dstPane);
1790 }
1791
1792 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
1793 {
1794         int dstPane = m_nThisPane;
1795         int srcPane = dstPane + 1;
1796         if (srcPane >= GetDocument()->m_nBuffers)
1797                 pCmdUI->Enable(false);
1798         else
1799                 OnUpdateX2Y(dstPane, pCmdUI);
1800 }
1801
1802 /**
1803  * @brief Copy all diffs from right pane to left pane
1804  */
1805 void CMergeEditView::OnAllLeft()
1806 {
1807         // Check that left side is not readonly
1808         int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
1809         int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
1810         if (IsReadOnly(dstPane))
1811                 return;
1812         CWaitCursor waitstatus;
1813
1814         GetDocument()->CopyAllList(srcPane, dstPane);
1815 }
1816
1817 /**
1818  * @brief Called when "Copy all to left" item is updated
1819  */
1820 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
1821 {
1822         // Check that left side is not readonly
1823         int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
1824         if (!IsReadOnly(dstPane))
1825                 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
1826         else
1827                 pCmdUI->Enable(false);
1828 }
1829
1830 /**
1831  * @brief Copy all diffs from left pane to right pane
1832  */
1833 void CMergeEditView::OnAllRight()
1834 {
1835         // Check that right side is not readonly
1836         int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
1837         int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1838         if (IsReadOnly(dstPane))
1839                 return;
1840
1841         CWaitCursor waitstatus;
1842
1843         GetDocument()->CopyAllList(srcPane, dstPane);
1844 }
1845
1846 /**
1847  * @brief Called when "Copy all to right" item is updated
1848  */
1849 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
1850 {
1851         // Check that right side is not readonly
1852         int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1853         if (!IsReadOnly(dstPane))
1854                 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
1855         else
1856                 pCmdUI->Enable(false);
1857 }
1858
1859 /**
1860  * @brief Do Auto merge
1861  */
1862 void CMergeEditView::OnAutoMerge()
1863 {
1864         // Check current pane is not readonly
1865         if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || IsReadOnly(m_nThisPane))
1866                 return;
1867
1868         CWaitCursor waitstatus;
1869
1870         GetDocument()->DoAutoMerge(m_nThisPane);
1871 }
1872
1873 /**
1874  * @brief Called when "Auto Merge" item is updated
1875  */
1876 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
1877 {
1878         pCmdUI->Enable(GetDocument()->m_nBuffers == 3 && 
1879                 !GetDocument()->IsModified() && 
1880                 !GetDocument()->GetAutoMerged() && 
1881                 !IsReadOnly(m_nThisPane));
1882 }
1883
1884 /**
1885  * @brief Add synchronization point
1886  */
1887 void CMergeEditView::OnAddSyncPoint()
1888 {
1889         GetDocument()->AddSyncPoint();
1890 }
1891
1892 /**
1893  * @brief Clear synchronization points
1894  */
1895 void CMergeEditView::OnClearSyncPoints()
1896 {
1897         GetDocument()->ClearSyncPoints();
1898 }
1899
1900 /**
1901  * @brief Called when "Clear Synchronization Points" item is updated
1902  */
1903 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
1904 {
1905         pCmdUI->Enable(GetDocument()->HasSyncPoints());
1906 }
1907
1908 /**
1909  * @brief This function is called before other edit events.
1910  * @param [in] nAction Edit operation to do
1911  * @param [in] pszText Text to insert, delete etc
1912  * @sa CCrystalEditView::OnEditOperation()
1913  * @todo More edit-events for rescan delaying?
1914  */
1915 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
1916 {
1917         if (IsReadOnly(m_nThisPane))
1918         {
1919                 // We must not arrive here, and assert helps detect troubles
1920                 ASSERT(false);
1921                 return;
1922         }
1923
1924         CMergeDoc* pDoc = GetDocument();
1925         pDoc->SetEditedAfterRescan(m_nThisPane);
1926
1927         // simple hook for multiplex undo operations
1928         // deleted by jtuc 2003-06-28
1929         // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
1930         /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
1931         {
1932                 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
1933                 pDoc->undoTgt.push_back(this);
1934                 pDoc->curUndo = pDoc->undoTgt.end();
1935         }*/
1936
1937         // perform original function
1938         CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
1939
1940         // augment with additional operations
1941
1942         // Change header to inform about changed doc
1943         pDoc->UpdateHeaderPath(m_nThisPane);
1944
1945         // If automatic rescan enabled, rescan after edit events
1946         if (m_bAutomaticRescan)
1947         {
1948                 // keep document up to date     
1949                 // (Re)start timer to rescan only when user edits text
1950                 // If timer starting fails, rescan immediately
1951                 if (nAction == CE_ACTION_TYPING ||
1952                         nAction == CE_ACTION_REPLACE ||
1953                         nAction == CE_ACTION_BACKSPACE ||
1954                         nAction == CE_ACTION_INDENT ||
1955                         nAction == CE_ACTION_PASTE ||
1956                         nAction == CE_ACTION_DELSEL ||
1957                         nAction == CE_ACTION_DELETE ||
1958                         nAction == CE_ACTION_CUT)
1959                 {
1960                         if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
1961                                 pDoc->FlushAndRescan();
1962                 }
1963                 else
1964                         pDoc->FlushAndRescan();
1965         }
1966         else
1967         {
1968                 if (m_bWordWrap)
1969                 {
1970                         // Update other pane for sync line.
1971                         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
1972                         {
1973                                 if (nPane == m_nThisPane)
1974                                         continue;
1975                                 CCrystalEditView *pView = GetGroupView(nPane);
1976                                 if (pView != nullptr)
1977                                         pView->Invalidate();
1978                         }
1979                 }
1980         }
1981 }
1982
1983 /**
1984  * @brief Redo last action
1985  */
1986 void CMergeEditView::OnEditRedo()
1987 {
1988         CWaitCursor waitstatus;
1989         CMergeDoc* pDoc = GetDocument();
1990         CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
1991         if(tgt==this)
1992         {
1993                 if (IsReadOnly(m_nThisPane))
1994                         return;
1995
1996                 GetParentFrame()->SetActiveView(this, true);
1997                 if(CCrystalEditViewEx::DoEditRedo())
1998                 {
1999                         ++pDoc->curUndo;
2000                         pDoc->UpdateHeaderPath(m_nThisPane);
2001                         pDoc->FlushAndRescan();
2002                 }
2003         }
2004         else
2005         {
2006                 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2007         }
2008 }
2009
2010 /**
2011  * @brief Called when "Redo" item is updated
2012  */
2013 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2014 {
2015         CMergeDoc* pDoc = GetDocument();
2016         if (pDoc->curUndo!=pDoc->undoTgt.end())
2017         {
2018                 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2019                 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2020         }
2021         else
2022                 pCmdUI->Enable(false);
2023 }
2024
2025 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2026 {
2027         CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2028 }
2029
2030 /**
2031  * @brief Scrolls to current diff and/or selects diff text
2032  * @param [in] bScroll If true scroll diff to view
2033  * @param [in] bSelectText If true select diff text
2034  * @note If bScroll and bSelectText are false, this does nothing!
2035  * @todo This shouldn't be called when no diff is selected, so
2036  * somebody could try to ASSERT(nDiff > -1)...
2037  */
2038 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2039 {
2040         CMergeDoc *pd = GetDocument();
2041         const int nDiff = pd->GetCurrentDiff();
2042
2043         // Try to trap some errors
2044         if (nDiff >= pd->m_diffList.GetSize())
2045                 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2046                         nDiff, pd->m_diffList.GetSize());
2047
2048         if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2049         {
2050                 CPoint ptStart, ptEnd;
2051                 DIFFRANGE curDiff;
2052                 pd->m_diffList.GetDiff(nDiff, curDiff);
2053
2054                 ptStart.x = 0;
2055                 ptStart.y = curDiff.dbegin;
2056                 ptEnd.x = 0;
2057                 ptEnd.y = curDiff.dend;
2058
2059                 if (bScroll)
2060                 {
2061                         if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2062                         {
2063                                 // Difference is not visible, scroll it so that max amount of
2064                                 // scrolling is done while keeping the diff in screen. So if
2065                                 // scrolling is downwards, scroll the diff to as up in screen
2066                                 // as possible. This usually brings next diff to the screen
2067                                 // and we don't need to scroll into it.
2068                                 int nLine = GetSubLineIndex(ptStart.y);
2069                                 if (nLine > CONTEXT_LINES_ABOVE)
2070                                 {
2071                                         nLine -= CONTEXT_LINES_ABOVE;
2072                                 }
2073                                 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2074                                 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2075                                 {
2076                                         if (nPane != m_nThisPane)
2077                                                 GetGroupView(nPane)->ScrollToSubLine(nLine);
2078                                 }
2079                         }
2080                         GetGroupView(m_nThisPane)->SetCursorPos(ptStart);
2081                         GetGroupView(m_nThisPane)->SetAnchor(ptStart);
2082                         GetGroupView(m_nThisPane)->SetSelection(ptStart, ptStart);
2083                         for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2084                         {
2085                                 if (nPane != m_nThisPane)
2086                                 {
2087                                         GetGroupView(nPane)->SetCursorPos(ptStart);
2088                                         GetGroupView(nPane)->SetAnchor(ptStart);
2089                                         GetGroupView(nPane)->SetSelection(ptStart, ptStart);
2090                                 }
2091                         }
2092                 }
2093
2094                 if (bSelectText)
2095                 {
2096                         ptEnd.x = GetLineLength(ptEnd.y);
2097                         SetSelection(ptStart, ptEnd);
2098                         UpdateCaret();
2099                 }
2100                 else
2101                         Invalidate();
2102         }
2103 }
2104
2105
2106 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2107 {
2108         // Maybe we want theApp::OnIdle to proceed before processing a timer message
2109         // ...but for this the queue must be empty
2110         // The timer message is a low priority message but the queue is maybe not yet empty
2111         // So we set a flag, wait for OnIdle to proceed, then come back here...
2112         // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2113         // not with SetTimer so there is no delay)
2114
2115         // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2116
2117         // IDLE_TIMER is the false timer used to come back here after OnIdle
2118         // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2119         // (one normal timer = one flag = one command)
2120
2121         if (nIDEvent == IDT_RESCAN)
2122         {
2123                 KillTimer(IDT_RESCAN);
2124                 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2125                 // notify the app to come back after OnIdle
2126                 theApp.SetNeedIdleTimer();
2127         }
2128
2129         if (nIDEvent == IDLE_TIMER)
2130         {
2131                 // not a real timer, just come back after OnIdle
2132                 // look to flags to know what to do
2133                 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2134                         GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2135                 fTimerWaitingForIdle = 0;
2136         }
2137
2138         CCrystalEditViewEx::OnTimer(nIDEvent);
2139 }
2140
2141 /**
2142  * @brief Returns if buffer is read-only
2143  * @note This has no any relation to file being read-only!
2144  */
2145 bool CMergeEditView::IsReadOnly(int pane) const
2146 {
2147         return GetDocument()->m_ptBuf[pane]->GetReadOnly() != false;
2148 }
2149
2150 /**
2151  * @brief Called when "Save left (as...)" item is updated
2152  */
2153 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2154 {
2155         CMergeDoc *pd = GetDocument();
2156         pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2157 }
2158
2159 /**
2160  * @brief Called when "Save middle (as...)" item is updated
2161  */
2162 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2163 {
2164         CMergeDoc *pd = GetDocument();
2165         pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2166 }
2167
2168 /**
2169  * @brief Called when "Save right (as...)" item is updated
2170  */
2171 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2172 {
2173         CMergeDoc *pd = GetDocument();
2174         pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2175 }
2176
2177 /**
2178  * @brief Refresh display using text-buffers
2179  * @note This DOES NOT reload files!
2180  */
2181 void CMergeEditView::OnRefresh()
2182 {
2183         CMergeDoc *pd = GetDocument();
2184         ASSERT(pd != nullptr);
2185         pd->FlushAndRescan(true);
2186 }
2187
2188 /**
2189  * @brief Enable/Disable automatic rescanning
2190  */
2191 bool CMergeEditView::EnableRescan(bool bEnable)
2192 {
2193         bool bOldValue = m_bAutomaticRescan;
2194         m_bAutomaticRescan = bEnable;
2195         return bOldValue;
2196 }
2197
2198 /**
2199  * @brief Handle some keys when in merging mode
2200  */
2201 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2202 {
2203         bool bHandled = false;
2204
2205         // Allow default text selection when SHIFT pressed
2206         if (::GetAsyncKeyState(VK_SHIFT))
2207                 return false;
2208
2209         // Allow default editor functions when CTRL pressed
2210         if (::GetAsyncKeyState(VK_CONTROL))
2211                 return false;
2212
2213         // If we are in merging mode (merge with cursor keys)
2214         // handle some keys here
2215         switch (pMsg->wParam)
2216         {
2217         case VK_LEFT:
2218                 OnR2l();
2219                 bHandled = true;
2220                 break;
2221
2222         case VK_RIGHT:
2223                 OnL2r();
2224                 bHandled = true;
2225                 break;
2226
2227         case VK_UP:
2228                 OnPrevdiff();
2229                 bHandled = true;
2230                 break;
2231
2232         case VK_DOWN:
2233                 OnNextdiff();
2234                 bHandled = true;
2235                 break;
2236         }
2237
2238         return bHandled;
2239 }
2240
2241 /**
2242  * @brief Called before messages are translated.
2243  *
2244  * Checks if ESC key was pressed, saves and closes doc.
2245  * Also if in merge mode traps cursor keys.
2246  */
2247 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2248 {
2249         if (pMsg->message == WM_KEYDOWN)
2250         {
2251                 // If we are in merging mode (merge with cursor keys)
2252                 // handle some keys here
2253                 if (theApp.GetMergingMode())
2254                 {
2255                         bool bHandled = MergeModeKeyDown(pMsg);
2256                         if (bHandled)
2257                                 return false;
2258                 }
2259
2260                 // Close window if user has allowed it from options
2261                 if (pMsg->wParam == VK_ESCAPE)
2262                 {
2263                         bool bCloseWithEsc = GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC);
2264                         if (bCloseWithEsc)
2265                                 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2266                         return false;
2267                 }
2268         }
2269
2270         return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2271 }
2272
2273 /**
2274  * @brief Called when "Save" item is updated
2275  */
2276 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2277 {
2278         CMergeDoc *pd = GetDocument();
2279
2280         bool bModified = false;
2281         for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2282         {
2283                 if (pd->m_ptBuf[nPane]->IsModified())
2284                         bModified = true;
2285         }
2286         pCmdUI->Enable(bModified);
2287 }
2288
2289 /**
2290  * @brief Enable/disable left buffer read-only
2291  */
2292 void CMergeEditView::OnLeftReadOnly()
2293 {
2294         CMergeDoc *pd = GetDocument();
2295         bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2296         pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2297 }
2298
2299 /**
2300  * @brief Called when "Left read-only" item is updated
2301  */
2302 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2303 {
2304         CMergeDoc *pd = GetDocument();
2305         bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2306         pCmdUI->Enable(true);
2307         pCmdUI->SetCheck(bReadOnly);
2308 }
2309
2310 /**
2311  * @brief Enable/disable middle buffer read-only
2312  */
2313 void CMergeEditView::OnMiddleReadOnly()
2314 {
2315         CMergeDoc *pd = GetDocument();
2316         if (pd->m_nBuffers == 3)
2317         {
2318                 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2319                 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2320         }
2321 }
2322
2323 /**
2324  * @brief Called when "Middle read-only" item is updated
2325  */
2326 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2327 {
2328         CMergeDoc *pd = GetDocument();
2329         if (pd->m_nBuffers < 3)
2330         {
2331                 pCmdUI->Enable(false);
2332         }
2333         else
2334         {
2335                 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2336                 pCmdUI->Enable(true);
2337                 pCmdUI->SetCheck(bReadOnly);
2338         }
2339 }
2340
2341 /**
2342  * @brief Enable/disable right buffer read-only
2343  */
2344 void CMergeEditView::OnRightReadOnly()
2345 {
2346         CMergeDoc *pd = GetDocument();
2347         bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2348         pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2349 }
2350
2351 /**
2352  * @brief Called when "Left read-only" item is updated
2353  */
2354 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2355 {
2356         CMergeDoc *pd = GetDocument();
2357         bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2358         pCmdUI->Enable(true);
2359         pCmdUI->SetCheck(bReadOnly);
2360 }
2361
2362 /// Store interface we use to display status line info
2363 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2364 {
2365         ASSERT(m_piMergeEditStatus == nullptr);
2366         m_piMergeEditStatus = piMergeEditStatus;
2367 }
2368
2369 /**
2370  * @brief Update status bar contents.
2371  */
2372 void CMergeEditView::UpdateStatusbar()
2373 {
2374         OnUpdateCaret();
2375 }
2376
2377 /**
2378  * @brief Update statusbar info, Override from CCrystalTextView
2379  * @note we tab-expand column, but we don't tab-expand char count,
2380  * since we want to show how many chars there are and tab is just one
2381  * character although it expands to several spaces.
2382  */
2383 void CMergeEditView::OnUpdateCaret()
2384 {
2385         if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2386                 return;
2387
2388         CPoint cursorPos = GetCursorPos();
2389         int nScreenLine = cursorPos.y;
2390         const int nRealLine = ComputeRealLine(nScreenLine);
2391         CString sLine;
2392         int chars = -1;
2393         CString sEol;
2394         int column = -1;
2395         int columns = -1;
2396         int curChar = -1;
2397         DWORD dwLineFlags = 0;
2398
2399         dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2400         // Is this a ghost line ?
2401         if (dwLineFlags & LF_GHOST)
2402         {
2403                 // Ghost lines display eg "Line 12-13"
2404                 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2405                 sEol = _T("hidden");
2406         }
2407         else
2408         {
2409                 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2410                 sLine.Format(_T("%d"), nRealLine+1);
2411                 curChar = cursorPos.x + 1;
2412                 chars = GetLineLength(nScreenLine);
2413                 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2414                 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2415                 chars++;
2416                 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2417                                 GetDocument()->IsMixedEOL(m_nThisPane))
2418                 {
2419                         sEol = GetTextBufferEol(nScreenLine);
2420                 }
2421                 else
2422                         sEol = _T("hidden");
2423         }
2424         m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2425                 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2426
2427         // Is cursor inside difference?
2428         if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2429                 m_bCurrentLineIsDiff = true;
2430         else
2431                 m_bCurrentLineIsDiff = false;
2432
2433         UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2434 }
2435 /**
2436  * @brief Select linedifference in the current line.
2437  *
2438  * Select line difference in current line. Selection type
2439  * is choosed by highlight type.
2440  */
2441 template<bool reversed>
2442 void CMergeEditView::OnSelectLineDiff()
2443 {
2444         // Pass this to the document, to compare this file to other
2445         GetDocument()->Showlinediff(this, reversed);
2446 }
2447
2448 /// Enable select difference menuitem if current line is inside difference.
2449 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2450 {
2451         int line = GetCursorPos().y;
2452         bool enable = ((GetLineFlags(line) & (LF_DIFF | LF_GHOST)) != 0);
2453         if (GetDocument()->IsEditedAfterRescan(m_nThisPane))
2454                 enable = false;
2455         pCmdUI->Enable(enable);
2456 }
2457
2458 /**
2459  * @brief Enable/disable Replace-menuitem
2460  */
2461 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2462 {
2463         CMergeDoc *pd = GetDocument();
2464         bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2465
2466         pCmdUI->Enable(!bReadOnly);
2467 }
2468
2469 /**
2470  * @brief Update readonly statusbaritem
2471  */
2472 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2473 {
2474         bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2475         pCmdUI->Enable(bRO);
2476 }
2477
2478 /**
2479  * @brief Create the dynamic submenu for scripts
2480  */
2481 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2482 {
2483         // get scripts list
2484         std::vector<String> functionNamesList;
2485         FileTransform::GetFreeFunctionsInScripts(functionNamesList, L"EDITOR_SCRIPT");
2486
2487         // empty the menu
2488         size_t i = GetMenuItemCount(hMenu);
2489         while (i --)
2490                 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2491
2492         if (functionNamesList.size() == 0)
2493         {
2494                 // no script : create a <empty> entry
2495                 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2496         }
2497         else
2498         {
2499                 // or fill in the submenu with the scripts names
2500                 int ID = ID_SCRIPT_FIRST;       // first ID in menu
2501                 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2502                         AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2503
2504                 functionNamesList.clear();
2505         }
2506
2507         if (!plugin::IsWindowsScriptThere())
2508                 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2509
2510         return hMenu;
2511 }
2512
2513 /**
2514  * @brief Create the dynamic submenu for prediffers
2515  *
2516  * @note The plugins are grouped in (suggested) and (not suggested)
2517  *       The IDs follow the order of GetAvailableScripts
2518  *       For example :
2519  *                              suggested 0         ID_1ST + 0 
2520  *                              suggested 1         ID_1ST + 2 
2521  *                              suggested 2         ID_1ST + 5 
2522  *                              not suggested 0     ID_1ST + 1 
2523  *                              not suggested 1     ID_1ST + 3 
2524  *                              not suggested 2     ID_1ST + 4 
2525  */
2526 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2527 {
2528         // empty the menu
2529         int i = GetMenuItemCount(hMenu);
2530         while (i --)
2531                 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2532
2533         CMergeDoc *pd = GetDocument();
2534         ASSERT(pd != nullptr);
2535
2536         // title
2537         AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2538
2539         // get the scriptlet files
2540         PluginArray * piScriptArray = 
2541                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2542         PluginArray * piScriptArray2 = 
2543                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2544
2545         // build the menu : first part, suggested plugins
2546         // title
2547         AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2548         AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2549
2550         int ID = ID_PREDIFFERS_FIRST;   // first ID in menu
2551         size_t iScript;
2552         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2553         {
2554                 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2555                 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2556                         continue;
2557
2558                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2559         }
2560         for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2561         {
2562                 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2563                 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2564                         continue;
2565
2566                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2567         }
2568
2569         // build the menu : second part, others plugins
2570         // title
2571         AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2572         AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2573
2574         ID = ID_PREDIFFERS_FIRST;       // first ID in menu
2575         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2576         {
2577                 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2578                 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2579                         continue;
2580
2581                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2582         }
2583         for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2584         {
2585                 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2586                 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2587                         continue;
2588
2589                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2590         }
2591
2592         // compute the m_CurrentPredifferID (to set the radio button)
2593         PrediffingInfo prediffer;
2594         pd->GetPrediffer(&prediffer);
2595
2596         if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2597                 m_CurrentPredifferID = 0;
2598         else if (prediffer.m_PluginName.empty())
2599                 m_CurrentPredifferID = ID_NO_PREDIFFER;
2600         else
2601         {
2602                 ID = ID_PREDIFFERS_FIRST;       // first ID in menu
2603                 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2604                 {
2605                         const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2606                         if (prediffer.m_PluginName == plugin->m_name)
2607                                 m_CurrentPredifferID = ID;
2608
2609                 }
2610                 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2611                 {
2612                         const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2613                         if (prediffer.m_PluginName == plugin->m_name)
2614                                 m_CurrentPredifferID = ID;
2615                 }
2616         }
2617
2618         return hMenu;
2619 }
2620
2621 /**
2622  * @brief Offer a context menu built with scriptlet/ActiveX functions
2623  */
2624 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2625 {
2626         // Create the menu and populate it with the available functions
2627         BCMenu menu;
2628         VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2629
2630         // Remove copying item copying from active side
2631         if (m_nThisPane == 0) // left?
2632         {
2633                 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2634                 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2635         }
2636         if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2637         {
2638                 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2639                 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2640         }
2641
2642         VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
2643         theApp.TranslateMenu(menu.m_hMenu);
2644
2645         BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2646         ASSERT(pSub != nullptr);
2647
2648         // Context menu opened using keyboard has no coordinates
2649         if (point.x == -1 && point.y == -1)
2650         {
2651                 CRect rect;
2652                 GetClientRect(rect);
2653                 ClientToScreen(rect);
2654
2655                 point = rect.TopLeft();
2656                 point.Offset(5, 5);
2657         }
2658
2659         pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2660                 point.x, point.y, AfxGetMainWnd());
2661
2662 }
2663
2664 /**
2665  * @brief Update EOL mode in status bar
2666  */
2667 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2668 {
2669         GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2670 }
2671
2672 /**
2673  * @brief Change EOL mode and unify all the lines EOL to this new mode
2674  */
2675 void CMergeEditView::OnConvertEolTo(UINT nID )
2676 {
2677         CRLFSTYLE nStyle = CRLF_STYLE_AUTOMATIC;;
2678         switch (nID)
2679         {
2680                 case ID_EOL_TO_DOS:
2681                         nStyle = CRLF_STYLE_DOS;
2682                         break;
2683                 case ID_EOL_TO_UNIX:
2684                         nStyle = CRLF_STYLE_UNIX;
2685                         break;
2686                 case ID_EOL_TO_MAC:
2687                         nStyle = CRLF_STYLE_MAC;
2688                         break;
2689                 default:
2690                         // Catch errors
2691                         _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2692                         break;
2693         }
2694         m_pTextBuffer->SetCRLFMode(nStyle);
2695
2696         // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2697         if (m_pTextBuffer->applyEOLMode())
2698         {
2699                 CMergeDoc *pd = GetDocument();
2700                 ASSERT(pd != nullptr);
2701                 pd->UpdateHeaderPath(m_nThisPane);
2702                 pd->FlushAndRescan(true);
2703         }
2704 }
2705
2706 /**
2707  * @brief allow convert to entries in file submenu
2708  */
2709 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2710 {
2711         int nStyle = CRLF_STYLE_AUTOMATIC;
2712         switch (pCmdUI->m_nID)
2713         {
2714                 case ID_EOL_TO_DOS:
2715                         nStyle = CRLF_STYLE_DOS;
2716                         break;
2717                 case ID_EOL_TO_UNIX:
2718                         nStyle = CRLF_STYLE_UNIX;
2719                         break;
2720                 case ID_EOL_TO_MAC:
2721                         nStyle = CRLF_STYLE_MAC;
2722                         break;
2723                 default:
2724                         // Catch errors
2725                         _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2726                         break;
2727         }
2728
2729         if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2730                 GetDocument()->IsMixedEOL(m_nThisPane) ||
2731                 nStyle != m_pTextBuffer->GetCRLFMode())
2732         {
2733                 pCmdUI->SetRadio(false);
2734
2735                 // Don't allow selecting other EOL style for protected pane
2736                 if (IsReadOnly(m_nThisPane))
2737                         pCmdUI->Enable(false);
2738         }
2739         else
2740                 pCmdUI->SetRadio(true);
2741 }
2742
2743 /**
2744  * @brief Copy diff from left to right and advance to next diff
2745  */
2746 void CMergeEditView::OnL2RNext()
2747 {
2748         OnL2r();
2749         if (IsCursorInDiff()) // for 3-way file compare
2750                 OnNextdiff();
2751         OnNextdiff();
2752 }
2753
2754 /**
2755  * @brief Update "Copy right and advance" UI item
2756  */
2757 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
2758 {
2759         OnUpdateL2r(pCmdUI);
2760 }
2761
2762 /**
2763  * @brief Copy diff from right to left and advance to next diff
2764  */
2765 void CMergeEditView::OnR2LNext()
2766 {
2767         OnR2l();
2768         if (IsCursorInDiff()) // for 3-way file compare
2769                 OnNextdiff();
2770         OnNextdiff();
2771 }
2772
2773 /**
2774  * @brief Update "Copy left and advance" UI item
2775  */
2776 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
2777 {
2778         OnUpdateR2l(pCmdUI);
2779 }
2780
2781 /**
2782  * @brief Change active pane in MergeView.
2783  * Changes active pane and makes sure cursor position is kept in
2784  * screen. Currently we put cursor in same line than in original
2785  * active pane but we could be smarter too? Maybe update cursor
2786  * only when it is not visible in new pane?
2787  */
2788 void CMergeEditView::OnChangePane()
2789 {
2790         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
2791         CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
2792         CMergeDoc *pDoc = GetDocument();
2793         bool bFound = false;
2794         CMergeEditView *pNextActiveView = nullptr;
2795         std::vector<CMergeEditView *> list = pDoc->GetViewList();
2796         list.insert(list.end(), list.begin(), list.end());
2797         for (auto& pView : list)
2798         {
2799                 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
2800                 {
2801                         pNextActiveView = pView;
2802                         break;
2803                 }
2804                 if (pWnd == pView)
2805                         bFound = true;
2806         }
2807         GetParentFrame()->SetActiveView(pNextActiveView);
2808         CPoint ptCursor = pWnd->GetCursorPos();
2809         ptCursor.x = 0;
2810         if (ptCursor.y >= pNextActiveView->GetLineCount())
2811                 ptCursor.y = pNextActiveView->GetLineCount() - 1;
2812         pNextActiveView->SetCursorPos(ptCursor);
2813         pNextActiveView->SetAnchor(ptCursor);
2814         pNextActiveView->SetSelection(ptCursor, ptCursor);
2815 }
2816
2817 /**
2818  * @brief Show "Go To" dialog and scroll views to line or diff.
2819  *
2820  * Before dialog is opened, current line and file is determined
2821  * and selected.
2822  * @note Conversions needed between apparent and real lines
2823  */
2824 void CMergeEditView::OnWMGoto()
2825 {
2826         WMGotoDlg dlg;
2827         CMergeDoc *pDoc = GetDocument();
2828         CPoint pos = GetCursorPos();
2829         int nRealLine = 0;
2830         int nLastLine = 0;
2831
2832         nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
2833         int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
2834         nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
2835
2836         // Set active file and current line selected in dialog
2837         dlg.m_strParam = strutils::to_str(nRealLine + 1);
2838         dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
2839         dlg.m_nGotoWhat = 0;
2840
2841         if (dlg.DoModal() == IDOK)
2842         {
2843                 CMergeDoc * pDoc1 = GetDocument();
2844                 CMergeEditView * pCurrentView = nullptr;
2845
2846                 // Get views
2847                 pCurrentView = GetGroupView(m_nThisPane);
2848
2849                 int num = 0;
2850                 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
2851
2852                 if (dlg.m_nGotoWhat == 0)
2853                 {
2854                         int nRealLine1 = num;
2855                         if (nRealLine1 < 0)
2856                                 nRealLine1 = 0;
2857                         if (nRealLine1 > nLastLine)
2858                                 nRealLine1 = nLastLine;
2859
2860                         GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
2861                 }
2862                 else
2863                 {
2864                         int diff = num;
2865                         if (diff < 0)
2866                                 diff = 0;
2867                         if (diff >= pDoc1->m_diffList.GetSize())
2868                                 diff = pDoc1->m_diffList.GetSize();
2869
2870                         pCurrentView->SelectDiff(diff, true, false);
2871                 }
2872         }
2873 }
2874
2875 /**
2876  * @brief Reload options.
2877  */
2878 void CMergeEditView::RefreshOptions()
2879
2880         m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
2881
2882         if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
2883                 SetInsertTabs(true);
2884         else
2885                 SetInsertTabs(false);
2886
2887         SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
2888
2889         if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
2890                 SetTextType(CCrystalTextView::SRC_PLAIN);
2891
2892         SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
2893         SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
2894
2895         SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
2896         SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
2897                 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2898                 GetDocument()->IsMixedEOL(m_nThisPane));
2899
2900         Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
2901 }
2902
2903 void CMergeEditView::OnScripts(UINT nID )
2904 {
2905         // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
2906         String text = GetSelectedText();
2907
2908         // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
2909         bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
2910         if (bChanged)
2911                 // now replace the text
2912                 ReplaceSelection(text.c_str(), text.length(), 0);
2913 }
2914
2915 /**
2916  * @brief Called when an editor script item is updated
2917  */
2918 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
2919 {
2920         // append the scripts submenu
2921         HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
2922         if (scriptsSubmenu != nullptr)
2923                 createScriptsSubmenu(scriptsSubmenu);
2924
2925         pCmdUI->Enable(true);
2926 }
2927
2928 /**
2929  * @brief Called when an editor script item is updated
2930  */
2931 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
2932 {
2933         pCmdUI->Enable(true);
2934
2935         CMergeDoc *pd = GetDocument();
2936         ASSERT(pd != nullptr);
2937         PrediffingInfo prediffer;
2938         pd->GetPrediffer(&prediffer);
2939
2940         if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2941         {
2942                 pCmdUI->SetRadio(false);
2943                 return;
2944         }
2945
2946         // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
2947         if (prediffer.m_PluginName.empty())
2948                 m_CurrentPredifferID = ID_NO_PREDIFFER;
2949
2950         pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
2951 }
2952
2953 /**
2954  * @brief Update "Prediffer" menuitem
2955  */
2956 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
2957 {
2958         // recreate the sub menu (to fill the "selected prediffers")
2959         GetMainFrame()->UpdatePrediffersMenu();
2960         pCmdUI->Enable();
2961 }
2962
2963 void CMergeEditView::OnNoPrediffer()
2964 {
2965         OnPrediffer(ID_NO_PREDIFFER);
2966 }
2967 /**
2968  * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
2969  */
2970 void CMergeEditView::OnPrediffer(UINT nID )
2971 {
2972         CMergeDoc *pd = GetDocument();
2973         ASSERT(pd != nullptr);
2974
2975         SetPredifferByMenu(nID);
2976         pd->FlushAndRescan(true);
2977 }
2978
2979 /**
2980  * @brief Handler for all prediffer choices.
2981  * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
2982  * ID_NO_PREDIFFER, & specific prediffers.
2983  */
2984 void CMergeEditView::SetPredifferByMenu(UINT nID )
2985 {
2986         CMergeDoc *pd = GetDocument();
2987         ASSERT(pd != nullptr);
2988
2989         if (nID == ID_NO_PREDIFFER)
2990         {
2991                 m_CurrentPredifferID = nID;
2992                 // All flags are set correctly during the construction
2993                 PrediffingInfo *infoPrediffer = new PrediffingInfo;
2994                 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
2995                 infoPrediffer->m_PluginName.clear();
2996                 pd->SetPrediffer(infoPrediffer);
2997                 pd->FlushAndRescan(true);
2998                 return;
2999         }
3000
3001         // get the scriptlet files
3002         PluginArray * piScriptArray = 
3003                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3004         PluginArray * piScriptArray2 = 
3005                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3006
3007         // build a PrediffingInfo structure fom the ID
3008         PrediffingInfo prediffer;
3009         prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
3010
3011         size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3012         if (pluginNumber < piScriptArray->size())
3013         {
3014                 prediffer.m_bWithFile = true;
3015                 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3016                 prediffer.m_PluginName = plugin->m_name;
3017         }
3018         else
3019         {
3020                 pluginNumber -= piScriptArray->size();
3021                 if (pluginNumber >= piScriptArray2->size())
3022                         return;
3023                 prediffer.m_bWithFile = false;
3024                 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3025                 prediffer.m_PluginName = plugin->m_name;
3026         }
3027
3028         // update data for the radio button
3029         m_CurrentPredifferID = nID;
3030
3031         // update the prediffer and rescan
3032         pd->SetPrediffer(&prediffer);
3033 }
3034
3035 /**
3036  * @brief Look through available prediffers, and return ID of requested one, if found
3037  */
3038 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3039 {
3040         size_t i;
3041         int ID = ID_PREDIFFERS_FIRST;
3042
3043         // Search file prediffers
3044         PluginArray * piScriptArray = 
3045                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3046         for (i=0; i<piScriptArray->size(); ++i, ++ID)
3047         {
3048                 const PluginInfoPtr & plugin = piScriptArray->at(i);
3049                 if (plugin->m_name == prediffer)
3050                         return ID;
3051         }
3052
3053         // Search buffer prediffers
3054         PluginArray * piScriptArray2 = 
3055                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3056         for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3057         {
3058                 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3059                 if (plugin->m_name == prediffer)
3060                         return ID;
3061         }
3062         return -1;
3063 }
3064
3065
3066 /**
3067  * @brief Look through available prediffers, and return ID of requested one, if found
3068  */
3069 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3070 {
3071         int id = FindPrediffer(prediffer);
3072         if (id<0) return false;
3073         SetPredifferByMenu(id);
3074         return true;
3075 }
3076
3077 /** 
3078  * @brief Goto given line.
3079  * @param [in] nLine Destination linenumber
3080  * @param [in] bRealLine if true linenumber is real line, otherwise
3081  * it is apparent line (including deleted lines)
3082  * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3083  */
3084 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3085 {
3086         CMergeDoc *pDoc = GetDocument();
3087         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3088         CMergeEditView *pCurrentView = nullptr;
3089         if (pSplitterWnd != nullptr)
3090                 pCurrentView = static_cast<CMergeEditView*>
3091                         (pSplitterWnd->GetActivePane());
3092
3093         int nRealLine = nLine;
3094         int nApparentLine = nLine;
3095
3096         // Compute apparent (shown linenumber) line
3097         if (bRealLine)
3098         {
3099                 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3100                         nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3101
3102                 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3103         }
3104         CPoint ptPos;
3105         ptPos.x = 0;
3106         ptPos.y = nApparentLine;
3107
3108         // Scroll line to center of view
3109         int nScrollLine = GetSubLineIndex(nApparentLine);
3110         nScrollLine -= GetScreenLines() / 2;
3111         if (nScrollLine < 0)
3112                 nScrollLine = 0;
3113
3114         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3115         {
3116                 CMergeEditView *pView = GetGroupView(nPane);
3117                 pView->ScrollToSubLine(nScrollLine);
3118                 if (ptPos.y < pView->GetLineCount())
3119                 {
3120                         pView->SetCursorPos(ptPos);
3121                         pView->SetAnchor(ptPos);
3122                 }
3123                 else
3124                 {
3125                         CPoint ptPos1(0, pView->GetLineCount() - 1);
3126                         pView->SetCursorPos(ptPos1);
3127                         pView->SetAnchor(ptPos1);
3128                 }
3129         }
3130
3131         // If goto target is another view - activate another view.
3132         // This is done for user convenience as user probably wants to
3133         // work with goto target file.
3134         if (GetGroupView(pane) != pCurrentView)
3135         {
3136                 if (pSplitterWnd != nullptr)
3137                 {
3138                         if (pSplitterWnd->GetColumnCount() > 1)
3139                                 pSplitterWnd->SetActivePane(0, pane);
3140                         else
3141                                 pSplitterWnd->SetActivePane(pane, 0);
3142                 }
3143         }
3144 }
3145
3146 /**
3147  * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3148  * a scroll bar.
3149  */
3150 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3151 {
3152         if (pScrollBar == nullptr)
3153         {
3154                 // Scroll did not come frome a scroll bar
3155                 // Find the appropriate scroll bar
3156                 // and send the message to the splitter window instead
3157                 // The event should eventually come back here but with a valid scrollbar
3158                 // Along the way it will be propagated to other windows that need it
3159                 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3160                 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3161                 pSplitterWnd->SendMessage(WM_HSCROLL,
3162                         MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3163                 return;
3164         }
3165         CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3166 }
3167
3168 /**
3169  * @brief When view is scrolled using scrollbars update location pane.
3170  */
3171 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3172 {
3173         if (pScrollBar == nullptr)
3174         {
3175                 // Scroll did not come frome a scroll bar
3176                 // Find the appropriate scroll bar
3177                 // and send the message to the splitter window instead
3178                 // The event should eventually come back here but with a valid scrollbar
3179                 // Along the way it will be propagated to other windows that need it
3180                 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3181                 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3182                 pSplitterWnd->SendMessage(WM_VSCROLL,
3183                         MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3184                 return;
3185         }
3186         CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3187
3188         if (nSBCode == SB_ENDSCROLL)
3189                 return;
3190
3191         // Note we cannot use nPos because of its 16-bit nature
3192         SCROLLINFO si = {0};
3193         si.cbSize = sizeof (si);
3194         si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3195         VERIFY (GetScrollInfo (SB_VERT, &si));
3196
3197         // Get the current position of scroll   box.
3198         int nCurPos =   si.nPos;
3199         
3200         UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3201 }
3202
3203 /**
3204  * @brief Copy selected lines adding linenumbers.
3205  */
3206 void CMergeEditView::OnEditCopyLineNumbers()
3207 {
3208         CPoint ptStart;
3209         CPoint ptEnd;
3210         CString strText;
3211         CString strLine;
3212         CString strNumLine;
3213
3214         CMergeDoc *pDoc = GetDocument();
3215         GetSelection(ptStart, ptEnd);
3216
3217         // Get last selected line (having widest linenumber)
3218         int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3219         size_t nNumWidth = strutils::to_str(line + 1).length();
3220         
3221         for (int i = ptStart.y; i <= ptEnd.y; i++)
3222         {
3223                 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3224                         continue;
3225
3226                 // We need to convert to real linenumbers
3227                 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3228
3229                 // Insert spaces to align different width linenumbers (99, 100)
3230                 strLine = GetLineText(i);
3231                 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3232                 
3233                 strText += sSpaces;
3234                 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3235                 strText += strNumLine;
3236         }
3237         PutToClipboard(strText, strText.GetLength(), m_bColumnSelection);
3238 }
3239
3240 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3241 {
3242         CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3243 }
3244
3245 /**
3246  * @brief Open active file with associated application.
3247  *
3248  * First tries to open file using shell 'Edit' action, since that
3249  * action open scripts etc. to editor instead of running them. If
3250  * edit-action is not registered, 'Open' action is used.
3251  */
3252 void CMergeEditView::OnOpenFile()
3253 {
3254         CMergeDoc * pDoc = GetDocument();
3255         ASSERT(pDoc != nullptr);
3256
3257         String sFileName = pDoc->m_filePaths[m_nThisPane];
3258         if (sFileName.empty())
3259                 return;
3260         HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3261                         0, 0, SW_SHOWNORMAL);
3262         if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3263                 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3264                          0, 0, SW_SHOWNORMAL);
3265         if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3266                 OnOpenFileWith();
3267 }
3268
3269 /**
3270  * @brief Open active file with app selection dialog
3271  */
3272 void CMergeEditView::OnOpenFileWith()
3273 {
3274         CMergeDoc * pDoc = GetDocument();
3275         ASSERT(pDoc != nullptr);
3276
3277         String sFileName = pDoc->m_filePaths[m_nThisPane];
3278         if (sFileName.empty())
3279                 return;
3280
3281         CString sysdir;
3282         if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3283                 return;
3284         sysdir.ReleaseBuffer();
3285         CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3286         ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3287                         sysdir, SW_SHOWNORMAL);
3288 }
3289
3290 /**
3291  * @brief Open active file with external editor
3292  */
3293 void CMergeEditView::OnOpenFileWithEditor()
3294 {
3295         CMergeDoc * pDoc = GetDocument();
3296         ASSERT(pDoc != nullptr);
3297
3298         String sFileName = pDoc->m_filePaths[m_nThisPane];
3299         if (sFileName.empty())
3300                 return;
3301
3302         int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3303         theApp.OpenFileToExternalEditor(sFileName.c_str(), nRealLine);
3304 }
3305
3306 /**
3307  * @brief Force repaint of the location pane.
3308  */
3309 void CMergeEditView::RepaintLocationPane()
3310 {
3311         // Must force recalculation due to caching of data in location pane.
3312         CLocationView *pLocationView = GetDocument()->GetLocationView();
3313         if (pLocationView != nullptr)
3314                 pLocationView->ForceRecalculate();
3315 }
3316
3317 /**
3318  * @brief Enables/disables linediff (different color for diffs)
3319  */
3320 void CMergeEditView::OnViewLineDiffs()
3321 {
3322         bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3323         GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3324
3325         // Call CMergeDoc RefreshOptions() to refresh *both* views
3326         CMergeDoc *pDoc = GetDocument();
3327         pDoc->RefreshOptions();
3328         pDoc->FlushAndRescan(true);
3329 }
3330
3331 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3332 {
3333         pCmdUI->Enable(true);
3334         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3335 }
3336
3337 /**
3338  * @brief Enables/disables line number
3339  */
3340 void CMergeEditView::OnViewLineNumbers()
3341 {
3342         GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3343
3344         // Call CMergeDoc RefreshOptions() to refresh *both* views
3345         CMergeDoc *pDoc = GetDocument();
3346         pDoc->RefreshOptions();
3347 }
3348
3349 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3350 {
3351         pCmdUI->Enable(true);
3352         pCmdUI->SetCheck(GetViewLineNumbers());
3353 }
3354
3355 /**
3356  * @brief Enables/disables word wrap
3357  */
3358 void CMergeEditView::OnViewWordWrap()
3359 {
3360         GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3361
3362         // Call CMergeDoc RefreshOptions() to refresh *both* views
3363         CMergeDoc *pDoc = GetDocument();
3364         pDoc->RefreshOptions();
3365         pDoc->UpdateAllViews(this);
3366
3367         UpdateCaret();
3368 }
3369
3370 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3371 {
3372         pCmdUI->Enable(true);
3373         pCmdUI->SetCheck(m_bWordWrap);
3374 }
3375
3376 void CMergeEditView::OnViewWhitespace() 
3377 {
3378         GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3379
3380         // Call CMergeDoc RefreshOptions() to refresh *both* views
3381         CMergeDoc *pDoc = GetDocument();
3382         pDoc->RefreshOptions();
3383 }
3384
3385 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI) 
3386 {
3387         pCmdUI->SetCheck(GetViewTabs());
3388 }
3389
3390 void CMergeEditView::OnSize(UINT nType, int cx, int cy) 
3391 {
3392         if (!IsInitialized())
3393                 return;
3394
3395         CMergeDoc * pDoc = GetDocument();
3396         if (m_nThisPane < pDoc->m_nBuffers - 1)
3397         {
3398                 // To calculate subline index correctly
3399                 // we have to invalidate line cache in all pane before calling the function related the subline.
3400                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3401                 {
3402                         CMergeEditView *pView = GetGroupView(nPane);
3403                         if (pView != nullptr)
3404                                 pView->InvalidateScreenRect(false);
3405                 }
3406         }
3407         else
3408         {
3409                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3410                 {
3411                         CMergeEditView *pView = GetGroupView(nPane);
3412                         if (pView != nullptr)
3413                                 pView->Invalidate();
3414                 }
3415         }
3416         // recalculate m_nTopSubLine
3417         m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3418
3419         UpdateCaret();
3420         
3421         RecalcVertScrollBar (false, false);
3422         RecalcHorzScrollBar (false, false);
3423 }
3424
3425 /**
3426 * @brief allocates GDI resources for printing
3427 * @param pDC [in] points to the printer device context
3428 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3429 */
3430 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3431 {
3432         GetParentFrame()->PostMessage(WM_TIMER);
3433
3434         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3435         {
3436                 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3437                 pView->m_bPrintHeader = true;
3438                 pView->m_bPrintFooter = true;
3439                 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3440         }
3441 }
3442
3443 /**
3444 * @brief frees GDI resources for printing
3445 * @param pDC [in] points to the printer device context
3446 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3447 */
3448 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3449 {
3450         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3451                 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3452
3453         GetParentFrame()->PostMessage(WM_TIMER);
3454 }
3455
3456 /**
3457 * @brief Gets header text to print
3458 * @param [in]  nPageNum the page number to print
3459 * @param [out] header text to print
3460 */
3461 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3462 {
3463         text = GetDocument()->GetTitle();
3464 }
3465
3466 /**
3467 * @brief Prints header
3468 * @param [in] nPageNum the page number to print
3469 */
3470 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3471 {
3472         if (m_nThisPane > 0)
3473                 return;
3474         int oldRight = m_rcPrintArea.right;
3475         m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3476         CGhostTextView::PrintHeader(pdc, nPageNum);
3477         m_rcPrintArea.right = oldRight;
3478 }
3479
3480 /**
3481 * @brief Prints footer
3482 * @param [in] nPageNum the page number to print
3483 */
3484 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3485 {
3486         if (m_nThisPane > 0)
3487                 return;
3488         int oldRight = m_rcPrintArea.right;
3489         m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3490         CGhostTextView::PrintFooter(pdc, nPageNum);
3491         m_rcPrintArea.right = oldRight;
3492 }
3493
3494 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3495 {
3496         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3497                 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3498 }
3499
3500 /**
3501 * @brief Prints or previews both panes.
3502 * @param pDC [in] points to the printer device context
3503 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3504 */
3505 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo) 
3506 {
3507         CRect rDraw = pInfo->m_rectDraw;
3508         CSize sz = rDraw.Size();
3509         CMergeDoc *pDoc = GetDocument();
3510
3511         SIZE szLeftTop, szRightBottom;
3512         GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3513         pDC->HIMETRICtoLP(&szLeftTop);
3514         pDC->HIMETRICtoLP(&szRightBottom);
3515         
3516         int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3517
3518         // print pane
3519         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3520         {
3521                 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3522                 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3523                 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3524                 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3525         }
3526 }
3527
3528 bool CMergeEditView::IsInitialized() const
3529 {
3530         CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3531         CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3532         return pBuffer->IsInitialized();
3533 }
3534
3535 /**
3536  * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3537  */
3538 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3539 {
3540         int     nBreaks[3] = {0};
3541         int nMaxBreaks = -1;
3542         CMergeDoc * pDoc = GetDocument();
3543         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3544         {
3545                 CMergeEditView *pView = GetGroupView(nPane);
3546                 if (pView != nullptr)
3547                 {
3548                         if (nLineIndex >= pView->GetLineCount())
3549                                 return 0;
3550                         pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3551                 }
3552                 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3553         }
3554
3555         if (nBreaks[m_nThisPane] < nMaxBreaks)
3556                 return nMaxBreaks - nBreaks[m_nThisPane];
3557         else
3558                 return 0;
3559 }
3560
3561 /**
3562  * @brief Invalidate sub line index cache from the specified index to the end of file.
3563  * @param [in] nLineIndex Index of the first line to invalidate 
3564  */
3565 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3566 {
3567         CMergeDoc * pDoc = GetDocument();
3568         ASSERT(pDoc != nullptr);
3569
3570     // We have to invalidate sub line index cache on both panes.
3571         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3572         {
3573                 CMergeEditView *pView = GetGroupView(nPane);
3574                 if (pView != nullptr)
3575                         pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3576         }
3577 }
3578
3579 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3580 {
3581         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3582                 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3583         CCrystalTextView::SetWordWrapping(bWordWrap);
3584 }
3585
3586 /**
3587  * @brief Swap the positions of the two panes
3588  */
3589 void CMergeEditView::OnViewSwapPanes()
3590 {
3591         GetDocument()->SwapFiles();
3592 }
3593
3594 /**
3595  * @brief Check if cursor is inside difference.
3596  * @return true if cursor is inside difference.
3597  */
3598 bool CMergeEditView::IsCursorInDiff() const
3599 {
3600         return m_bCurrentLineIsDiff;
3601 }
3602
3603 /**
3604 * @brief Determine if difference is visible on screen.
3605 * @param [in] nDiff Number of diff to check.
3606 * @return true if difference is visible.
3607 */
3608 bool CMergeEditView::IsDiffVisible(int nDiff)
3609 {
3610         const CMergeDoc *pd = GetDocument();
3611
3612         DIFFRANGE diff;
3613         pd->m_diffList.GetDiff(nDiff, diff);
3614
3615         return IsDiffVisible(diff);
3616 }
3617
3618 /**
3619  * @brief Determine if difference is visible on screen.
3620  * @param [in] diff diff to check.
3621  * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
3622  * @return true if difference is visible, false otherwise.
3623  */
3624 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
3625 {
3626         const int nDiffStart = GetSubLineIndex(diff.dbegin);
3627         const int nDiffEnd = GetSubLineIndex(diff.dend);
3628         // Diff's height is last line - first line + last line's line count
3629         const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
3630
3631         // If diff first line outside current view - context OR
3632         // if diff last line outside current view - context OR
3633         // if diff is bigger than screen
3634         if ((nDiffStart < m_nTopSubLine) ||
3635                 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
3636                 (nDiffHeight >= GetScreenLines()))
3637         {
3638                 return false;
3639         }
3640         else
3641         {
3642                 return true;
3643         }
3644 }
3645
3646 /** @brief Open help from mainframe when user presses F1*/
3647 void CMergeEditView::OnHelp()
3648 {
3649         theApp.ShowHelp(MergeViewHelpLocation);
3650 }
3651
3652 /**
3653  * @brief Called after document is loaded.
3654  * This function is called from CMergeDoc::OpenDocs() after documents are
3655  * loaded. So this is good place to set View's options etc.
3656  */
3657 void CMergeEditView::DocumentsLoaded()
3658 {
3659         // Enable/disable automatic rescan (rescanning after edit)
3660         EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
3661
3662         // SetTextType will revert to language dependent defaults for tab
3663         SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
3664         SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3665         const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3666                 GetDocument()->IsMixedEOL(m_nThisPane);
3667         SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE), mixedEOLs);
3668         SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3669         SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3670         SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3671
3672         // Enable Backspace at beginning of line
3673         SetDisableBSAtSOL(false);
3674
3675         // Set tab type (tabs/spaces)
3676         bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
3677         SetInsertTabs(bInsertTabs);
3678
3679         // Sometimes WinMerge doesn't update scrollbars correctly (they remain
3680         // disabled) after docs are open in screen. So lets make sure they are
3681         // really updated, even though this is unnecessary in most cases.
3682         RecalcHorzScrollBar();
3683         RecalcVertScrollBar();
3684 }
3685
3686 /**
3687  * @brief Update LocationView position.
3688  * This function updates LocationView position to given lines.
3689  * Usually we want to lines in file compare view and area in
3690  * LocationView to match. Be extra carefull to not call non-existing
3691  * LocationView.
3692  * @param [in] nTopLine Top line of current view.
3693  * @param [in] nBottomLine Bottom line of current view.
3694  */
3695 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
3696                 int nBottomLine /*= -1*/)
3697 {
3698         CMergeDoc *pDoc = GetDocument();
3699         if (pDoc == nullptr)
3700                 return;
3701
3702         CLocationView *pLocationView = pDoc->GetLocationView();
3703
3704         if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
3705         {
3706                 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
3707         }
3708 }
3709
3710 /**
3711  * @brief Enable/Disable view's selection margins.
3712  * Selection margins show bookmarks and word-wrap symbols, so they are pretty
3713  * useful. But it appears many users don't use/need those features and for them
3714  * selection margins are just wasted screen estate.
3715  */
3716 void CMergeEditView::OnViewMargin()
3717 {
3718         bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
3719         GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
3720
3721         SetSelectionMargin(!bViewMargin);
3722         CMergeDoc *pDoc = GetDocument();
3723         pDoc->RefreshOptions();
3724         pDoc->UpdateAllViews(this);
3725 }
3726
3727 /**
3728  * @brief Update GUI for Enable/Disable view's selection margin.
3729  * @param [in] pCmdUI Pointer to UI item to update.
3730  */
3731 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
3732 {
3733         pCmdUI->Enable(true);
3734         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3735 }
3736
3737 /**
3738 * @brief Create the "Change Scheme" sub menu.
3739 * @param [in] pCmdUI Pointer to UI item to update.
3740 */
3741 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
3742 {
3743         // Delete the place holder menu.
3744         pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
3745
3746         const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
3747
3748         String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
3749         AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
3750         AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
3751
3752         for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
3753         {
3754                 name = theApp.LoadString(i);
3755                 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
3756         }
3757
3758         pCmdUI->Enable(true);
3759 }
3760
3761 /**
3762 * @brief Change the editor's syntax highlighting scheme.
3763 * @param [in] nID Selected color scheme sub menu id.
3764 */
3765 void CMergeEditView::OnChangeScheme(UINT nID)
3766 {
3767         CMergeDoc *pDoc = GetDocument();
3768         ASSERT(pDoc != nullptr);
3769
3770         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3771         {
3772                 CMergeEditView *pView = GetGroupView(nPane);
3773                 ASSERT(pView != nullptr);
3774
3775                 if (pView != nullptr)
3776                 {
3777                         pView->SetTextType(CCrystalTextView::TextType(nID - ID_COLORSCHEME_FIRST));
3778                         pView->SetDisableBSAtSOL(false);
3779                 }
3780         }
3781
3782         pDoc->UpdateAllViews(nullptr);
3783 }
3784
3785 /**
3786 * @brief Enable all color schemes sub menu items.
3787 * @param [in] pCmdUI Pointer to UI item to update.
3788 */
3789 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
3790 {
3791         const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
3792         pCmdUI->SetRadio(bIsCurrentScheme);
3793         pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
3794 }
3795
3796 /**
3797  * @brief Called when mouse's wheel is scrolled.
3798  */
3799 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
3800 {
3801         if ( nFlags == MK_CONTROL )
3802         {
3803                 short amount = zDelta < 0 ? -1: 1;
3804                 ZoomText(amount);
3805
3806                 // no default CCrystalTextView
3807                 return CView::OnMouseWheel(nFlags, zDelta, pt);
3808         }
3809
3810         if (nFlags == MK_SHIFT)
3811         {
3812                 SCROLLINFO si = { sizeof SCROLLINFO };
3813                 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
3814
3815                 VERIFY(GetScrollInfo(SB_HORZ, &si));
3816
3817                 // new horz pos
3818                 si.nPos -= zDelta / 40;
3819                 if (si.nPos > si.nMax) si.nPos = si.nMax;
3820                 if (si.nPos < si.nMin) si.nPos = si.nMin;
3821
3822                 SetScrollInfo(SB_HORZ, &si);
3823
3824                 // for update
3825                 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
3826
3827                 // no default CCrystalTextView
3828                 return CView::OnMouseWheel(nFlags, zDelta, pt);
3829         }
3830
3831         return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
3832 }
3833
3834 /**
3835  * @brief Change font size (zoom) in views.
3836  * @param [in] amount Amount of change/zoom, negative number makes
3837  *  font smaller, positive number bigger and 0 reset the font size.
3838  */
3839 void CMergeEditView::ZoomText(short amount)
3840 {
3841         LOGFONT lf = { 0 };
3842         GetFont(lf);
3843
3844         const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
3845         int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
3846
3847         if ( amount == 0)
3848         {
3849                 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
3850         }
3851
3852         nPointSize += amount;
3853         if (nPointSize < 2)
3854                 nPointSize = 2;
3855
3856         lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
3857
3858         CMergeDoc *pDoc = GetDocument();
3859         ASSERT(pDoc != nullptr);
3860
3861         if (pDoc != nullptr)
3862         {
3863                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3864                 {
3865                         CMergeEditView *pView = GetGroupView(nPane);
3866                         ASSERT(pView != nullptr);
3867                         
3868                         if (pView != nullptr)
3869                         {
3870                                 pView->SetFont(lf);
3871                         }
3872                 }
3873         }
3874 }
3875
3876 /**
3877  * @brief Called when user selects View/Zoom In from menu.
3878  */
3879 void CMergeEditView::OnViewZoomIn()
3880 {
3881         ZoomText(1);
3882 }
3883
3884 /**
3885  * @brief Called when user selects View/Zoom Out from menu.
3886  */
3887 void CMergeEditView::OnViewZoomOut()
3888 {
3889         ZoomText(-1);
3890 }
3891
3892 /**
3893  * @brief Called when user selects View/Zoom Normal from menu.
3894  */
3895 void CMergeEditView::OnViewZoomNormal()
3896 {
3897         ZoomText(0);
3898 }
3899
3900 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
3901 {
3902         if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
3903         {
3904                 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
3905                 return;
3906         }
3907
3908         GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
3909 }
3910
3911 void CMergeEditView::OnWindowSplit()
3912 {
3913
3914         auto& wndSplitter = dynamic_cast<CChildFrame *>(GetParentFrame())->GetSplitter();
3915         CMergeDoc *pDoc = GetDocument();
3916         CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
3917         auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
3918         int nBuffer = m_nThisPane;
3919         if (pDoc->m_nGroups <= 2)
3920         {
3921                 wndSplitter.SplitRow(1);
3922                 wndSplitter.EqualizeRows();
3923         }
3924         else
3925         {
3926                 wndSplitter.SetActivePane(0, 0);
3927                 wndSplitter.DeleteRow(1);
3928                 if (pwndSplitterChild->GetColumnCount() > 1)
3929                         pwndSplitterChild->SetActivePane(0, nBuffer);
3930                 else
3931                         pwndSplitterChild->SetActivePane(nBuffer, 0);
3932         }
3933 }
3934
3935 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
3936 {
3937         pCmdUI->Enable(TRUE);
3938         pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
3939 }
3940