OSDN Git Service

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