OSDN Git Service

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