OSDN Git Service

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