OSDN Git Service

Merge pull request #101 from GreyMerlin/master
[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         OnNextdiff();
2750 }
2751
2752 /**
2753  * @brief Update "Copy right and advance" UI item
2754  */
2755 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
2756 {
2757         OnUpdateL2r(pCmdUI);
2758 }
2759
2760 /**
2761  * @brief Copy diff from right to left and advance to next diff
2762  */
2763 void CMergeEditView::OnR2LNext()
2764 {
2765         OnR2l();
2766         OnNextdiff();
2767 }
2768
2769 /**
2770  * @brief Update "Copy left and advance" UI item
2771  */
2772 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
2773 {
2774         OnUpdateR2l(pCmdUI);
2775 }
2776
2777 /**
2778  * @brief Change active pane in MergeView.
2779  * Changes active pane and makes sure cursor position is kept in
2780  * screen. Currently we put cursor in same line than in original
2781  * active pane but we could be smarter too? Maybe update cursor
2782  * only when it is not visible in new pane?
2783  */
2784 void CMergeEditView::OnChangePane()
2785 {
2786         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
2787         CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
2788         CMergeDoc *pDoc = GetDocument();
2789         bool bFound = false;
2790         CMergeEditView *pNextActiveView = nullptr;
2791         std::vector<CMergeEditView *> list = pDoc->GetViewList();
2792         list.insert(list.end(), list.begin(), list.end());
2793         for (auto& pView : list)
2794         {
2795                 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
2796                 {
2797                         pNextActiveView = pView;
2798                         break;
2799                 }
2800                 if (pWnd == pView)
2801                         bFound = true;
2802         }
2803         GetParentFrame()->SetActiveView(pNextActiveView);
2804         CPoint ptCursor = pWnd->GetCursorPos();
2805         ptCursor.x = 0;
2806         if (ptCursor.y >= pNextActiveView->GetLineCount())
2807                 ptCursor.y = pNextActiveView->GetLineCount() - 1;
2808         pNextActiveView->SetCursorPos(ptCursor);
2809         pNextActiveView->SetAnchor(ptCursor);
2810         pNextActiveView->SetSelection(ptCursor, ptCursor);
2811 }
2812
2813 /**
2814  * @brief Show "Go To" dialog and scroll views to line or diff.
2815  *
2816  * Before dialog is opened, current line and file is determined
2817  * and selected.
2818  * @note Conversions needed between apparent and real lines
2819  */
2820 void CMergeEditView::OnWMGoto()
2821 {
2822         WMGotoDlg dlg;
2823         CMergeDoc *pDoc = GetDocument();
2824         CPoint pos = GetCursorPos();
2825         int nRealLine = 0;
2826         int nLastLine = 0;
2827
2828         nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
2829         int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
2830         nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
2831
2832         // Set active file and current line selected in dialog
2833         dlg.m_strParam = strutils::to_str(nRealLine + 1);
2834         dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
2835         dlg.m_nGotoWhat = 0;
2836
2837         if (dlg.DoModal() == IDOK)
2838         {
2839                 CMergeDoc * pDoc1 = GetDocument();
2840                 CMergeEditView * pCurrentView = nullptr;
2841
2842                 // Get views
2843                 pCurrentView = GetGroupView(m_nThisPane);
2844
2845                 int num = 0;
2846                 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
2847
2848                 if (dlg.m_nGotoWhat == 0)
2849                 {
2850                         int nRealLine1 = num;
2851                         if (nRealLine1 < 0)
2852                                 nRealLine1 = 0;
2853                         if (nRealLine1 > nLastLine)
2854                                 nRealLine1 = nLastLine;
2855
2856                         GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
2857                 }
2858                 else
2859                 {
2860                         int diff = num;
2861                         if (diff < 0)
2862                                 diff = 0;
2863                         if (diff >= pDoc1->m_diffList.GetSize())
2864                                 diff = pDoc1->m_diffList.GetSize();
2865
2866                         pCurrentView->SelectDiff(diff, true, false);
2867                 }
2868         }
2869 }
2870
2871 /**
2872  * @brief Reload options.
2873  */
2874 void CMergeEditView::RefreshOptions()
2875
2876         m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
2877
2878         if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
2879                 SetInsertTabs(true);
2880         else
2881                 SetInsertTabs(false);
2882
2883         SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
2884
2885         if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
2886                 SetTextType(CCrystalTextView::SRC_PLAIN);
2887
2888         SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
2889         SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
2890
2891         SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
2892         SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
2893                 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2894                 GetDocument()->IsMixedEOL(m_nThisPane));
2895
2896         Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
2897 }
2898
2899 void CMergeEditView::OnScripts(UINT nID )
2900 {
2901         // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
2902         String text = GetSelectedText();
2903
2904         // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
2905         bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
2906         if (bChanged)
2907                 // now replace the text
2908                 ReplaceSelection(text.c_str(), text.length(), 0);
2909 }
2910
2911 /**
2912  * @brief Called when an editor script item is updated
2913  */
2914 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
2915 {
2916         // append the scripts submenu
2917         HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
2918         if (scriptsSubmenu != nullptr)
2919                 createScriptsSubmenu(scriptsSubmenu);
2920
2921         pCmdUI->Enable(true);
2922 }
2923
2924 /**
2925  * @brief Called when an editor script item is updated
2926  */
2927 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
2928 {
2929         pCmdUI->Enable(true);
2930
2931         CMergeDoc *pd = GetDocument();
2932         ASSERT(pd != nullptr);
2933         PrediffingInfo prediffer;
2934         pd->GetPrediffer(&prediffer);
2935
2936         if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2937         {
2938                 pCmdUI->SetRadio(false);
2939                 return;
2940         }
2941
2942         // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
2943         if (prediffer.m_PluginName.empty())
2944                 m_CurrentPredifferID = ID_NO_PREDIFFER;
2945
2946         pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
2947 }
2948
2949 /**
2950  * @brief Update "Prediffer" menuitem
2951  */
2952 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
2953 {
2954         // recreate the sub menu (to fill the "selected prediffers")
2955         GetMainFrame()->UpdatePrediffersMenu();
2956         pCmdUI->Enable();
2957 }
2958
2959 void CMergeEditView::OnNoPrediffer()
2960 {
2961         OnPrediffer(ID_NO_PREDIFFER);
2962 }
2963 /**
2964  * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
2965  */
2966 void CMergeEditView::OnPrediffer(UINT nID )
2967 {
2968         CMergeDoc *pd = GetDocument();
2969         ASSERT(pd != nullptr);
2970
2971         SetPredifferByMenu(nID);
2972         pd->FlushAndRescan(true);
2973 }
2974
2975 /**
2976  * @brief Handler for all prediffer choices.
2977  * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
2978  * ID_NO_PREDIFFER, & specific prediffers.
2979  */
2980 void CMergeEditView::SetPredifferByMenu(UINT nID )
2981 {
2982         CMergeDoc *pd = GetDocument();
2983         ASSERT(pd != nullptr);
2984
2985         if (nID == ID_NO_PREDIFFER)
2986         {
2987                 m_CurrentPredifferID = nID;
2988                 // All flags are set correctly during the construction
2989                 PrediffingInfo *infoPrediffer = new PrediffingInfo;
2990                 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
2991                 infoPrediffer->m_PluginName.clear();
2992                 pd->SetPrediffer(infoPrediffer);
2993                 pd->FlushAndRescan(true);
2994                 return;
2995         }
2996
2997         // get the scriptlet files
2998         PluginArray * piScriptArray = 
2999                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3000         PluginArray * piScriptArray2 = 
3001                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3002
3003         // build a PrediffingInfo structure fom the ID
3004         PrediffingInfo prediffer;
3005         prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
3006
3007         size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3008         if (pluginNumber < piScriptArray->size())
3009         {
3010                 prediffer.m_bWithFile = true;
3011                 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3012                 prediffer.m_PluginName = plugin->m_name;
3013         }
3014         else
3015         {
3016                 pluginNumber -= piScriptArray->size();
3017                 if (pluginNumber >= piScriptArray2->size())
3018                         return;
3019                 prediffer.m_bWithFile = false;
3020                 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3021                 prediffer.m_PluginName = plugin->m_name;
3022         }
3023
3024         // update data for the radio button
3025         m_CurrentPredifferID = nID;
3026
3027         // update the prediffer and rescan
3028         pd->SetPrediffer(&prediffer);
3029 }
3030
3031 /**
3032  * @brief Look through available prediffers, and return ID of requested one, if found
3033  */
3034 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3035 {
3036         size_t i;
3037         int ID = ID_PREDIFFERS_FIRST;
3038
3039         // Search file prediffers
3040         PluginArray * piScriptArray = 
3041                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3042         for (i=0; i<piScriptArray->size(); ++i, ++ID)
3043         {
3044                 const PluginInfoPtr & plugin = piScriptArray->at(i);
3045                 if (plugin->m_name == prediffer)
3046                         return ID;
3047         }
3048
3049         // Search buffer prediffers
3050         PluginArray * piScriptArray2 = 
3051                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3052         for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3053         {
3054                 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3055                 if (plugin->m_name == prediffer)
3056                         return ID;
3057         }
3058         return -1;
3059 }
3060
3061
3062 /**
3063  * @brief Look through available prediffers, and return ID of requested one, if found
3064  */
3065 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3066 {
3067         int id = FindPrediffer(prediffer);
3068         if (id<0) return false;
3069         SetPredifferByMenu(id);
3070         return true;
3071 }
3072
3073 /** 
3074  * @brief Goto given line.
3075  * @param [in] nLine Destination linenumber
3076  * @param [in] bRealLine if true linenumber is real line, otherwise
3077  * it is apparent line (including deleted lines)
3078  * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3079  */
3080 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3081 {
3082         CMergeDoc *pDoc = GetDocument();
3083         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3084         CMergeEditView *pCurrentView = nullptr;
3085         if (pSplitterWnd != nullptr)
3086                 pCurrentView = static_cast<CMergeEditView*>
3087                         (pSplitterWnd->GetActivePane());
3088
3089         int nRealLine = nLine;
3090         int nApparentLine = nLine;
3091
3092         // Compute apparent (shown linenumber) line
3093         if (bRealLine)
3094         {
3095                 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3096                         nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3097
3098                 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3099         }
3100         CPoint ptPos;
3101         ptPos.x = 0;
3102         ptPos.y = nApparentLine;
3103
3104         // Scroll line to center of view
3105         int nScrollLine = GetSubLineIndex(nApparentLine);
3106         nScrollLine -= GetScreenLines() / 2;
3107         if (nScrollLine < 0)
3108                 nScrollLine = 0;
3109
3110         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3111         {
3112                 CMergeEditView *pView = GetGroupView(nPane);
3113                 pView->ScrollToSubLine(nScrollLine);
3114                 if (ptPos.y < pView->GetLineCount())
3115                 {
3116                         pView->SetCursorPos(ptPos);
3117                         pView->SetAnchor(ptPos);
3118                 }
3119                 else
3120                 {
3121                         CPoint ptPos1(0, pView->GetLineCount() - 1);
3122                         pView->SetCursorPos(ptPos1);
3123                         pView->SetAnchor(ptPos1);
3124                 }
3125         }
3126
3127         // If goto target is another view - activate another view.
3128         // This is done for user convenience as user probably wants to
3129         // work with goto target file.
3130         if (GetGroupView(pane) != pCurrentView)
3131         {
3132                 if (pSplitterWnd != nullptr)
3133                 {
3134                         if (pSplitterWnd->GetColumnCount() > 1)
3135                                 pSplitterWnd->SetActivePane(0, pane);
3136                         else
3137                                 pSplitterWnd->SetActivePane(pane, 0);
3138                 }
3139         }
3140 }
3141
3142 /**
3143  * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3144  * a scroll bar.
3145  */
3146 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3147 {
3148         if (pScrollBar == nullptr)
3149         {
3150                 // Scroll did not come frome a scroll bar
3151                 // Find the appropriate scroll bar
3152                 // and send the message to the splitter window instead
3153                 // The event should eventually come back here but with a valid scrollbar
3154                 // Along the way it will be propagated to other windows that need it
3155                 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3156                 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3157                 pSplitterWnd->SendMessage(WM_HSCROLL,
3158                         MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3159                 return;
3160         }
3161         CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3162 }
3163
3164 /**
3165  * @brief When view is scrolled using scrollbars update location pane.
3166  */
3167 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3168 {
3169         if (pScrollBar == nullptr)
3170         {
3171                 // Scroll did not come frome a scroll bar
3172                 // Find the appropriate scroll bar
3173                 // and send the message to the splitter window instead
3174                 // The event should eventually come back here but with a valid scrollbar
3175                 // Along the way it will be propagated to other windows that need it
3176                 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3177                 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3178                 pSplitterWnd->SendMessage(WM_VSCROLL,
3179                         MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3180                 return;
3181         }
3182         CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3183
3184         if (nSBCode == SB_ENDSCROLL)
3185                 return;
3186
3187         // Note we cannot use nPos because of its 16-bit nature
3188         SCROLLINFO si = {0};
3189         si.cbSize = sizeof (si);
3190         si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3191         VERIFY (GetScrollInfo (SB_VERT, &si));
3192
3193         // Get the current position of scroll   box.
3194         int nCurPos =   si.nPos;
3195         
3196         UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3197 }
3198
3199 /**
3200  * @brief Copy selected lines adding linenumbers.
3201  */
3202 void CMergeEditView::OnEditCopyLineNumbers()
3203 {
3204         CPoint ptStart;
3205         CPoint ptEnd;
3206         CString strText;
3207         CString strLine;
3208         CString strNumLine;
3209
3210         CMergeDoc *pDoc = GetDocument();
3211         GetSelection(ptStart, ptEnd);
3212
3213         // Get last selected line (having widest linenumber)
3214         int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3215         size_t nNumWidth = strutils::to_str(line + 1).length();
3216         
3217         for (int i = ptStart.y; i <= ptEnd.y; i++)
3218         {
3219                 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3220                         continue;
3221
3222                 // We need to convert to real linenumbers
3223                 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3224
3225                 // Insert spaces to align different width linenumbers (99, 100)
3226                 strLine = GetLineText(i);
3227                 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3228                 
3229                 strText += sSpaces;
3230                 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3231                 strText += strNumLine;
3232         }
3233         PutToClipboard(strText, strText.GetLength(), m_bColumnSelection);
3234 }
3235
3236 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3237 {
3238         CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3239 }
3240
3241 /**
3242  * @brief Open active file with associated application.
3243  *
3244  * First tries to open file using shell 'Edit' action, since that
3245  * action open scripts etc. to editor instead of running them. If
3246  * edit-action is not registered, 'Open' action is used.
3247  */
3248 void CMergeEditView::OnOpenFile()
3249 {
3250         CMergeDoc * pDoc = GetDocument();
3251         ASSERT(pDoc != nullptr);
3252
3253         String sFileName = pDoc->m_filePaths[m_nThisPane];
3254         if (sFileName.empty())
3255                 return;
3256         HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3257                         0, 0, SW_SHOWNORMAL);
3258         if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3259                 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3260                          0, 0, SW_SHOWNORMAL);
3261         if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3262                 OnOpenFileWith();
3263 }
3264
3265 /**
3266  * @brief Open active file with app selection dialog
3267  */
3268 void CMergeEditView::OnOpenFileWith()
3269 {
3270         CMergeDoc * pDoc = GetDocument();
3271         ASSERT(pDoc != nullptr);
3272
3273         String sFileName = pDoc->m_filePaths[m_nThisPane];
3274         if (sFileName.empty())
3275                 return;
3276
3277         CString sysdir;
3278         if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3279                 return;
3280         sysdir.ReleaseBuffer();
3281         CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3282         ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3283                         sysdir, SW_SHOWNORMAL);
3284 }
3285
3286 /**
3287  * @brief Open active file with external editor
3288  */
3289 void CMergeEditView::OnOpenFileWithEditor()
3290 {
3291         CMergeDoc * pDoc = GetDocument();
3292         ASSERT(pDoc != nullptr);
3293
3294         String sFileName = pDoc->m_filePaths[m_nThisPane];
3295         if (sFileName.empty())
3296                 return;
3297
3298         int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3299         theApp.OpenFileToExternalEditor(sFileName.c_str(), nRealLine);
3300 }
3301
3302 /**
3303  * @brief Force repaint of the location pane.
3304  */
3305 void CMergeEditView::RepaintLocationPane()
3306 {
3307         // Must force recalculation due to caching of data in location pane.
3308         CLocationView *pLocationView = GetDocument()->GetLocationView();
3309         if (pLocationView != nullptr)
3310                 pLocationView->ForceRecalculate();
3311 }
3312
3313 /**
3314  * @brief Enables/disables linediff (different color for diffs)
3315  */
3316 void CMergeEditView::OnViewLineDiffs()
3317 {
3318         bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3319         GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3320
3321         // Call CMergeDoc RefreshOptions() to refresh *both* views
3322         CMergeDoc *pDoc = GetDocument();
3323         pDoc->RefreshOptions();
3324         pDoc->FlushAndRescan(true);
3325 }
3326
3327 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3328 {
3329         pCmdUI->Enable(true);
3330         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3331 }
3332
3333 /**
3334  * @brief Enables/disables line number
3335  */
3336 void CMergeEditView::OnViewLineNumbers()
3337 {
3338         GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3339
3340         // Call CMergeDoc RefreshOptions() to refresh *both* views
3341         CMergeDoc *pDoc = GetDocument();
3342         pDoc->RefreshOptions();
3343 }
3344
3345 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3346 {
3347         pCmdUI->Enable(true);
3348         pCmdUI->SetCheck(GetViewLineNumbers());
3349 }
3350
3351 /**
3352  * @brief Enables/disables word wrap
3353  */
3354 void CMergeEditView::OnViewWordWrap()
3355 {
3356         GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3357
3358         // Call CMergeDoc RefreshOptions() to refresh *both* views
3359         CMergeDoc *pDoc = GetDocument();
3360         pDoc->RefreshOptions();
3361         pDoc->UpdateAllViews(this);
3362
3363         UpdateCaret();
3364 }
3365
3366 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3367 {
3368         pCmdUI->Enable(true);
3369         pCmdUI->SetCheck(m_bWordWrap);
3370 }
3371
3372 void CMergeEditView::OnViewWhitespace() 
3373 {
3374         GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3375
3376         // Call CMergeDoc RefreshOptions() to refresh *both* views
3377         CMergeDoc *pDoc = GetDocument();
3378         pDoc->RefreshOptions();
3379 }
3380
3381 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI) 
3382 {
3383         pCmdUI->SetCheck(GetViewTabs());
3384 }
3385
3386 void CMergeEditView::OnSize(UINT nType, int cx, int cy) 
3387 {
3388         if (!IsInitialized())
3389                 return;
3390
3391         CMergeDoc * pDoc = GetDocument();
3392         if (m_nThisPane < pDoc->m_nBuffers - 1)
3393         {
3394                 // To calculate subline index correctly
3395                 // we have to invalidate line cache in all pane before calling the function related the subline.
3396                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3397                 {
3398                         CMergeEditView *pView = GetGroupView(nPane);
3399                         if (pView != nullptr)
3400                                 pView->InvalidateScreenRect(false);
3401                 }
3402         }
3403         else
3404         {
3405                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3406                 {
3407                         CMergeEditView *pView = GetGroupView(nPane);
3408                         if (pView != nullptr)
3409                                 pView->Invalidate();
3410                 }
3411         }
3412         // recalculate m_nTopSubLine
3413         m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3414
3415         UpdateCaret();
3416         
3417         RecalcVertScrollBar (false, false);
3418         RecalcHorzScrollBar (false, false);
3419 }
3420
3421 /**
3422 * @brief allocates GDI resources for printing
3423 * @param pDC [in] points to the printer device context
3424 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3425 */
3426 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3427 {
3428         GetParentFrame()->PostMessage(WM_TIMER);
3429
3430         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3431         {
3432                 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3433                 pView->m_bPrintHeader = true;
3434                 pView->m_bPrintFooter = true;
3435                 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3436         }
3437 }
3438
3439 /**
3440 * @brief frees GDI resources for printing
3441 * @param pDC [in] points to the printer device context
3442 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3443 */
3444 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3445 {
3446         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3447                 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3448
3449         GetParentFrame()->PostMessage(WM_TIMER);
3450 }
3451
3452 /**
3453 * @brief Gets header text to print
3454 * @param [in]  nPageNum the page number to print
3455 * @param [out] header text to print
3456 */
3457 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3458 {
3459         text = GetDocument()->GetTitle();
3460 }
3461
3462 /**
3463 * @brief Prints header
3464 * @param [in] nPageNum the page number to print
3465 */
3466 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3467 {
3468         if (m_nThisPane > 0)
3469                 return;
3470         int oldRight = m_rcPrintArea.right;
3471         m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3472         CGhostTextView::PrintHeader(pdc, nPageNum);
3473         m_rcPrintArea.right = oldRight;
3474 }
3475
3476 /**
3477 * @brief Prints footer
3478 * @param [in] nPageNum the page number to print
3479 */
3480 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3481 {
3482         if (m_nThisPane > 0)
3483                 return;
3484         int oldRight = m_rcPrintArea.right;
3485         m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3486         CGhostTextView::PrintFooter(pdc, nPageNum);
3487         m_rcPrintArea.right = oldRight;
3488 }
3489
3490 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3491 {
3492         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3493                 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3494 }
3495
3496 /**
3497 * @brief Prints or previews both panes.
3498 * @param pDC [in] points to the printer device context
3499 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3500 */
3501 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo) 
3502 {
3503         CRect rDraw = pInfo->m_rectDraw;
3504         CSize sz = rDraw.Size();
3505         CMergeDoc *pDoc = GetDocument();
3506
3507         SIZE szLeftTop, szRightBottom;
3508         GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3509         pDC->HIMETRICtoLP(&szLeftTop);
3510         pDC->HIMETRICtoLP(&szRightBottom);
3511         
3512         int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3513
3514         // print pane
3515         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3516         {
3517                 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3518                 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3519                 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3520                 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3521         }
3522 }
3523
3524 bool CMergeEditView::IsInitialized() const
3525 {
3526         CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3527         CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3528         return pBuffer->IsInitialized();
3529 }
3530
3531 /**
3532  * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3533  */
3534 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3535 {
3536         int     nBreaks[3] = {0};
3537         int nMaxBreaks = -1;
3538         CMergeDoc * pDoc = GetDocument();
3539         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3540         {
3541                 CMergeEditView *pView = GetGroupView(nPane);
3542                 if (pView != nullptr)
3543                 {
3544                         if (nLineIndex >= pView->GetLineCount())
3545                                 return 0;
3546                         pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3547                 }
3548                 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3549         }
3550
3551         if (nBreaks[m_nThisPane] < nMaxBreaks)
3552                 return nMaxBreaks - nBreaks[m_nThisPane];
3553         else
3554                 return 0;
3555 }
3556
3557 /**
3558  * @brief Invalidate sub line index cache from the specified index to the end of file.
3559  * @param [in] nLineIndex Index of the first line to invalidate 
3560  */
3561 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3562 {
3563         CMergeDoc * pDoc = GetDocument();
3564         ASSERT(pDoc != nullptr);
3565
3566     // We have to invalidate sub line index cache on both panes.
3567         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3568         {
3569                 CMergeEditView *pView = GetGroupView(nPane);
3570                 if (pView != nullptr)
3571                         pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3572         }
3573 }
3574
3575 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3576 {
3577         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3578                 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3579         CCrystalTextView::SetWordWrapping(bWordWrap);
3580 }
3581
3582 /**
3583  * @brief Swap the positions of the two panes
3584  */
3585 void CMergeEditView::OnViewSwapPanes()
3586 {
3587         GetDocument()->SwapFiles();
3588 }
3589
3590 /**
3591  * @brief Check if cursor is inside difference.
3592  * @return true if cursor is inside difference.
3593  */
3594 bool CMergeEditView::IsCursorInDiff() const
3595 {
3596         return m_bCurrentLineIsDiff;
3597 }
3598
3599 /**
3600 * @brief Determine if difference is visible on screen.
3601 * @param [in] nDiff Number of diff to check.
3602 * @return true if difference is visible.
3603 */
3604 bool CMergeEditView::IsDiffVisible(int nDiff)
3605 {
3606         const CMergeDoc *pd = GetDocument();
3607
3608         DIFFRANGE diff;
3609         pd->m_diffList.GetDiff(nDiff, diff);
3610
3611         return IsDiffVisible(diff);
3612 }
3613
3614 /**
3615  * @brief Determine if difference is visible on screen.
3616  * @param [in] diff diff to check.
3617  * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
3618  * @return true if difference is visible, false otherwise.
3619  */
3620 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
3621 {
3622         const int nDiffStart = GetSubLineIndex(diff.dbegin);
3623         const int nDiffEnd = GetSubLineIndex(diff.dend);
3624         // Diff's height is last line - first line + last line's line count
3625         const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
3626
3627         // If diff first line outside current view - context OR
3628         // if diff last line outside current view - context OR
3629         // if diff is bigger than screen
3630         if ((nDiffStart < m_nTopSubLine) ||
3631                 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
3632                 (nDiffHeight >= GetScreenLines()))
3633         {
3634                 return false;
3635         }
3636         else
3637         {
3638                 return true;
3639         }
3640 }
3641
3642 /** @brief Open help from mainframe when user presses F1*/
3643 void CMergeEditView::OnHelp()
3644 {
3645         theApp.ShowHelp(MergeViewHelpLocation);
3646 }
3647
3648 /**
3649  * @brief Called after document is loaded.
3650  * This function is called from CMergeDoc::OpenDocs() after documents are
3651  * loaded. So this is good place to set View's options etc.
3652  */
3653 void CMergeEditView::DocumentsLoaded()
3654 {
3655         // Enable/disable automatic rescan (rescanning after edit)
3656         EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
3657
3658         // SetTextType will revert to language dependent defaults for tab
3659         SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
3660         SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3661         const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3662                 GetDocument()->IsMixedEOL(m_nThisPane);
3663         SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE), mixedEOLs);
3664         SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3665         SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3666         SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3667
3668         // Enable Backspace at beginning of line
3669         SetDisableBSAtSOL(false);
3670
3671         // Set tab type (tabs/spaces)
3672         bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
3673         SetInsertTabs(bInsertTabs);
3674
3675         // Sometimes WinMerge doesn't update scrollbars correctly (they remain
3676         // disabled) after docs are open in screen. So lets make sure they are
3677         // really updated, even though this is unnecessary in most cases.
3678         RecalcHorzScrollBar();
3679         RecalcVertScrollBar();
3680 }
3681
3682 /**
3683  * @brief Update LocationView position.
3684  * This function updates LocationView position to given lines.
3685  * Usually we want to lines in file compare view and area in
3686  * LocationView to match. Be extra carefull to not call non-existing
3687  * LocationView.
3688  * @param [in] nTopLine Top line of current view.
3689  * @param [in] nBottomLine Bottom line of current view.
3690  */
3691 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
3692                 int nBottomLine /*= -1*/)
3693 {
3694         CMergeDoc *pDoc = GetDocument();
3695         if (pDoc == nullptr)
3696                 return;
3697
3698         CLocationView *pLocationView = pDoc->GetLocationView();
3699
3700         if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
3701         {
3702                 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
3703         }
3704 }
3705
3706 /**
3707  * @brief Enable/Disable view's selection margins.
3708  * Selection margins show bookmarks and word-wrap symbols, so they are pretty
3709  * useful. But it appears many users don't use/need those features and for them
3710  * selection margins are just wasted screen estate.
3711  */
3712 void CMergeEditView::OnViewMargin()
3713 {
3714         bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
3715         GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
3716
3717         SetSelectionMargin(!bViewMargin);
3718         CMergeDoc *pDoc = GetDocument();
3719         pDoc->RefreshOptions();
3720         pDoc->UpdateAllViews(this);
3721 }
3722
3723 /**
3724  * @brief Update GUI for Enable/Disable view's selection margin.
3725  * @param [in] pCmdUI Pointer to UI item to update.
3726  */
3727 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
3728 {
3729         pCmdUI->Enable(true);
3730         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3731 }
3732
3733 /**
3734 * @brief Create the "Change Scheme" sub menu.
3735 * @param [in] pCmdUI Pointer to UI item to update.
3736 */
3737 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
3738 {
3739         // Delete the place holder menu.
3740         pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
3741
3742         const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
3743
3744         String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
3745         AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
3746         AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
3747
3748         for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
3749         {
3750                 name = theApp.LoadString(i);
3751                 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
3752         }
3753
3754         pCmdUI->Enable(true);
3755 }
3756
3757 /**
3758 * @brief Change the editor's syntax highlighting scheme.
3759 * @param [in] nID Selected color scheme sub menu id.
3760 */
3761 void CMergeEditView::OnChangeScheme(UINT nID)
3762 {
3763         CMergeDoc *pDoc = GetDocument();
3764         ASSERT(pDoc != nullptr);
3765
3766         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3767         {
3768                 CMergeEditView *pView = GetGroupView(nPane);
3769                 ASSERT(pView != nullptr);
3770
3771                 if (pView != nullptr)
3772                 {
3773                         pView->SetTextType(CCrystalTextView::TextType(nID - ID_COLORSCHEME_FIRST));
3774                         pView->SetDisableBSAtSOL(false);
3775                 }
3776         }
3777
3778         pDoc->UpdateAllViews(nullptr);
3779 }
3780
3781 /**
3782 * @brief Enable all color schemes sub menu items.
3783 * @param [in] pCmdUI Pointer to UI item to update.
3784 */
3785 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
3786 {
3787         const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
3788         pCmdUI->SetRadio(bIsCurrentScheme);
3789         pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
3790 }
3791
3792 /**
3793  * @brief Called when mouse's wheel is scrolled.
3794  */
3795 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
3796 {
3797         if ( nFlags == MK_CONTROL )
3798         {
3799                 short amount = zDelta < 0 ? -1: 1;
3800                 ZoomText(amount);
3801
3802                 // no default CCrystalTextView
3803                 return CView::OnMouseWheel(nFlags, zDelta, pt);
3804         }
3805
3806         if (nFlags == MK_SHIFT)
3807         {
3808                 SCROLLINFO si = { sizeof SCROLLINFO };
3809                 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
3810
3811                 VERIFY(GetScrollInfo(SB_HORZ, &si));
3812
3813                 // new horz pos
3814                 si.nPos -= zDelta / 40;
3815                 if (si.nPos > si.nMax) si.nPos = si.nMax;
3816                 if (si.nPos < si.nMin) si.nPos = si.nMin;
3817
3818                 SetScrollInfo(SB_HORZ, &si);
3819
3820                 // for update
3821                 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
3822
3823                 // no default CCrystalTextView
3824                 return CView::OnMouseWheel(nFlags, zDelta, pt);
3825         }
3826
3827         return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
3828 }
3829
3830 /**
3831  * @brief Change font size (zoom) in views.
3832  * @param [in] amount Amount of change/zoom, negative number makes
3833  *  font smaller, positive number bigger and 0 reset the font size.
3834  */
3835 void CMergeEditView::ZoomText(short amount)
3836 {
3837         LOGFONT lf = { 0 };
3838         GetFont(lf);
3839
3840         const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
3841         int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
3842
3843         if ( amount == 0)
3844         {
3845                 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
3846         }
3847
3848         nPointSize += amount;
3849         if (nPointSize < 2)
3850                 nPointSize = 2;
3851
3852         lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
3853
3854         CMergeDoc *pDoc = GetDocument();
3855         ASSERT(pDoc != nullptr);
3856
3857         if (pDoc != nullptr)
3858         {
3859                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
3860                 {
3861                         CMergeEditView *pView = GetGroupView(nPane);
3862                         ASSERT(pView != nullptr);
3863                         
3864                         if (pView != nullptr)
3865                         {
3866                                 pView->SetFont(lf);
3867                         }
3868                 }
3869         }
3870 }
3871
3872 /**
3873  * @brief Called when user selects View/Zoom In from menu.
3874  */
3875 void CMergeEditView::OnViewZoomIn()
3876 {
3877         ZoomText(1);
3878 }
3879
3880 /**
3881  * @brief Called when user selects View/Zoom Out from menu.
3882  */
3883 void CMergeEditView::OnViewZoomOut()
3884 {
3885         ZoomText(-1);
3886 }
3887
3888 /**
3889  * @brief Called when user selects View/Zoom Normal from menu.
3890  */
3891 void CMergeEditView::OnViewZoomNormal()
3892 {
3893         ZoomText(0);
3894 }
3895
3896 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
3897 {
3898         if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
3899         {
3900                 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
3901                 return;
3902         }
3903
3904         GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
3905 }
3906
3907 void CMergeEditView::OnWindowSplit()
3908 {
3909
3910         auto& wndSplitter = dynamic_cast<CChildFrame *>(GetParentFrame())->GetSplitter();
3911         CMergeDoc *pDoc = GetDocument();
3912         CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
3913         auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
3914         int nBuffer = m_nThisPane;
3915         if (pDoc->m_nGroups <= 2)
3916         {
3917                 wndSplitter.SplitRow(1);
3918                 wndSplitter.EqualizeRows();
3919         }
3920         else
3921         {
3922                 wndSplitter.SetActivePane(0, 0);
3923                 wndSplitter.DeleteRow(1);
3924                 if (pwndSplitterChild->GetColumnCount() > 1)
3925                         pwndSplitterChild->SetActivePane(0, nBuffer);
3926                 else
3927                         pwndSplitterChild->SetActivePane(nBuffer, 0);
3928         }
3929 }
3930
3931 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
3932 {
3933         pCmdUI->Enable(TRUE);
3934         pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
3935 }
3936